summaryrefslogtreecommitdiff
path: root/themes
diff options
context:
space:
mode:
Diffstat (limited to 'themes')
-rw-r--r--themes/#LeafParticleRender.cpp#162
-rw-r--r--themes/LeafParticleRender.cpp19
-rw-r--r--themes/List.h222
-rw-r--r--themes/Logger.cpp123
-rw-r--r--themes/Logger.h43
-rw-r--r--themes/Renderer2d.cpp9
-rw-r--r--themes/Renderer2d.h2
-rw-r--r--themes/Snowflake.cpp136
-rw-r--r--themes/Snowflake.h46
-rw-r--r--themes/TreeShape.cpp6
-rw-r--r--themes/dist/output.js103
-rwxr-xr-xthemes/dist/output.wasmbin66787 -> 82223 bytes
-rw-r--r--themes/main.cpp9
13 files changed, 842 insertions, 38 deletions
diff --git a/themes/#LeafParticleRender.cpp# b/themes/#LeafParticleRender.cpp#
new file mode 100644
index 0000000..2cb0b35
--- /dev/null
+++ b/themes/#LeafParticleRender.cpp#
@@ -0,0 +1,162 @@
+#include "LeafParticleRender.h"
+#include "Renderer2d.h"
+#include "mathlib.h"
+#include "TreeShape.h"
+#include "types.h"
+#include <math.h>
+
+const int32 verticesPerLeaf = 6;
+const float32 leafRadius = 3.f;
+const int32 fallChanceMax = 100;
+
+inline void updateLeaf(Renderer2dVertex* vertices, Vector2 position, Vector4 color, float32 scale) {
+ float32 radius = scale * leafRadius;
+ Vector2 bottomLeft = Vector2(-radius, -radius) + position;
+ Vector2 bottomRight = Vector2(radius, -radius) + position;
+ Vector2 topLeft = Vector2(-radius, radius) + position;
+ Vector2 topRight = Vector2(radius, radius) + position;
+
+ vertices[0] = { bottomLeft, color };
+ vertices[1] = { bottomRight, color };
+ vertices[2] = { topLeft, color };
+ vertices[3] = { topLeft, color };
+ vertices[4] = { topRight, color };
+ vertices[5] = { bottomRight, color };
+}
+
+void LeafParticleRender::load(Renderer2d *renderer, TreeShapeLoadResult* lr) {
+ LeafParticleLoadData ld;
+ ld.numLeaves = 256;
+ numLeaves = ld.numLeaves;
+ numVertices = ld.numLeaves * verticesPerLeaf;
+
+ updateData = new LeafParticleUpdateData[numLeaves];
+ vertices = new Renderer2dVertex[numVertices];
+
+ for (int32 leafIdx = 0; leafIdx < numLeaves; leafIdx++) {
+ int32 randomBranch = randomIntBetween(0, lr->numBranches);
+ int32 randomVertex = randomIntBetween(0, 6); // TODO: Manually entering num vertices per branch.
+ updateData[leafIdx].vertexToFollow = &lr->updateData[randomBranch].vertices[randomVertex];
+ updateData[leafIdx].fallChance = randomIntBetween(0, fallChanceMax);
+ updateData[leafIdx].color = Vector4(randomFloatBetween(0.3, 0.9), randomFloatBetween(0.1, 0.6), 0, 1);
+ updateData[leafIdx].vertexPtr = &vertices[leafIdx * verticesPerLeaf];
+ updateData[leafIdx].resetTime = randomFloatBetween(4.f, 6.f);
+ }
+
+ useShader(renderer->shader);
+
+ glGenVertexArrays(1, &vao);
+ glBindVertexArray(vao);
+
+ glGenBuffers(1, &vbo);
+ glBindBuffer(GL_ARRAY_BUFFER, vbo);
+ glBufferData(GL_ARRAY_BUFFER, numVertices * sizeof(Renderer2dVertex), &vertices[0], GL_DYNAMIC_DRAW);
+
+ glEnableVertexAttribArray(renderer->attributes.position);
+ glVertexAttribPointer(renderer->attributes.position, 2, GL_FLOAT, GL_FALSE, sizeof(Renderer2dVertex), (GLvoid *)0);
+
+ glEnableVertexAttribArray(renderer->attributes.color);
+ glVertexAttribPointer(renderer->attributes.color, 4, GL_FLOAT, GL_FALSE, sizeof(Renderer2dVertex), (GLvoid *)offsetof(Renderer2dVertex, color));
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindVertexArray(0);
+}
+
+void LeafParticleRender::update(float32 dtSeconds) {
+ elapsedTimeSeconds += dtSeconds;
+
+ // Every time the fallIntervalSeconds passes, we remove one leaf
+ // from the tree and send it barrelling towards the earth.
+ int32 fallRoll;
+ bool didGenerateFall = false;
+ if (elapsedTimeSeconds >= fallIntervalSeconds) {
+ fallRoll = randomIntBetween(0, fallChanceMax);
+ didGenerateFall = true;
+ elapsedTimeSeconds = 0;
+ }
+
+ for (int32 leafIdx = 0; leafIdx < numLeaves; leafIdx++) {
+ auto updateDataItem = &updateData[leafIdx];
+
+ if (didGenerateFall) {
+ if (updateDataItem->state == LeafParticleState::OnTree && updateDataItem->fallChance == fallRoll) {
+ updateDataItem->state = LeafParticleState::Falling;
+ updateDataItem->fallPosition = updateDataItem->vertexToFollow->position;
+ updateDataItem->fallVerticalVelocity = -randomFloatBetween(15.f, 25.f);
+ updateDataItem->fallHorizontalFrequency = randomFloatBetween(3.f, 5.f);
+ }
+ }
+
+ switch (updateDataItem->state) {
+ case (LeafParticleState::Remerging): {
+ updateDataItem->timeElapsedSeconds += dtSeconds;
+
+ if (updateDataItem->timeElapsedSeconds >= updateDataItem->resetTime) {
+ updateDataItem->timeElapsedSeconds = 0.f;
+ updateDataItem->state = LeafParticleState::OnTree;
+ updateDataItem->color.w = 1.f;
+ updateDataItem->scale = 1.f;
+ }
+ else {
+ updateDataItem->color.w = (updateDataItem->timeElapsedSeconds / updateDataItem->resetTime);
+ updateDataItem->scale = (updateDataItem->timeElapsedSeconds / updateDataItem->resetTime);
+ }
+
+ updateLeaf(updateDataItem->vertexPtr, updateDataItem->vertexToFollow->position, updateDataItem->color, updateDataItem->scale);
+ break;
+ }
+ case (LeafParticleState::OnGround): {
+ updateDataItem->timeElapsedSeconds += dtSeconds;
+
+ if (updateDataItem->timeElapsedSeconds >= updateDataItem->resetTime) {
+ updateDataItem->timeElapsedSeconds = 0.f;
+ updateDataItem->color.w = 0.f;
+ updateDataItem->state = LeafParticleState::Remerging;
+ }
+ else {
+ updateDataItem->color.w = 1.f - (updateDataItem->timeElapsedSeconds / updateDataItem->resetTime);
+ updateLeaf(updateDataItem->vertexPtr, updateDataItem->fallPosition, updateDataItem->color, updateDataItem->scale);
+ }
+ break;
+ }
+ case (LeafParticleState::Falling): {
+ updateDataItem->timeElapsedSeconds += dtSeconds;
+ const float32 xPosUpdate = cosf(updateDataItem->fallHorizontalFrequency * updateDataItem->timeElapsedSeconds);
+ updateDataItem->fallPosition.x += xPosUpdate;
+ updateDataItem->fallPosition.y += updateDataItem->fallVerticalVelocity * dtSeconds;
+ if (updateDataItem->fallPosition.y <= 50.f) { // TODO: Hardcoded ground for now
+ updateDataItem->fallPosition.y = 50.f;
+ updateDataItem->state = LeafParticleState::OnGround;
+ updateDataItem->timeElapsedSeconds = 0;
+ updateDataItem->resetTime = randomFloatBetween(2.f, 5.f); // TODO: Hardcoded reset interval
+ }
+ updateLeaf(updateDataItem->vertexPtr, updateDataItem->fallPosition, updateDataItem->color, updateDataItem->scale);
+ break;
+ }
+ case (LeafParticleState::OnTree): {
+ updateLeaf(updateDataItem->vertexPtr, updateDataItem->vertexToFollow->position, updateDataItem->color, updateDataItem->scale);
+ break;
+ }
+ }
+ }
+}
+
+void LeafParticleRender::render(Renderer2d *renderer) {
+ setShaderMat4(renderer->uniforms.model, model);
+
+ glBindBuffer(GL_ARRAY_BUFFER, vbo);
+ glBufferSubData(GL_ARRAY_BUFFER, 0, numVertices * sizeof(Renderer2dVertex), &vertices[0]);
+
+ glBindVertexArray(vao);
+ glDrawArrays(GL_TRIANGLES, 0, numVertices);
+ glBindVertexArray(0);
+}
+
+void LeafParticleRender::unload() {
+ glDeleteVertexArrays(1, &vao);
+ glDeleteBuffers(1, &vbo);
+ delete [] vertices;
+ delete [] updateData;
+
+ elapsedTimeSeconds = 0;
+} \ No newline at end of file
diff --git a/themes/LeafParticleRender.cpp b/themes/LeafParticleRender.cpp
index 2fc89ac..e1ed1ec 100644
--- a/themes/LeafParticleRender.cpp
+++ b/themes/LeafParticleRender.cpp
@@ -16,12 +16,12 @@ inline void updateLeaf(Renderer2dVertex* vertices, Vector2 position, Vector4 col
Vector2 topLeft = Vector2(-radius, radius) + position;
Vector2 topRight = Vector2(radius, radius) + position;
- vertices[0] = { bottomLeft, color };
- vertices[1] = { bottomRight, color };
- vertices[2] = { topLeft, color };
- vertices[3] = { topLeft, color };
- vertices[4] = { topRight, color };
- vertices[5] = { bottomRight, color };
+ vertices[0] = { bottomLeft, color, Mat4x4() };
+ vertices[1] = { bottomRight, color, Mat4x4() };
+ vertices[2] = { topLeft, color, Mat4x4() };
+ vertices[3] = { topLeft, color, Mat4x4() };
+ vertices[4] = { topRight, color, Mat4x4() };
+ vertices[5] = { bottomRight, color, Mat4x4() };
}
void LeafParticleRender::load(Renderer2d *renderer, TreeShapeLoadResult* lr) {
@@ -58,6 +58,11 @@ void LeafParticleRender::load(Renderer2d *renderer, TreeShapeLoadResult* lr) {
glEnableVertexAttribArray(renderer->attributes.color);
glVertexAttribPointer(renderer->attributes.color, 4, GL_FLOAT, GL_FALSE, sizeof(Renderer2dVertex), (GLvoid *)offsetof(Renderer2dVertex, color));
+ for (int32 idx = 0; idx < 4; idx++) {
+ glEnableVertexAttribArray(renderer->attributes.vMatrix + idx);
+ glVertexAttribPointer(renderer->attributes.vMatrix + idx, 4, GL_FLOAT, GL_FALSE, sizeof(Renderer2dVertex), (GLvoid *)(offsetof(Renderer2dVertex, vMatrix) + (idx * 16)));
+ }
+
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
@@ -158,4 +163,4 @@ void LeafParticleRender::unload() {
delete [] vertices;
elapsedTimeSeconds = 0;
-} \ No newline at end of file
+}
diff --git a/themes/List.h b/themes/List.h
new file mode 100644
index 0000000..00a466a
--- /dev/null
+++ b/themes/List.h
@@ -0,0 +1,222 @@
+#pragma once
+#include <cstdlib>
+#include <cstring>
+#include "Logger.h"
+
+#define FOREACH(list) \
+ for (size_t idx = 0; idx < list.numElements; idx++) \
+ if (auto value = list.getValue(idx)) \
+
+
+template <typename T>
+struct List {
+ T* data = nullptr;
+ size_t capacity = 0;
+ size_t numElements = 0;
+ bool growDynamically = true;
+
+ void allocate(size_t size);
+ void add(T* element);
+ void add(T& element);
+ void add(T&& element);
+ bool grow(size_t newSize);
+ void set(T* value, size_t index);
+ void remove(size_t index);
+ void clear();
+ void deallocate();
+ bool isEmpty() {
+ return data == nullptr || numElements == 0;
+ }
+ T* getValue(int index) const;
+ T& operator[](int idx) const;
+ void binarySort(int (*f)(T *first, T* second));
+ void setFromArray(T* arry, int size) {
+ allocate(size);
+ memcpy(data, arry, size * sizeof(T));
+ numElements = size;
+ }
+ void remove(int index) {
+ if (index >= numElements) {
+ logger_error("Cannot remove element at index: %d", index);
+ return;
+ }
+
+
+ if (index == numElements - 1) {
+ numElements--;
+ return;
+ }
+
+ memmove(data[index], data[index + 1], sizeof(T) * (numElements - index));
+ numElements--;
+ }
+};
+
+template <typename T>
+void List<T>::allocate(size_t size) {
+ if (size == 0 || size == capacity) {
+ numElements = 0;
+ return;
+ }
+
+ if (data != nullptr) {
+ deallocate();
+ }
+
+ data = static_cast<T*>(malloc(sizeof(T) * size));
+ capacity = size;
+ numElements = 0;
+}
+
+template <typename T>
+bool List<T>::grow(size_t newSize) {
+ if (!growDynamically) {
+ return false;
+ }
+
+ if (newSize == 0) {
+ return false;
+ }
+
+ T* newData = static_cast<T*>(malloc(sizeof(T) * newSize));
+
+ if (data != nullptr) {
+ memcpy(newData, data, numElements * sizeof(T));
+ delete data;
+ }
+
+ data = newData;
+ capacity = newSize;
+ return true;
+}
+
+template <typename T>
+void List<T>::set(T* value, size_t index) {
+ if (index >= capacity && !grow(index * 2)) {
+ return;
+ }
+
+ memcpy(&data[index], value, sizeof(T));
+}
+
+template <typename T>
+void List<T>::add(T* element) {
+ if (data == nullptr) {
+ allocate(2);
+ }
+
+ if (element == nullptr) {
+ logger_error("Element not defined");
+ return;
+ }
+
+ size_t newNumElements = numElements + 1;
+ if (newNumElements > capacity) {
+ if (!grow(2 * capacity)) {
+ logger_error("Trying to add to list but unable to grow the array");
+ return;
+ }
+ }
+
+ memcpy(&data[numElements], element, sizeof(T));
+ numElements = newNumElements;
+}
+
+template <typename T>
+void List<T>::add(T& element) {
+ if (data == nullptr) {
+ allocate(2);
+ }
+
+ size_t newNumElements = numElements + 1;
+ if (newNumElements > capacity) {
+ if (!grow(2 * capacity)) {
+ logger_error("Trying to add to list but unable to grow the array");
+ return;
+ }
+ }
+
+ memcpy(&data[numElements], &element, sizeof(T));
+ numElements = newNumElements;
+}
+
+template <typename T>
+void List<T>::add(T&& element) {
+ if (data == nullptr) {
+ allocate(2);
+ }
+
+ size_t newNumElements = numElements + 1;
+ if (newNumElements > capacity) {
+ if (!grow(2 * capacity)) {
+ logger_error("Trying to add to list but unable to grow the array");
+ return;
+ }
+ }
+
+ memcpy(&data[numElements], &element, sizeof(T));
+ numElements = newNumElements;
+}
+
+template <typename T>
+void List<T>::remove(size_t idx) {
+ if (idx >= numElements) {
+ logger_error("Index is outside of the list: %d >= %d", idx, numElements);
+ return;
+ }
+
+ for (; idx < numElements - 1; idx++) {
+ data[idx] = data[idx + 1];
+ }
+
+ numElements--;
+}
+
+template <typename T>
+void List<T>::deallocate() {
+ if (data != nullptr) {
+ free(data);
+ data = nullptr;
+ }
+
+ capacity = 0;
+ numElements = 0;
+}
+
+template <typename T>
+void List<T>::clear() {
+ numElements = 0;
+}
+
+template <typename T>
+T* List<T>::getValue(int idx) const {
+ return &data[idx];
+}
+
+template <typename T>
+T& List<T>::operator[](int idx) const {
+ return data[idx];
+}
+
+template <typename T>
+void List<T>::binarySort(int (*f)(T *first, T* second)) {
+ if (data == nullptr) {
+ return;
+ }
+
+ for (size_t idx = 0; idx < numElements - 1; idx++) {
+ int minIdx = idx;
+ T firstValue = data[idx];
+
+ for (int innerIdx = idx + 1; innerIdx < numElements; innerIdx++) {\
+ T secondValue = data[innerIdx];
+ if (f(&firstValue, &secondValue) > 0) {
+ minIdx= innerIdx;
+ }
+ }
+
+ T temp = data[minIdx];
+ memmove(&data[minIdx], &data[idx], sizeof(T));
+ memmove(&data[idx], &temp, sizeof(T));
+ }
+}
diff --git a/themes/Logger.cpp b/themes/Logger.cpp
new file mode 100644
index 0000000..1068d88
--- /dev/null
+++ b/themes/Logger.cpp
@@ -0,0 +1,123 @@
+#include "Logger.h"
+#include <chrono>
+#include <cstdarg>
+#include <cstdio>
+
+namespace Logger {
+ LogLevel gLogLevel = LogLevel_Debug;
+ FILE* gFilePointer = NULL;
+
+ void initialize(LoggerOptions options) {
+ setLevel(options.level);
+ if (options.logToFile) {
+#ifdef WIN32
+ fopen_s(&gFilePointer, options.filePath, "a");
+#else
+ gFilePointer = fopen(options.filePath, "a");
+#endif
+ }
+ }
+
+ void setLevel(LogLevel level) {
+ gLogLevel = level;
+ }
+
+ LogLevel getLevel() {
+ return gLogLevel;
+ }
+
+ void printHeader(const char* levelStr, const char* fileName, int lineNumber) {
+ time_t t = time(0);
+ tm now;
+#ifdef WIN32
+ localtime_s(&now, &t);
+#else
+ now = *localtime(&t);
+#endif
+
+ printf("%s:%d [%d-%d-%d %d:%d:%d] %s: ", fileName, lineNumber, (now.tm_year + 1900), (now.tm_mon + 1), now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec, levelStr);
+ if (gFilePointer != NULL) {
+ fprintf(gFilePointer, "[%d-%d-%d %d:%d:%d] %s: ", (now.tm_year + 1900), (now.tm_mon + 1), now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec, levelStr);
+ }
+ }
+
+ void logInternal(const char* file, int lineNumber,LogLevel level, const char* format, va_list args) {
+ if (level < gLogLevel) {
+ return;
+ }
+
+
+ const char* levelStr;
+ switch (level) {
+ case LogLevel_Debug:
+ levelStr = "Debug";
+ break;
+ case LogLevel_Info:
+ levelStr = "Info";
+ break;
+ case LogLevel_Warn:
+ levelStr = "Warning";
+ break;
+ case LogLevel_Error:
+ levelStr = "Error";
+ break;
+ default:
+ levelStr = "Unknown";
+ break;
+ }
+
+ if (gFilePointer != NULL) {
+ va_list fileArgs;
+ va_copy(fileArgs, args);
+ vfprintf(gFilePointer, format, fileArgs);
+ fprintf(gFilePointer, "\n");
+ }
+
+ printHeader(levelStr, file, lineNumber);
+
+ vprintf(format, args);
+ printf("\n");
+ }
+
+ void doLog(const char* file, int lineNumber,LogLevel level, const char* format, ...) {
+ va_list args;
+ va_start(args, format);
+ logInternal(file, lineNumber, level, format, args);
+ va_end(args);
+ }
+
+ void doDebug(const char* file, int lineNumber,const char* format, ...) {
+ va_list args;
+ va_start(args, format);
+ logInternal(file, lineNumber, LogLevel_Debug, format, args);
+ va_end(args);
+ }
+
+ void doInfo(const char* file, int lineNumber,const char* format, ...) {
+ va_list args;
+ va_start(args, format);
+ logInternal(file, lineNumber, LogLevel_Info, format, args);
+ va_end(args);
+ }
+
+ void doWarning(const char* file, int lineNumber,const char* format, ...) {
+ va_list args;
+ va_start(args, format);
+ logInternal(file, lineNumber, LogLevel_Warn, format, args);
+ va_end(args);
+ }
+
+ void doError(const char* file, int lineNumber,const char* format, ...) {
+ va_list args;
+ va_start(args, format);
+ logInternal(file, lineNumber, LogLevel_Error, format, args);
+ va_end(args);
+ }
+
+ void free() {
+ if (gFilePointer) {
+ fclose(gFilePointer);
+ gFilePointer = NULL;
+ }
+ }
+}
diff --git a/themes/Logger.h b/themes/Logger.h
new file mode 100644
index 0000000..7596b6f
--- /dev/null
+++ b/themes/Logger.h
@@ -0,0 +1,43 @@
+#ifndef LOGGER_H
+#define LOGGER_H
+
+#include <cstring>
+
+enum LogLevel {
+ LogLevel_Debug = 0,
+ LogLevel_Info = 1,
+ LogLevel_Warn = 2,
+ LogLevel_Error = 3
+};
+
+struct LoggerOptions {
+ LogLevel level = LogLevel_Debug;
+ bool logToFile = false;
+ const char* filePath = "debug.log";
+};
+
+namespace Logger {
+ void initialize(LoggerOptions options);
+ void setLevel(LogLevel level);
+ LogLevel getLevel();
+ void doLog(const char* file, int lineNumber, LogLevel level, const char* format, ...);
+ void doDebug(const char* file, int lineNumber, const char* format, ...);
+ void doInfo(const char* file, int lineNumber, const char* format, ...);
+ void doWarning(const char* file, int lineNumber, const char* format, ...);
+ void doError(const char* file, int lineNumber, const char* format, ...);
+ void free();
+};
+
+#if WIN32
+#define __FILENAME__ (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__)
+#else
+#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
+#endif
+
+#define logger_log(level, format, ...) Logger::doLog(__FILENAME__, __LINE__, level, format, ## __VA_ARGS__)
+#define logger_debug(format, ...) Logger::doDebug(__FILENAME__, __LINE__, format, ## __VA_ARGS__)
+#define logger_info(format, ...) Logger::doInfo(__FILENAME__, __LINE__, format, ## __VA_ARGS__)
+#define logger_warning(format, ...) Logger::doWarning(__FILENAME__, __LINE__, format, ## __VA_ARGS__)
+#define logger_error(format, ...) Logger::doError(__FILENAME__, __LINE__, format, ## __VA_ARGS__)
+
+#endif
diff --git a/themes/Renderer2d.cpp b/themes/Renderer2d.cpp
index a58ad86..d81b97b 100644
--- a/themes/Renderer2d.cpp
+++ b/themes/Renderer2d.cpp
@@ -9,11 +9,12 @@
const char* renderer2dVertexShader =
"attribute vec2 position; \n"
"attribute vec4 color; \n"
+"attribute mat4 vMatrix; \n"
"uniform mat4 projection; \n"
"uniform mat4 model; \n"
"varying lowp vec4 VertexColor; \n"
"void main() { \n"
-" vec4 fragmentPosition = projection * model * vec4(position.x, position.y, 0, 1); \n"
+" vec4 fragmentPosition = projection * model * vMatrix * vec4(position.x, position.y, 0, 1); \n"
" gl_Position = fragmentPosition; \n"
" VertexColor = color; \n"
"}";
@@ -44,6 +45,7 @@ void Renderer2d::load(WebglContext* inContext) {
useShader(shader);
attributes.position = getShaderAttribute(shader, "position");
attributes.color = getShaderAttribute(shader, "color");
+ attributes.vMatrix = getShaderAttribute(shader, "vMatrix");
uniforms.projection = getShaderUniform(shader, "projection");
uniforms.model = getShaderUniform(shader, "model");
projection = Mat4x4().getOrthographicMatrix(0, context->width, 0, context->height);
@@ -90,6 +92,11 @@ void Renderer2dShape::load(Renderer2dVertex* inVertices, uint32 inNumVertices, R
glEnableVertexAttribArray(renderer->attributes.color);
glVertexAttribPointer(renderer->attributes.color, 4, GL_FLOAT, GL_FALSE, sizeof(Renderer2dVertex), (GLvoid *)offsetof(Renderer2dVertex, color));
+ for (int32 idx = 0; idx < 4; idx++) {
+ glEnableVertexAttribArray(renderer->attributes.vMatrix + idx);
+ glVertexAttribPointer(renderer->attributes.vMatrix + idx, 4, GL_FLOAT, GL_FALSE, sizeof(Renderer2dVertex), (GLvoid *)(offsetof(Renderer2dVertex, vMatrix) + (idx * 16)));
+ }
+
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
diff --git a/themes/Renderer2d.h b/themes/Renderer2d.h
index cbdf4d4..8630169 100644
--- a/themes/Renderer2d.h
+++ b/themes/Renderer2d.h
@@ -16,6 +16,7 @@ struct Renderer2d {
struct {
int32 position;
int32 color;
+ int32 vMatrix;
} attributes;
struct {
@@ -31,6 +32,7 @@ struct Renderer2d {
struct Renderer2dVertex {
Vector2 position;
Vector4 color;
+ Mat4x4 vMatrix;
};
struct Renderer2dShape {
diff --git a/themes/Snowflake.cpp b/themes/Snowflake.cpp
index 54654f0..3c9afa4 100644
--- a/themes/Snowflake.cpp
+++ b/themes/Snowflake.cpp
@@ -1,26 +1,146 @@
#include "Snowflake.h"
+#include "Renderer2d.h"
+#include "mathlib.h"
+#include "List.h"
+
+const Vector4 snowColor = Vector4(1.0, 0.98, 0.98, 1);
+
+inline void generateSnowflakeShape(List<Renderer2dVertex>* vertices, int32 numArms, float32 radius) {
+ float32 dx = ((2 * PI) / numArms) / 3.0;
+ for (int32 centerIdx = 0; centerIdx < (3 * numArms); centerIdx+=3) {
+ float32 degreeStart = dx * centerIdx;
+ float32 degreeEnd = dx * (centerIdx + 1);
+
+ float32 cosStart = cosf(degreeStart);
+ float32 cosEnd = cosf(degreeEnd);
+
+ float32 sinStart = sinf(degreeStart);
+ float32 sinEnd = sinf(degreeEnd);
+
+ Vector2 leftEnd = Vector2(radius * cosStart, radius * sinStart);
+ Vector2 rightEnd = Vector2(radius * cosEnd, radius * sinEnd);
+ Vector2 diff = (rightEnd - leftEnd) / 2.0;
+ Vector2 leftStart = Vector2(-diff.x, -diff.y);
+ Vector2 rightStart = diff;
+
+ vertices->add({ leftStart, snowColor, Mat4x4() });
+ vertices->add({ leftEnd, snowColor, Mat4x4() });
+ vertices->add({ rightEnd, snowColor, Mat4x4() });
+ vertices->add({ rightEnd, snowColor, Mat4x4() });
+ vertices->add({ rightStart, snowColor, Mat4x4() });
+ vertices->add({ leftStart, snowColor, Mat4x4() });
+ }
+}
+
+inline void initFlake(SnowflakeParticleRenderer* renderer, SnowflakeUpdateData* ud) {
+ ud->vtxIdx = renderer->vertices.numElements;
+ generateSnowflakeShape(&renderer->vertices, 6, randomFloatBetween(8.f, 24.f);
+ ud->numVertices = renderer->vertices.numElements - ud->vtxIdx;
+}
+
+inline void spawnFlake(SnowflakeParticleRenderer* renderer, SnowflakeUpdateData* ud) {
+ ud->velocity = Vector2(randomFloatBetween(-10, 10), randomFloatBetween(-100, -85));
+ ud->position = Vector2(randomFloatBetween(0, renderer->xMax), randomFloatBetween(renderer->yMax, renderer->yMax + 256));
+}
+
+inline void findAndSpawnNextFlake(SnowflakeParticleRenderer* renderer) {
+ do {
+ renderer->endIndex++;
+
+ if (renderer->endIndex >= renderer->numSnowflakes)
+ renderer->endIndex = 0;
+ } while (renderer->updateData[renderer->endIndex].onGround);
+
+ spawnFlake(renderer, &renderer->updateData[renderer->endIndex]);
+}
void SnowflakeParticleRenderer::load(SnowflakeLoadParameters params, Renderer2d* renderer) {
- updateData = new SnowflakeUpdateData[maxSnowflakes];
- renderData = new SnowflakeRenderData[maxSnowflakes];
- activeIndex = 0;
+ startIndex = 0;
+ spawnIntervalSeconds = params.spawnIntervalSeconds;
+ endIndex = params.initialSnowflakeCount;
+ numSnowflakes = params.maxSnowflakes;
+
+ updateData = new SnowflakeUpdateData[params.maxSnowflakes];
- for (int32 s = 0; s < maxSnowflakes; s++) {
+ xMax = static_cast<float32>(renderer->context->width);
+ yMax = static_cast<float32>(renderer->context->height);
+
+ // Initialize each snow flake with its shape
+ for (int32 s = 0; s < numSnowflakes; s++) {
auto ud = &updateData[s];
- auto rd = &renderData[s];
+ initFlake(this, ud);
+
+ if (s < endIndex) {
+ spawnFlake(this, ud);
+ }
+ }
+
+ useShader(renderer->shader);
+
+ glGenVertexArrays(1, &vao);
+ glBindVertexArray(vao);
+
+ glGenBuffers(1, &vbo);
+ glBindBuffer(GL_ARRAY_BUFFER, vbo);
+ glBufferData(GL_ARRAY_BUFFER, vertices.numElements * sizeof(Renderer2dVertex), &vertices.data[0], GL_DYNAMIC_DRAW);
+
+ glEnableVertexAttribArray(renderer->attributes.position);
+ glVertexAttribPointer(renderer->attributes.position, 2, GL_FLOAT, GL_FALSE, sizeof(Renderer2dVertex), (GLvoid *)0);
+
+ glEnableVertexAttribArray(renderer->attributes.color);
+ glVertexAttribPointer(renderer->attributes.color, 4, GL_FLOAT, GL_FALSE, sizeof(Renderer2dVertex), (GLvoid *)offsetof(Renderer2dVertex, color));
- ud->velocity = params->flakeV0;
+ for (int32 idx = 0; idx < 4; idx++) {
+ int32 offset = (4 * sizeof(float32)) * idx;
+ glEnableVertexAttribArray(renderer->attributes.vMatrix + idx);
+ glVertexAttribPointer(renderer->attributes.vMatrix + idx,
+ 4,
+ GL_FLOAT,
+ GL_FALSE,
+ sizeof(Renderer2dVertex),
+ (GLvoid *)(offsetof(Renderer2dVertex, vMatrix) + offset));
+ //glVertexAttribDivisor(renderer->attributes.vMatrix + idx, 1);
}
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindVertexArray(0);
}
void SnowflakeParticleRenderer::update(float32 dtSeconds) {
-
+ timeUntilNextSpawnSeconds -= dtSeconds;
+ if (timeUntilNextSpawnSeconds < 0) {
+ timeUntilNextSpawnSeconds = spawnIntervalSeconds;
+ findAndSpawnNextFlake(this);
+ }
+
+ for (int32 s = startIndex; s < endIndex; s++) {
+ SnowflakeUpdateData* ud = &updateData[s];
+ ud->position += ud->velocity * dtSeconds;
+
+ Mat4x4 m = Mat4x4().translateByVec2(ud->position);
+ for (int32 v = ud->vtxIdx; v < (ud->vtxIdx + ud->numVertices); v++) {
+ vertices.data[v].vMatrix = m;
+ }
+ }
}
void SnowflakeParticleRenderer::render(Renderer2d* renderer) {
+ auto startVertex = &updateData[startIndex];
+ auto endVertex = &updateData[endIndex];
+ int32 numVertices = (endVertex->vtxIdx + endVertex->numVertices) - startVertex->vtxIdx;
+ setShaderMat4(renderer->uniforms.model, model);
+ glBindBuffer(GL_ARRAY_BUFFER, vbo);
+ glBufferSubData(GL_ARRAY_BUFFER, 0, numVertices * sizeof(Renderer2dVertex), &vertices.data[startVertex->vtxIdx]);
+
+ glBindVertexArray(vao);
+ glDrawArrays(GL_TRIANGLES, 0, numVertices);
+ glBindVertexArray(0);
}
void SnowflakeParticleRenderer::unload() {
-
+ glDeleteVertexArrays(1, &vao);
+ glDeleteBuffers(1, &vbo);
+ vertices.deallocate();
+ delete [] updateData;
}
diff --git a/themes/Snowflake.h b/themes/Snowflake.h
index 09b05db..08f1c0c 100644
--- a/themes/Snowflake.h
+++ b/themes/Snowflake.h
@@ -1,36 +1,50 @@
#include "types.h"
#include "mathlib.h"
+#include "List.h"
struct Renderer2d;
-struct SnowflakeUpdateData;
-struct SnowflakeRenderData;
+struct Renderer2dVertex;
struct SnowflakeLoadParameters {
- int32 maxSnowflakes = 10000;
+ int32 maxSnowflakes = 1000;
+ int32 initialSnowflakeCount = 100;
float32 rateOfSnowfall = 0.1f;
- Vector3 flakeV0 = { 0, 1, 0 };
+ Vector2 flakeV0 = { 0, 1 };
float32 flakeSize = 5.f;
float32 flakeSizeDeviation = 1.f;
Vector4 snowColor = { 0.8, 0.8, 0.8, 1.0 };
+ float32 spawnIntervalSeconds = 0.3;
+};
+
+struct SnowflakeUpdateData {
+ Vector2 v0;
+ Vector2 velocity;
+ Vector2 position;
+ float32 rotation;
+ bool onGround = false;
+
+ int32 vtxIdx = 0;
+ int32 numVertices = 0;
};
struct SnowflakeParticleRenderer {
- int32 activeIndex = 0;
+ float32 xMax = 0;
+ float32 yMax = 0;
+ float32 spawnIntervalSeconds = 0.3;
+ int32 startIndex = 0;
+ int32 endIndex = 0;
+ int32 numSnowflakes = 0;
Vector3 windSpeed = { 0, 0, 0 };
SnowflakeUpdateData* updateData;
- SnowflakeRenderData* renderData;
+ float32 timeUntilNextSpawnSeconds = 0;;
+
+ uint32 vao;
+ uint32 vbo;
+ Mat4x4 model;
+ List<Renderer2dVertex> vertices;
- void load(SnowflakeLoadParameters params, Renderer2d* renderer);o
+ void load(SnowflakeLoadParameters params, Renderer2d* renderer);
void update(float32 dtSeconds);
void render(Renderer2d* renderer);
void unload();
};
-
-struct SnowflakeUpdateData {
- Vector3 velocity;
- Vector3 position;
-};
-
-struct SnowflakeRenderData {
-
-};
diff --git a/themes/TreeShape.cpp b/themes/TreeShape.cpp
index 797ceca..08170ea 100644
--- a/themes/TreeShape.cpp
+++ b/themes/TreeShape.cpp
@@ -59,6 +59,12 @@ TreeShapeLoadResult TreeShape::load(Renderer2d* renderer) {
glEnableVertexAttribArray(renderer->attributes.color);
glVertexAttribPointer(renderer->attributes.color, 4, GL_FLOAT, GL_FALSE, sizeof(Renderer2dVertex), (GLvoid *)offsetof(Renderer2dVertex, color));
+ for (int32 idx = 0; idx < 4; idx++) {
+ glEnableVertexAttribArray(renderer->attributes.vMatrix + idx);
+ glVertexAttribPointer(renderer->attributes.vMatrix + idx, 4, GL_FLOAT, GL_FALSE, sizeof(Renderer2dVertex), (GLvoid *)(offsetof(Renderer2dVertex, vMatrix) + (idx * 16)));
+ glVertexAttribDivisor(renderer->attributes.vMatrix + idx, 1);
+ }
+
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
diff --git a/themes/dist/output.js b/themes/dist/output.js
index 9a83997..9eb0f70 100644
--- a/themes/dist/output.js
+++ b/themes/dist/output.js
@@ -1835,6 +1835,82 @@ var ASM_CONSTS = {
return demangleAll(js);
}
+ function _tzset_impl() {
+ var currentYear = new Date().getFullYear();
+ var winter = new Date(currentYear, 0, 1);
+ var summer = new Date(currentYear, 6, 1);
+ var winterOffset = winter.getTimezoneOffset();
+ var summerOffset = summer.getTimezoneOffset();
+
+ // Local standard timezone offset. Local standard time is not adjusted for daylight savings.
+ // This code uses the fact that getTimezoneOffset returns a greater value during Standard Time versus Daylight Saving Time (DST).
+ // Thus it determines the expected output during Standard Time, and it compares whether the output of the given date the same (Standard) or less (DST).
+ var stdTimezoneOffset = Math.max(winterOffset, summerOffset);
+
+ // timezone is specified as seconds west of UTC ("The external variable
+ // `timezone` shall be set to the difference, in seconds, between
+ // Coordinated Universal Time (UTC) and local standard time."), the same
+ // as returned by stdTimezoneOffset.
+ // See http://pubs.opengroup.org/onlinepubs/009695399/functions/tzset.html
+ HEAP32[((__get_timezone())>>2)] = stdTimezoneOffset * 60;
+
+ HEAP32[((__get_daylight())>>2)] = Number(winterOffset != summerOffset);
+
+ function extractZone(date) {
+ var match = date.toTimeString().match(/\(([A-Za-z ]+)\)$/);
+ return match ? match[1] : "GMT";
+ };
+ var winterName = extractZone(winter);
+ var summerName = extractZone(summer);
+ var winterNamePtr = allocateUTF8(winterName);
+ var summerNamePtr = allocateUTF8(summerName);
+ if (summerOffset < winterOffset) {
+ // Northern hemisphere
+ HEAP32[((__get_tzname())>>2)] = winterNamePtr;
+ HEAP32[(((__get_tzname())+(4))>>2)] = summerNamePtr;
+ } else {
+ HEAP32[((__get_tzname())>>2)] = summerNamePtr;
+ HEAP32[(((__get_tzname())+(4))>>2)] = winterNamePtr;
+ }
+ }
+ function _tzset() {
+ // TODO: Use (malleable) environment variables instead of system settings.
+ if (_tzset.called) return;
+ _tzset.called = true;
+ _tzset_impl();
+ }
+ function _localtime_r(time, tmPtr) {
+ _tzset();
+ var date = new Date(HEAP32[((time)>>2)]*1000);
+ HEAP32[((tmPtr)>>2)] = date.getSeconds();
+ HEAP32[(((tmPtr)+(4))>>2)] = date.getMinutes();
+ HEAP32[(((tmPtr)+(8))>>2)] = date.getHours();
+ HEAP32[(((tmPtr)+(12))>>2)] = date.getDate();
+ HEAP32[(((tmPtr)+(16))>>2)] = date.getMonth();
+ HEAP32[(((tmPtr)+(20))>>2)] = date.getFullYear()-1900;
+ HEAP32[(((tmPtr)+(24))>>2)] = date.getDay();
+
+ var start = new Date(date.getFullYear(), 0, 1);
+ var yday = ((date.getTime() - start.getTime()) / (1000 * 60 * 60 * 24))|0;
+ HEAP32[(((tmPtr)+(28))>>2)] = yday;
+ HEAP32[(((tmPtr)+(36))>>2)] = -(date.getTimezoneOffset() * 60);
+
+ // Attention: DST is in December in South, and some regions don't have DST at all.
+ var summerOffset = new Date(date.getFullYear(), 6, 1).getTimezoneOffset();
+ var winterOffset = start.getTimezoneOffset();
+ var dst = (summerOffset != winterOffset && date.getTimezoneOffset() == Math.min(winterOffset, summerOffset))|0;
+ HEAP32[(((tmPtr)+(32))>>2)] = dst;
+
+ var zonePtr = HEAP32[(((__get_tzname())+(dst ? 4 : 0))>>2)];
+ HEAP32[(((tmPtr)+(40))>>2)] = zonePtr;
+
+ return tmPtr;
+ }
+ function ___localtime_r(a0,a1
+ ) {
+ return _localtime_r(a0,a1);
+ }
+
function _abort() {
abort('native code called abort()');
}
@@ -3017,6 +3093,10 @@ var ASM_CONSTS = {
GLctx.currentProgram = program;
}
+ function _glVertexAttribDivisor(index, divisor) {
+ GLctx['vertexAttribDivisor'](index, divisor);
+ }
+
function _glVertexAttribPointer(index, size, type, normalized, stride, ptr) {
var cb = GL.currentContext.clientBuffers[index];
if (!GLctx.currentArrayBufferBinding) {
@@ -3083,6 +3163,7 @@ function intArrayToString(array) {
var asmLibraryArg = {
+ "__localtime_r": ___localtime_r,
"abort": _abort,
"emscripten_get_element_css_size": _emscripten_get_element_css_size,
"emscripten_memcpy_big": _emscripten_memcpy_big,
@@ -3130,6 +3211,7 @@ var asmLibraryArg = {
"glShaderSource": _glShaderSource,
"glUniformMatrix4fv": _glUniformMatrix4fv,
"glUseProgram": _glUseProgram,
+ "glVertexAttribDivisor": _glVertexAttribDivisor,
"glVertexAttribPointer": _glVertexAttribPointer,
"setTempRet0": _setTempRet0,
"time": _time
@@ -3142,12 +3224,27 @@ var ___wasm_call_ctors = Module["___wasm_call_ctors"] = createExportWrapper("__w
var _main = Module["_main"] = createExportWrapper("main");
/** @type {function(...*):?} */
+var _free = Module["_free"] = createExportWrapper("free");
+
+/** @type {function(...*):?} */
+var _malloc = Module["_malloc"] = createExportWrapper("malloc");
+
+/** @type {function(...*):?} */
var ___errno_location = Module["___errno_location"] = createExportWrapper("__errno_location");
/** @type {function(...*):?} */
var _fflush = Module["_fflush"] = createExportWrapper("fflush");
/** @type {function(...*):?} */
+var __get_tzname = Module["__get_tzname"] = createExportWrapper("_get_tzname");
+
+/** @type {function(...*):?} */
+var __get_daylight = Module["__get_daylight"] = createExportWrapper("_get_daylight");
+
+/** @type {function(...*):?} */
+var __get_timezone = Module["__get_timezone"] = createExportWrapper("_get_timezone");
+
+/** @type {function(...*):?} */
var stackSave = Module["stackSave"] = createExportWrapper("stackSave");
/** @type {function(...*):?} */
@@ -3172,12 +3269,6 @@ var _emscripten_stack_get_end = Module["_emscripten_stack_get_end"] = function()
};
/** @type {function(...*):?} */
-var _malloc = Module["_malloc"] = createExportWrapper("malloc");
-
-/** @type {function(...*):?} */
-var _free = Module["_free"] = createExportWrapper("free");
-
-/** @type {function(...*):?} */
var dynCall_jiji = Module["dynCall_jiji"] = createExportWrapper("dynCall_jiji");
diff --git a/themes/dist/output.wasm b/themes/dist/output.wasm
index 495ea67..427f834 100755
--- a/themes/dist/output.wasm
+++ b/themes/dist/output.wasm
Binary files differ
diff --git a/themes/main.cpp b/themes/main.cpp
index 8ca7fc4..8f908a3 100644
--- a/themes/main.cpp
+++ b/themes/main.cpp
@@ -5,6 +5,7 @@
#include "types.h"
#include "TreeShape.h"
#include "LeafParticleRender.h"
+#include "Snowflake.h"
enum Theme {
Default = 0,
@@ -23,6 +24,8 @@ struct AutumnTheme {
};
struct WinterTheme {
+ SnowflakeParticleRenderer spr;
+
void load(Renderer2d* renderer);
void update(float32 dtSeconds);
void render(Renderer2d* renderer);
@@ -167,13 +170,19 @@ void AutumnTheme::unload() {
// -- Winter theme
void WinterTheme::load(Renderer2d* renderer) {
renderer->clearColor = Vector4(200, 229, 239, 255).toNormalizedColor();
+ SnowflakeLoadParameters lp;
+ lp.spawnIntervalSeconds = 0.05;
+ spr.load(lp, renderer);
}
void WinterTheme::update(float32 dtSeconds) {
+ spr.update(dtSeconds);
}
void WinterTheme::render(Renderer2d* renderer) {
+ spr.render(renderer);
}
void WinterTheme::unload() {
+ spr.unload();
}