summaryrefslogtreecommitdiff
path: root/themes/src
diff options
context:
space:
mode:
authormattkae <mattkae@protonmail.com>2022-12-23 12:47:10 -0500
committermattkae <mattkae@protonmail.com>2022-12-23 12:47:10 -0500
commit7228b2e1a2d0a8399facce3493d71a3569d250d5 (patch)
tree8eb5e4b686bf68fa12fcbb270ef88dd29aa1d704 /themes/src
parentf63d0af456f76d713e56ca17be114fba0af22f6c (diff)
Improved the makefile considerably
Diffstat (limited to 'themes/src')
-rw-r--r--themes/src/LeafParticleRender.cpp166
-rw-r--r--themes/src/LeafParticleRender.h58
-rw-r--r--themes/src/Logger.cpp123
-rw-r--r--themes/src/Logger.h43
-rw-r--r--themes/src/MainLoop.cpp31
-rw-r--r--themes/src/MainLoop.h29
-rw-r--r--themes/src/Renderer2d.cpp132
-rw-r--r--themes/src/Renderer2d.h54
-rw-r--r--themes/src/Renderer3d.cpp263
-rw-r--r--themes/src/Renderer3d.h56
-rw-r--r--themes/src/Shader.cpp61
-rw-r--r--themes/src/Shader.h64
-rw-r--r--themes/src/Snowflake.cpp135
-rw-r--r--themes/src/Snowflake.h47
-rw-r--r--themes/src/SummerTheme.cpp67
-rw-r--r--themes/src/SummerTheme.h23
-rw-r--r--themes/src/TreeShape.cpp214
-rw-r--r--themes/src/TreeShape.h74
-rw-r--r--themes/src/WebglContext.cpp46
-rw-r--r--themes/src/WebglContext.h18
-rw-r--r--themes/src/list.h245
-rw-r--r--themes/src/main.cpp445
-rw-r--r--themes/src/mathlib.cpp779
-rw-r--r--themes/src/mathlib.h181
-rw-r--r--themes/src/types.h16
25 files changed, 3370 insertions, 0 deletions
diff --git a/themes/src/LeafParticleRender.cpp b/themes/src/LeafParticleRender.cpp
new file mode 100644
index 0000000..0c6fbca
--- /dev/null
+++ b/themes/src/LeafParticleRender.cpp
@@ -0,0 +1,166 @@
+#include "LeafParticleRender.h"
+#include "Renderer2d.h"
+#include "mathlib.h"
+#include "TreeShape.h"
+#include "types.h"
+#include <math.h>
+
+const i32 verticesPerLeaf = 6;
+const f32 leafRadius = 3.f;
+const i32 fallChanceMax = 100;
+
+inline void updateLeaf(Vertex2D* vertices, Vector2 position, Vector4 color, f32 scale) {
+ f32 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, 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) {
+ LeafParticleLoadData ld;
+ ld.numLeaves = 256;
+ numLeaves = ld.numLeaves;
+ numVertices = ld.numLeaves * verticesPerLeaf;
+
+ updateData = new LeafParticleUpdateData[numLeaves];
+ vertices = new Vertex2D[numVertices];
+
+ for (i32 leafIdx = 0; leafIdx < numLeaves; leafIdx++) {
+ i32 randomBranch = randomIntBetween(0, lr->numBranches);
+ i32 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(Vertex2D), &vertices[0], GL_DYNAMIC_DRAW);
+
+ glEnableVertexAttribArray(renderer->attributes.position);
+ glVertexAttribPointer(renderer->attributes.position, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)0);
+
+ glEnableVertexAttribArray(renderer->attributes.color);
+ glVertexAttribPointer(renderer->attributes.color, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)offsetof(Vertex2D, color));
+
+ for (i32 idx = 0; idx < 4; idx++) {
+ glEnableVertexAttribArray(renderer->attributes.vMatrix + idx);
+ glVertexAttribPointer(renderer->attributes.vMatrix + idx, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)(offsetof(Vertex2D, vMatrix) + (idx * 16)));
+ }
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindVertexArray(0);
+}
+
+void LeafParticleRender::update(f32 dtSeconds) {
+ elapsedTimeSeconds += dtSeconds;
+
+ // Every time the fallIntervalSeconds passes, we remove one leaf
+ // from the tree and send it barrelling towards the earth.
+ i32 fallRoll;
+ bool didGenerateFall = false;
+ if (elapsedTimeSeconds >= fallIntervalSeconds) {
+ fallRoll = randomIntBetween(0, fallChanceMax);
+ didGenerateFall = true;
+ elapsedTimeSeconds = 0;
+ }
+
+ for (i32 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 f32 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(Vertex2D), &vertices[0]);
+
+ glBindVertexArray(vao);
+ glDrawArrays(GL_TRIANGLES, 0, numVertices);
+ glBindVertexArray(0);
+}
+
+void LeafParticleRender::unload() {
+ glDeleteVertexArrays(1, &vao);
+ glDeleteBuffers(1, &vbo);
+ delete [] vertices;
+
+ elapsedTimeSeconds = 0;
+}
diff --git a/themes/src/LeafParticleRender.h b/themes/src/LeafParticleRender.h
new file mode 100644
index 0000000..713d9f6
--- /dev/null
+++ b/themes/src/LeafParticleRender.h
@@ -0,0 +1,58 @@
+#include "Renderer2d.h"
+#include "mathlib.h"
+#include "types.h"
+
+struct TreeShapeLoadResult;
+
+struct LeafParticleLoadData {
+ Vector2 initPosition;
+ Vector4 initColor;
+ int numLeaves = 48;
+};
+
+enum LeafParticleState {
+ OnTree,
+ Falling,
+ OnGround,
+ Remerging
+};
+
+struct LeafParticleUpdateData {
+ LeafParticleState state = LeafParticleState::Remerging;
+
+ Vertex2D* vertexToFollow = NULL;
+ Vector4 color = Vector4(1.f, 0.f, 0.f, 0.f);
+ f32 scale = 1.f;
+
+ f32 timeElapsedSeconds = 0.f;
+ i32 fallChance = -1;
+ Vector2 fallPosition;
+ f32 fallVerticalVelocity;
+ f32 fallHorizontalFrequency;
+
+ f32 resetTime = 0.f;
+
+ Vertex2D* vertexPtr = NULL;
+};
+
+struct LeafParticleRender {
+ f32 elapsedTimeSeconds = 0.5;
+ f32 fallIntervalSeconds = 1.f;
+
+ // Update data
+ i32 numLeaves = 0;
+
+ LeafParticleUpdateData* updateData = NULL;
+ Vertex2D* vertices = NULL;
+
+ // Render data
+ u32 vao;
+ u32 vbo;
+ u32 numVertices = 0;
+ Mat4x4 model;
+
+ void load(Renderer2d* renderer, TreeShapeLoadResult* lr);
+ void update(f32 dtSeconds);
+ void render(Renderer2d* renderer);
+ void unload();
+}; \ No newline at end of file
diff --git a/themes/src/Logger.cpp b/themes/src/Logger.cpp
new file mode 100644
index 0000000..1068d88
--- /dev/null
+++ b/themes/src/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/src/Logger.h b/themes/src/Logger.h
new file mode 100644
index 0000000..7596b6f
--- /dev/null
+++ b/themes/src/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/src/MainLoop.cpp b/themes/src/MainLoop.cpp
new file mode 100644
index 0000000..09aa643
--- /dev/null
+++ b/themes/src/MainLoop.cpp
@@ -0,0 +1,31 @@
+#include "MainLoop.h"
+#include <cstdio>
+#include <cstdlib>
+
+EM_BOOL loop(double time, void* loop) {
+ MainLoop* mainLoop = (MainLoop*) loop;
+ if (!mainLoop->isRunning) {
+ return false;
+ }
+
+ if (mainLoop->lastTime == 0) {
+ mainLoop->lastTime = time;
+ return true;
+ }
+
+ long deltaTime = time - mainLoop->lastTime;
+ mainLoop->lastTime = time;
+ mainLoop->elapsedTime += deltaTime;
+ mainLoop->numFrames++;
+ float deltaTimeSeconds = static_cast<float>(deltaTime) / 1000.f;
+
+ if (mainLoop->elapsedTime >= 1000.0) {
+ printf("FPS: %d\n", mainLoop->numFrames);
+
+ mainLoop->elapsedTime = 0.0;
+ mainLoop->numFrames = 0;
+ }
+
+ mainLoop->updateFunc(deltaTimeSeconds, NULL);
+ return true;
+} \ No newline at end of file
diff --git a/themes/src/MainLoop.h b/themes/src/MainLoop.h
new file mode 100644
index 0000000..2573bb8
--- /dev/null
+++ b/themes/src/MainLoop.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <emscripten.h>
+#include <emscripten/html5.h>
+#include <GLES2/gl2.h>
+#include <EGL/egl.h>
+
+EM_BOOL loop(double time, void* loop);
+
+struct MainLoop {
+ bool isRunning = false;
+ double lastTime = 0, elapsedTime = 0;
+ int numFrames = 0;
+ void (*updateFunc)(float dtSeconds, void *userData);
+
+ void run(void (*cb)(float dtSeconds, void *userData)) {
+ isRunning = true;
+ lastTime = 0;
+ elapsedTime = 0;
+ numFrames = 0;
+ updateFunc = cb;
+
+ emscripten_request_animation_frame_loop(loop, this);
+ }
+
+ void stop() {
+ isRunning = false;
+ }
+}; \ No newline at end of file
diff --git a/themes/src/Renderer2d.cpp b/themes/src/Renderer2d.cpp
new file mode 100644
index 0000000..c303e83
--- /dev/null
+++ b/themes/src/Renderer2d.cpp
@@ -0,0 +1,132 @@
+#include "Renderer2d.h"
+#include "Shader.h"
+#include "WebglContext.h"
+#include "mathlib.h"
+#include <cstdio>
+
+// Note: In the 'transform' attribute, the transform.x is the scale,
+// transform.y is the rotation, and transform.zw is the translation.
+const char* Vertex2DShader =
+"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 * vMatrix * vec4(position.x, position.y, 0, 1); \n"
+" gl_Position = fragmentPosition; \n"
+" VertexColor = color; \n"
+"}";
+
+const char* renderer2dFragmentShader =
+"varying lowp vec4 VertexColor; \n"
+"void main() { \n"
+" gl_FragColor = VertexColor; \n"
+"}";
+
+void Renderer2d::load(WebglContext* inContext) {
+ context = inContext;
+ printf("Compiling Renderer2d shader...\n");
+ shader = loadShader(Vertex2DShader, renderer2dFragmentShader);
+
+ 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);
+
+ printf("Renderer2d shader compiled.\n");
+}
+
+void Renderer2d::render() {
+ projection = Mat4x4().getOrthographicMatrix(0, context->width, 0, context->height);
+
+ glEnable(GL_DEPTH_TEST);
+ glDepthFunc(GL_LEQUAL);
+ glDepthMask(GL_TRUE);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glEnable(GL_BLEND);
+ glClearColor(clearColor.x, clearColor.y, clearColor.z, clearColor.w);
+ glClearDepth(1.0f);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+ useShader(shader);
+ setShaderMat4(uniforms.projection, projection);
+}
+
+void Renderer2d::unload() {
+ glClearColor(0.f, 0.f, 0.f, 0.f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glDeleteProgram(shader);
+}
+
+
+void Mesh2D::load(Vertex2D* inVertices, u32 inNumVertices, Renderer2d* renderer) {
+ ebo = 0;
+ numVertices = inNumVertices;
+ useShader(renderer->shader);
+
+ glGenVertexArrays(1, &vao);
+ glBindVertexArray(vao);
+
+ glGenBuffers(1, &vbo);
+ glBindBuffer(GL_ARRAY_BUFFER, vbo);
+ glBufferData(GL_ARRAY_BUFFER, inNumVertices * sizeof(Vertex2D), &inVertices[0], GL_STATIC_DRAW);
+
+ glEnableVertexAttribArray(renderer->attributes.position);
+ glVertexAttribPointer(renderer->attributes.position, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)0);
+
+ glEnableVertexAttribArray(renderer->attributes.color);
+ glVertexAttribPointer(renderer->attributes.color, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)offsetof(Vertex2D, color));
+
+ for (i32 idx = 0; idx < 4; idx++) {
+ glEnableVertexAttribArray(renderer->attributes.vMatrix + idx);
+ glVertexAttribPointer(renderer->attributes.vMatrix + idx, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)(offsetof(Vertex2D, vMatrix) + (idx * 16)));
+ }
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindVertexArray(0);
+}
+
+
+void Mesh2D::load(Vertex2D* vertices,
+ u32 numVertices,
+ u32* indices,
+ u32 inNumIndices,
+ Renderer2d* renderer) {
+ load(vertices, numVertices, renderer);
+ glBindVertexArray(vao);
+ glGenBuffers(1, &ebo);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, inNumIndices * sizeof(u32), &indices[0], GL_STATIC_DRAW);
+ numIndices = inNumIndices;
+ glBindVertexArray(0);
+}
+
+void Mesh2D::render(Renderer2d* renderer, GLenum drawType) {
+ setShaderMat4(renderer->uniforms.model, model);
+
+ if (ebo == 0) {
+ glBindVertexArray(vao);
+ glDrawArrays(drawType, 0, numVertices);
+ glBindVertexArray(0);
+ }
+ else {
+ glBindVertexArray(vao);
+ glDrawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_INT, 0);
+ glBindVertexArray(0);
+ }
+}
+
+void Mesh2D::unload() {
+ glDeleteVertexArrays(1, &vao);
+ glDeleteBuffers(1, &vbo);
+ if (ebo != 0) {
+ glDeleteBuffers(1, &ebo);
+ ebo = 0;
+ }
+ vao = 0;
+ vbo = 0;
+}
diff --git a/themes/src/Renderer2d.h b/themes/src/Renderer2d.h
new file mode 100644
index 0000000..909f088
--- /dev/null
+++ b/themes/src/Renderer2d.h
@@ -0,0 +1,54 @@
+#pragma once
+
+#include "WebglContext.h"
+#include "types.h"
+#include "Shader.h"
+#include "mathlib.h"
+
+struct WebglContext;
+
+struct Renderer2d {
+ WebglContext* context = NULL;
+ Mat4x4 projection;
+ u32 shader;
+ Vector4 clearColor;
+
+ struct {
+ i32 position;
+ i32 color;
+ i32 vMatrix;
+ } attributes;
+
+ struct {
+ i32 projection;
+ i32 model;
+ } uniforms;
+
+ void load(WebglContext* context);
+ void render();
+ void unload();
+};
+
+struct Vertex2D {
+ Vector2 position;
+ Vector4 color;
+ Mat4x4 vMatrix;
+};
+
+struct Mesh2D {
+ u32 vao;
+ u32 vbo;
+ u32 ebo = 0;
+ u32 numVertices = 0;
+ u32 numIndices = 0;
+ Mat4x4 model;
+
+ void load(Vertex2D* vertices, u32 numVertices, Renderer2d* renderer);
+ void load(Vertex2D* vertices,
+ u32 numVertices,
+ u32* indices,
+ u32 numIndices,
+ Renderer2d* renderer);
+ void render(Renderer2d* renderer, GLenum drawType = GL_TRIANGLES);
+ void unload();
+};
diff --git a/themes/src/Renderer3d.cpp b/themes/src/Renderer3d.cpp
new file mode 100644
index 0000000..5f9ce88
--- /dev/null
+++ b/themes/src/Renderer3d.cpp
@@ -0,0 +1,263 @@
+#include "Renderer3d.h"
+#include "Shader.h"
+#include "list.h"
+#include "mathlib.h"
+#include "WebglContext.h"
+#include "Logger.h"
+#include <cstdio>
+
+// Note: In the 'transform' attribute, the transform.x is the scale,
+// transform.y is the rotation, and transform.zw is the translatiob.
+const char* vertexShader =
+ "attribute vec4 position; \n"
+ "attribute vec4 color; \n"
+ "attribute vec4 normal; \n"
+ "uniform mat4 projection; \n"
+ "uniform mat4 view; \n"
+ "uniform mat4 model; \n"
+ "varying lowp vec4 VertexColor; \n"
+ "varying lowp vec4 VertexNormal; \n"
+ "void main() { \n"
+ " vec4 fragmentPosition = projection * view * model * position; \n"
+ " gl_Position = fragmentPosition; \n"
+ " VertexColor = color; \n"
+ " VertexNormal = normal; \n"
+ "}";
+
+const char* fragmentShader =
+ "varying lowp vec4 VertexColor; \n"
+ "varying lowp vec4 VertexNormal; \n"
+ "void main() { \n"
+ " const lowp vec3 lightDirection = vec3(0.0, 1.0, 0.0);\n"
+ " gl_FragColor = vec4(VertexColor.xyz * dot(VertexNormal.xyz, lightDirection), 1); \n"
+ "}";
+
+EM_BOOL onScreenSizeChanged_3D(int eventType, const EmscriptenUiEvent *uiEvent, void *userData) {
+ Renderer3D* renderer = (Renderer3D*)userData;
+
+ EMSCRIPTEN_RESULT result = emscripten_set_canvas_element_size( renderer->context->query, uiEvent->documentBodyClientWidth, uiEvent->documentBodyClientHeight);
+ if (result != EMSCRIPTEN_RESULT_SUCCESS) {
+ logger_error("Failed to resize element at query: %s\n", renderer->context->query);
+ }
+ //renderer->projection = Mat4x4().getOrthographicMatrix(0, renderer->context->width, 0, renderer->context->height);
+
+ return true;
+}
+
+void Renderer3D::load(WebglContext* inContext) {
+ context = inContext;
+ printf("Compiling Renderer2d shader...\n");
+ shader = loadShader(vertexShader, fragmentShader);
+
+ useShader(shader);
+ attributes.position = getShaderAttribute(shader, "position");
+ attributes.color = getShaderAttribute(shader, "color");
+ attributes.normal = getShaderAttribute(shader, "normal");
+ uniforms.projection = getShaderUniform(shader, "projection");
+ uniforms.view = getShaderUniform(shader, "view");
+ uniforms.model = getShaderUniform(shader, "model");
+ projection = Mat4x4().getPerspectiveProjection(0.1, 1000.f, 0.872f, static_cast<f32>(context->width) / static_cast<f32>(context->height));
+ view = Mat4x4().getLookAt({ 0, 25, 75 }, { 0, 15, 0 }, { 0, 1, 0 });
+
+ logger_info("Renderer2d shader compiled.\n");
+
+ emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, this, false, onScreenSizeChanged_3D);
+}
+
+void Renderer3D::render() {
+ glEnable(GL_DEPTH_TEST);
+ glDepthFunc(GL_LEQUAL);
+ glDepthMask(GL_TRUE);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glEnable(GL_BLEND);
+ glClearColor(clearColor.x, clearColor.y, clearColor.z, clearColor.w);
+ glClearDepth(1.0f);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+ useShader(shader);
+ setShaderMat4(uniforms.projection, projection);
+ setShaderMat4(uniforms.view, view);
+}
+
+void Renderer3D::unload() {
+ glClearColor(0.f, 0.f, 0.f, 0.f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glDeleteProgram(shader);
+}
+
+enum LineType {
+ LineType_None,
+ LineType_Comment,
+ LineType_mtl,
+ LineType_v,
+ LineType_f,
+ LineType_Unsupported
+};
+
+struct LineItem {
+ LineType type = LineType_None;
+
+ i32 idx = 0;
+
+ union {
+ f32 vertices[3];
+ i32 indices[3];
+ } v;
+};
+
+inline i32 readPastSpaces(i32 i, const char* content) {
+ while (content[i] == ' ') i++;
+ return i;
+}
+
+inline i32 readPastLine(i32 i, const char* content) {
+ while (content[i] != '\n' && content[i] != '\0') i++;
+ return i;
+}
+
+inline i32 readToken(i32 i, const char* content, char* output) {
+ i32 tidx = 0;
+ i = readPastSpaces(i, content);
+ while (content[i] != ' ' && content[i] != '\n' && content[i] != '\0') {
+ output[tidx] = content[i];
+ i++;
+ tidx++;
+ }
+ output[tidx] = '\0';
+ return i;
+}
+
+Mesh3d Mesh3d_fromObj(Renderer3D* renderer, const char* content, const i32 len) {
+ Mesh3d result;
+ result.vertices.allocate(2048);
+ result.indices.allocate(2048);
+
+ LineItem lt;
+ lt.type = LineType_None;
+ i32 lineNumber = 0;
+ i32 i = 0;
+ while (content[i] != '\0') {
+ i = readPastSpaces(i, content);
+ if (lt.type == LineType_None) {
+ lineNumber++;
+ char type[32];
+ i = readToken(i, content, type);
+
+ if (strncmp(type, "#", 1) == 0) {
+ lt.type = LineType_Comment;
+ }
+ else if (strncmp(type, "mtllib", 6) == 0) {
+ lt.type = LineType_mtl;
+ }
+ else if (strncmp(type, "v", 1) == 0) {
+ lt.type = LineType_v;
+ }
+ else if (strncmp(type, "f", 1) == 0) {
+ lt.type = LineType_f;
+ }
+ else {
+ i++;
+ //lt.type = LineType_Unsupported;
+ //logger_error("Unknown type %s, %d", type, lineNumber);
+ }
+ }
+ else {
+ char buffer[32];
+ switch (lt.type) {
+ case LineType_mtl:
+ i = readToken(i, content, buffer);
+ break;
+ case LineType_v: {
+ while (content[i] != '\n' && content[i] != '\0') {
+ i = readToken(i, content, buffer);
+ lt.v.vertices[lt.idx] = atof(buffer);
+ lt.idx++;
+ }
+
+ float fColor = randomFloatBetween(0.8, 1);
+ result.vertices.add({
+ Vector4(lt.v.vertices[0], lt.v.vertices[1], lt.v.vertices[2], 1.f),
+ Vector4(fColor, fColor, fColor, 1)
+ });
+ break;
+ }
+ case LineType_f: {
+ while (content[i] != '\n' && content[i] != '\0') {
+ i = readToken(i, content, buffer);
+ lt.v.indices[lt.idx] = atoi(buffer);
+ lt.idx++;
+ }
+
+ auto v1idx = lt.v.indices[0] - 1;
+ auto v2idx = lt.v.indices[1] - 1;
+ auto v3idx = lt.v.indices[2] - 1;
+
+ result.indices.add(v1idx);
+ result.indices.add(v2idx);
+ result.indices.add(v3idx);
+
+ auto& v1 = result.vertices[v1idx];
+ auto& v2 = result.vertices[v2idx];
+ auto& v3 = result.vertices[v3idx];
+ Vector3 normal = (v1.position - v2.position).cross(v1.position - v3.position).toVector3().normalize();
+ v1.normal = normal;
+ v2.normal = normal;
+ v3.normal = normal;
+ break;
+ }
+ default:
+ i = readPastLine(i, content);
+ break;
+ }
+
+ lt = LineItem();
+ }
+ }
+
+ printf("Completed Mesh3d loading.\n");
+ result.load(renderer);
+ return result;
+}
+
+void Mesh3d::load(Renderer3D* renderer) {
+ glGenVertexArrays(1, &vao);
+ glGenBuffers(1, &vbo);
+ glGenBuffers(1, &ebo);
+
+ glBindVertexArray(vao);
+
+ glBindBuffer(GL_ARRAY_BUFFER, vbo);
+ glBufferData(GL_ARRAY_BUFFER, vertices.numElements * sizeof(Vertex3d), &vertices.data[0], GL_STATIC_DRAW);
+
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.numElements * sizeof(GLuint), &indices.data[0], GL_STATIC_DRAW);
+
+ // Position
+ glEnableVertexAttribArray(renderer->attributes.position);
+ glVertexAttribPointer(renderer->attributes.position, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex3d), (GLvoid *)0);
+
+ // Color
+ glEnableVertexAttribArray(renderer->attributes.color);
+ glVertexAttribPointer(renderer->attributes.color, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex3d), (GLvoid *)offsetof(Vertex3d, color));
+
+ // Normal
+ glEnableVertexAttribArray(renderer->attributes.normal);
+ glVertexAttribPointer(renderer->attributes.normal, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex3d), (GLvoid *)offsetof(Vertex3d, normal));
+
+ glBindVertexArray(0);
+}
+
+void Mesh3d::unload() {
+ if (vao) glDeleteVertexArrays(1, &vao);
+ if (vbo) glDeleteBuffers(1, &vbo);
+ if (ebo) glDeleteBuffers(1, &ebo);
+ vertices.deallocate();
+ indices.deallocate();
+}
+
+void Mesh3d::render(Renderer3D* renderer) {
+ setShaderMat4(renderer->uniforms.model, model);
+
+ glBindVertexArray(vao);
+ glDrawElements(GL_TRIANGLES, indices.numElements, GL_UNSIGNED_INT, 0);
+ glBindVertexArray(0);
+}
diff --git a/themes/src/Renderer3d.h b/themes/src/Renderer3d.h
new file mode 100644
index 0000000..7e89c93
--- /dev/null
+++ b/themes/src/Renderer3d.h
@@ -0,0 +1,56 @@
+#ifndef RENDERER3D_H
+#define RENDERER3D_H
+#include "mathlib.h"
+#include "list.h"
+#include "types.h"
+#include <string>
+
+struct Renderer3D;
+
+struct Vertex3d {
+ Vector4 position;
+ Vector4 color;
+ Vector4 normal;
+};
+
+struct Mesh3d {
+ u32 vao;
+ u32 vbo;
+ u32 ebo;
+ matte::List<Vertex3d> vertices;
+ matte::List<u32> indices;
+ Mat4x4 model;
+
+ void load(Renderer3D* renderer);
+ void render(Renderer3D* renderer);
+ void unload();
+};
+
+struct WebglContext;
+struct Renderer3D {
+ WebglContext* context = NULL;
+ Mat4x4 projection;
+ Mat4x4 view;
+ u32 shader;
+ Vector4 clearColor;
+
+ struct {
+ i32 position;
+ i32 color;
+ i32 normal;
+ } attributes;
+
+ struct {
+ i32 projection;
+ i32 view;
+ i32 model;
+ } uniforms;
+
+ void load(WebglContext* context);
+ void render();
+ void unload();
+};
+
+Mesh3d Mesh3d_fromObj(Renderer3D* renderer, const char* content, const i32 len);
+
+#endif
diff --git a/themes/src/Shader.cpp b/themes/src/Shader.cpp
new file mode 100644
index 0000000..5f2b00e
--- /dev/null
+++ b/themes/src/Shader.cpp
@@ -0,0 +1,61 @@
+#include "Shader.h"
+#include <string>
+
+GLuint loadIndividualShader(GLenum shaderType, const GLchar* cCode) {
+ GLuint shader = glCreateShader(shaderType);
+ glShaderSource(shader, 1, &cCode, 0);
+ glCompileShader(shader);
+ GLint success;
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
+ if (!success) {
+ GLchar infoLog[512];
+ glGetShaderInfoLog(shader, 512, 0, infoLog);
+ printf("Failed to load shader: %s, Shader =%s\n", infoLog, cCode);
+ return 0;
+ }
+
+ return shader;
+}
+
+void attachShaders(Shader& retVal, const GLchar* vertexShader, const GLchar* fragmentShader) {
+ GLuint vertex = 0, fragment = 0, geometry = 0;
+ if (vertexShader) {
+ vertex = loadIndividualShader(GL_VERTEX_SHADER, vertexShader);
+ glAttachShader(retVal, vertex);
+ }
+
+ if (fragmentShader) {
+ fragment = loadIndividualShader(GL_FRAGMENT_SHADER, fragmentShader);
+ glAttachShader(retVal, fragment);
+ }
+
+ glLinkProgram(retVal);
+ GLint isLinked = 0;
+ glGetProgramiv(retVal, GL_LINK_STATUS, (int*)&isLinked);
+ if (isLinked == GL_FALSE) {
+ GLint maxLength = 0;
+ glGetProgramiv(retVal, GL_INFO_LOG_LENGTH, &maxLength);
+
+ // The maxLength includes the NULL character
+ GLchar* infoLog = new GLchar[maxLength];
+ glGetProgramInfoLog(retVal, maxLength, &maxLength, infoLog);
+ glDeleteProgram(retVal);
+ printf("Error. Could not initialize shader with vertex=%s, error=%s\n", vertexShader, infoLog);
+ delete []infoLog;
+ }
+
+ if (vertexShader)
+ glDeleteShader(vertex);
+ if (fragmentShader)
+ glDeleteShader(fragment);
+}
+
+Shader loadShader(const GLchar* vertexShader, const GLchar* fragmentShader) {
+ Shader retVal;
+ retVal = glCreateProgram();
+
+ attachShaders(retVal, vertexShader, fragmentShader);
+ useShader(retVal);
+
+ return retVal;
+}
diff --git a/themes/src/Shader.h b/themes/src/Shader.h
new file mode 100644
index 0000000..bc81764
--- /dev/null
+++ b/themes/src/Shader.h
@@ -0,0 +1,64 @@
+#pragma once
+
+#include <GL/glew.h>
+#include <string>
+#include <vector>
+#include "mathlib.h"
+
+typedef GLuint Shader;
+
+Shader loadShader(const GLchar* vertexShader, const GLchar* fragmentShader);
+
+inline GLint getShaderUniform(const Shader& shader, const GLchar *name) {
+ GLint uid = glGetUniformLocation(shader, name);
+ if (uid < 0) {
+ return -1;
+ }
+ return uid;
+}
+
+inline GLint getShaderAttribute(const Shader& shader, const GLchar *name) {
+ printf("Getting attribute for shader, name: %d, %s\n", shader, name);
+ GLint uid = glGetAttribLocation(shader, name);
+ if (uid < 0) {
+ printf("Unable to get attribute %s for shader %d\n", name, shader);
+ return -1;
+ }
+ return uid;
+}
+
+inline void useShader(const Shader& shader) {
+ glUseProgram(shader);
+}
+
+inline void setShaderFloat(GLint location, GLfloat value) {
+ glUniform1f(location, value);
+}
+
+inline void setShaderInt(GLint location, GLint value) {
+ glUniform1i(location, value);
+}
+
+inline void setShaderUint(GLint location, GLuint value) {
+ glUniform1ui(location, value);
+}
+
+inline void setShaderVec2(GLint location, const Vector2& value) {
+ glUniform2f(location, value.x, value.y);
+}
+
+inline void setShaderMat4(GLint location, const Mat4x4& matrix) {
+ glUniformMatrix4fv(location, 1, GL_FALSE, matrix.m);
+}
+
+inline void setShaderBVec3(GLint location, bool first, bool second, bool third) {
+ glUniform3i(location, first, second, third);
+}
+
+inline void setShaderBVec4(GLint location, bool first, bool second, bool third, bool fourth) {
+ glUniform4i(location, first, second, third, fourth);
+}
+
+inline void setShaderBool(GLint location, bool value) {
+ glUniform1i(location, value);
+}
diff --git a/themes/src/Snowflake.cpp b/themes/src/Snowflake.cpp
new file mode 100644
index 0000000..7cab8b3
--- /dev/null
+++ b/themes/src/Snowflake.cpp
@@ -0,0 +1,135 @@
+#include "Snowflake.h"
+#include "Renderer2d.h"
+#include "mathlib.h"
+#include "list.h"
+#include <cstdio>
+
+const Vector4 snowColor = Vector4(1.0, 0.98, 0.98, 1);
+
+inline void generateSnowflakeShape(matte::List<Vertex2D>* vertices, i32 numArms, f32 radius, f32 innerRadius) {
+ f32 dx = ((2 * PI) / numArms) / 3.0;
+ for (i32 centerIdx = 0; centerIdx < (3 * numArms); centerIdx+=3) {
+ f32 degreeStart = dx * centerIdx;
+ f32 degreeEnd = dx * (centerIdx + 1);
+
+ f32 cosStart = cosf(degreeStart);
+ f32 cosEnd = cosf(degreeEnd);
+
+ f32 sinStart = sinf(degreeStart);
+ f32 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(innerRadius * cosStart, innerRadius * sinStart);
+ Vector2 rightStart = diff + Vector2(innerRadius * cosEnd, innerRadius * sinEnd);
+
+ 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, randomIntBetween(4, 16), randomFloatBetween(8.f, 16.f), randomFloatBetween(2.f, 6.f));
+ ud->numVertices = renderer->vertices.numElements - ud->vtxIdx;
+ ud->velocity = Vector2(randomFloatBetween(-10, 10), randomFloatBetween(-100, -85));
+ ud->position = Vector2(randomFloatBetween(0, renderer->xMax), randomFloatBetween(renderer->yMax, -renderer->yMax));
+}
+
+void SnowflakeParticleRenderer::load(SnowflakeLoadParameters params, Renderer2d* renderer) {
+ numSnowflakes = params.numSnowflakes;
+
+ updateData = new SnowflakeUpdateData[params.numSnowflakes];
+
+ xMax = static_cast<f32>(renderer->context->width);
+ yMax = static_cast<f32>(renderer->context->height);
+
+ vertices.deallocate();
+ vertices.growDynamically = true;
+
+ // Initialize each snow flake with its shape
+ for (i32 s = 0; s < numSnowflakes; s++) {
+ auto ud = &updateData[s];
+ initFlake(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(Vertex2D), &vertices.data[0], GL_DYNAMIC_DRAW);
+
+ glEnableVertexAttribArray(renderer->attributes.position);
+ glVertexAttribPointer(renderer->attributes.position, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)0);
+
+ glEnableVertexAttribArray(renderer->attributes.color);
+ glVertexAttribPointer(renderer->attributes.color, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)offsetof(Vertex2D, color));
+
+ for (i32 idx = 0; idx < 4; idx++) {
+ i32 offset = (4 * sizeof(f32)) * idx;
+ glEnableVertexAttribArray(renderer->attributes.vMatrix + idx);
+ glVertexAttribPointer(renderer->attributes.vMatrix + idx,
+ 4,
+ GL_FLOAT,
+ GL_FALSE,
+ sizeof(Vertex2D),
+ (GLvoid *)(offsetof(Vertex2D, vMatrix) + offset));
+ //glVertexAttribDivisor(renderer->attributes.vMatrix + idx, 1);
+ }
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindVertexArray(0);
+}
+
+inline void updateFlake(SnowflakeParticleRenderer* renderer, SnowflakeUpdateData* ud, i32 s, f32 dtSeconds, bool addWind) {
+ if (addWind) ud->velocity += renderer->windSpeed;
+ ud->position += ud->velocity * dtSeconds;
+
+ Mat4x4 m = Mat4x4().translateByVec2(ud->position);
+ for (i32 v = ud->vtxIdx; v < (ud->vtxIdx + ud->numVertices); v++) {
+ renderer->vertices.data[v].vMatrix = m;
+ }
+}
+
+void SnowflakeParticleRenderer::update(f32 dtSeconds) {
+ bool addWind = false;
+ timeUntilNextWindSeconds -= dtSeconds;
+ if (timeUntilNextWindSeconds < 0) {
+ timeUntilNextWindSeconds = windIntervalSeconds;
+ windSpeed = Vector2(randomFloatBetween(-10, 10), randomFloatBetween(-10, 0));
+ addWind = true;
+ }
+
+ for (i32 s = 0; s < numSnowflakes; s++) {
+ SnowflakeUpdateData* ud = &updateData[s];
+ updateFlake(this, ud, s, dtSeconds, addWind);
+ }
+}
+
+void SnowflakeParticleRenderer::render(Renderer2d* renderer) {
+ setShaderMat4(renderer->uniforms.model, model);
+
+ glBindBuffer(GL_ARRAY_BUFFER, vbo);
+ glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.numElements * sizeof(Vertex2D), &vertices.data[0]);
+
+ glBindVertexArray(vao);
+ glDrawArrays(GL_TRIANGLES, 0, vertices.numElements);
+ glBindVertexArray(0);
+}
+
+void SnowflakeParticleRenderer::unload() {
+ glDeleteVertexArrays(1, &vao);
+ glDeleteBuffers(1, &vbo);
+ vao = 0;
+ vbo = 0;
+ vertices.deallocate();
+ delete [] updateData;
+}
diff --git a/themes/src/Snowflake.h b/themes/src/Snowflake.h
new file mode 100644
index 0000000..c6323d6
--- /dev/null
+++ b/themes/src/Snowflake.h
@@ -0,0 +1,47 @@
+#include "types.h"
+#include "mathlib.h"
+#include "list.h"
+
+struct Renderer2d;
+struct Vertex2D;
+
+struct SnowflakeLoadParameters {
+ i32 numSnowflakes = 1000;
+ f32 rateOfSnowfall = 0.1f;
+ Vector2 flakeV0 = { 0, 1 };
+ f32 flakeSize = 5.f;
+ f32 flakeSizeDeviation = 1.f;
+ Vector4 snowColor = { 0.8, 0.8, 0.8, 1.0 };
+ f32 windIntervalSeconds = 1.5;
+};
+
+struct SnowflakeUpdateData {
+ Vector2 v0;
+ Vector2 velocity;
+ Vector2 position;
+ f32 rotation;
+ bool onGround = false;
+
+ i32 vtxIdx = 0;
+ i32 numVertices = 0;
+};
+
+struct SnowflakeParticleRenderer {
+ f32 xMax = 0;
+ f32 yMax = 0;
+ f32 windIntervalSeconds = 1.5;
+ i32 numSnowflakes = 0;
+ Vector2 windSpeed;
+ SnowflakeUpdateData* updateData;
+ f32 timeUntilNextWindSeconds = 0;
+
+ u32 vao;
+ u32 vbo;
+ Mat4x4 model;
+ matte::List<Vertex2D> vertices;
+
+ void load(SnowflakeLoadParameters params, Renderer2d* renderer);
+ void update(f32 dtSeconds);
+ void render(Renderer2d* renderer);
+ void unload();
+};
diff --git a/themes/src/SummerTheme.cpp b/themes/src/SummerTheme.cpp
new file mode 100644
index 0000000..20bb310
--- /dev/null
+++ b/themes/src/SummerTheme.cpp
@@ -0,0 +1,67 @@
+#include "SummerTheme.h"
+#include "Renderer2d.h"
+#include "list.h"
+#include "mathlib.h"
+#include <vector>
+
+void SummerTheme::load(Renderer2d* renderer) {
+ renderer->clearColor = Vector4(0, 181, 286, 255.f).toNormalizedColor();
+ sun.sectors = 180;
+ sun.radius = renderer->context->width / 4.f;
+ sun.load(renderer);
+}
+
+void SummerTheme::update(f32 dtSeconds) {
+ sun.update(dtSeconds);
+}
+
+void SummerTheme::render(Renderer2d* renderer) {
+ sun.render(renderer);
+}
+
+void SummerTheme::unload() {
+ sun.unload();
+}
+
+void Sun::load(Renderer2d* renderer) {
+ matte::List<Vertex2D> vertices;
+ matte::List<u32> indices;
+ Vector4 sunColor = Vector4(249, 215, 28, 255).toNormalizedColor();
+ vertices.add({ Vector2(0, 0), sunColor, Mat4x4() });
+
+ f32 radiansPerSector = (2.f * PI) / sectors;
+ for (i32 i = 0; i <= sectors; i++) {
+ f32 radians = radiansPerSector * i;
+ f32 cosAngle = cosf(radians);
+ f32 sinAngle = sinf(radians);
+ Vector2 vertex = Vector2(radius * cosAngle, radius * sinAngle);
+ vertices.add({ vertex, sunColor, Mat4x4() });
+
+ u32 first = i;
+ u32 second = 0;
+ u32 third = i + 1;
+ indices.add(first);
+ indices.add(second);
+ indices.add(third);
+ }
+
+ mesh.load(&vertices.data[0], vertices.numElements, &indices.data[0], indices.numElements, renderer);
+ mesh.model = Mat4x4().translateByVec2(Vector2(renderer->context->width / 2.f, renderer->context->height / 2.f));
+ vertices.deallocate();
+ indices.deallocate();
+}
+
+void Sun::update(f32 dtSeconds) {
+
+}
+
+void Sun::render(Renderer2d* renderer) {
+ setShaderMat4(renderer->uniforms.model, mesh.model);
+ glBindVertexArray(mesh.vao);
+ glDrawElements(GL_TRIANGLES, mesh.numIndices, GL_UNSIGNED_INT, 0);
+ glBindVertexArray(0);
+}
+
+void Sun::unload() {
+ mesh.unload();
+}
diff --git a/themes/src/SummerTheme.h b/themes/src/SummerTheme.h
new file mode 100644
index 0000000..1d9093a
--- /dev/null
+++ b/themes/src/SummerTheme.h
@@ -0,0 +1,23 @@
+#pragma once
+#include "types.h"
+#include "Renderer2d.h"
+#include <vector>
+
+struct Sun {
+ f32 radius = 20.f;
+ i32 sectors = 180;
+ Mesh2D mesh;
+
+ void load(Renderer2d* renderer);
+ void update(f32 dtSeconds);
+ void render(Renderer2d* renderer);
+ void unload();
+};
+
+struct SummerTheme {
+ Sun sun;
+ void load(Renderer2d* renderer);
+ void update(f32 dtSeconds);
+ void render(Renderer2d* renderer);
+ void unload();
+};
diff --git a/themes/src/TreeShape.cpp b/themes/src/TreeShape.cpp
new file mode 100644
index 0000000..a3ae8f7
--- /dev/null
+++ b/themes/src/TreeShape.cpp
@@ -0,0 +1,214 @@
+#include "TreeShape.h"
+#include "mathlib.h"
+#include <cstdio>
+#include <cstdlib>
+#include <cfloat>
+#include <ctime>
+
+void TreeBranchLoadData::fillVertices(Vertex2D* vertices, int branchTier) {
+ bottomLeft = Vector2 { position.x - width / 2.f, position.y }.rotateAround(rotation, position);
+ bottomRight = Vector2 { position.x + width / 2.f, position.y }.rotateAround(rotation, position);
+ topLeft = (Vector2 { position.x - width / 2.f, position.y + height }).rotateAround(rotation, position);
+ topRight = (Vector2 { position.x + width / 2.f, position.y + height }).rotateAround(rotation, position);
+
+ topMidpoint = topLeft + (topRight - topLeft) / 2.f;
+
+ vertices[0] = { bottomLeft, color};
+ vertices[1] = { bottomRight, color};
+ vertices[2] = { topLeft, color};
+ vertices[3] = { topLeft, color};
+ vertices[4] = { topRight, color};
+ vertices[5] = { bottomRight, color};
+};
+
+TreeShapeLoadResult TreeShape::load(Renderer2d* renderer) {
+ srand ( time(NULL) );
+
+ timeElapsedSeconds = 0;
+
+ TreeLoadData ld;
+
+ numBranches = pow(ld.divisionsPerBranch, ld.numBranchLevels + 1);
+ numVertices = 6 * numBranches;
+
+ TreeBranchLoadData* generationData = new TreeBranchLoadData[numBranches];
+ updateData = new TreeBranchUpdateData[numBranches];
+ vertices = new Vertex2D[numVertices];
+
+ // The load result will contain information that we can pass on to our leaf renderer.
+ TreeShapeLoadResult lr;
+ lr.lowerBounds = Vector2(FLT_MAX, FLT_MAX);
+ lr.upperBounds = Vector2(FLT_MIN, FLT_MIN);
+ lr.updateData = updateData;
+ lr.numBranches = numBranches;
+ i32 branchIndex = 0;
+ createBranch(&ld, generationData, numBranches, &branchIndex, 0, ld.trunkWidth, ld.trunkHeight, Vector2 { 300.f, 50.f }, 0, NULL, vertices, &lr);
+
+ useShader(renderer->shader);
+
+ glGenVertexArrays(1, &vao);
+ glBindVertexArray(vao);
+
+ glGenBuffers(1, &vbo);
+ glBindBuffer(GL_ARRAY_BUFFER, vbo);
+ glBufferData(GL_ARRAY_BUFFER, numVertices * sizeof(Vertex2D), &vertices[0], GL_DYNAMIC_DRAW);
+
+ glEnableVertexAttribArray(renderer->attributes.position);
+ glVertexAttribPointer(renderer->attributes.position, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)0);
+
+ glEnableVertexAttribArray(renderer->attributes.color);
+ glVertexAttribPointer(renderer->attributes.color, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)offsetof(Vertex2D, color));
+
+ for (i32 idx = 0; idx < 4; idx++) {
+ glEnableVertexAttribArray(renderer->attributes.vMatrix + idx);
+ glVertexAttribPointer(renderer->attributes.vMatrix + idx, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)(offsetof(Vertex2D, vMatrix) + (idx * 16)));
+ glVertexAttribDivisor(renderer->attributes.vMatrix + idx, 1);
+ }
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindVertexArray(0);
+
+ delete [] generationData;
+
+ return lr;
+}
+
+const f32 ninetyDegreeRotation = PI / 2.f;
+
+void TreeShape::createBranch(TreeLoadData* ld, TreeBranchLoadData* generationData, i32 numBranches, i32* branchIndex,
+ i32 branchLevel, f32 width, f32 height, Vector2 position, f32 rotation,
+ TreeBranchUpdateData* parent, Vertex2D* vertices, TreeShapeLoadResult* lr) {
+ TreeBranchLoadData* branchLoadData = &generationData[*branchIndex];
+ branchLoadData->width = width;
+ branchLoadData->height = height;
+ branchLoadData->position = position;
+ branchLoadData->rotation = rotation;
+ branchLoadData->fillVertices(&vertices[(*branchIndex) * 6], branchLevel);
+
+ // Fil in the bounds for the LeafRenderer later.
+ if (branchLoadData->topMidpoint.x > lr->upperBounds.x) {
+ lr->upperBounds.x = branchLoadData->topMidpoint.x;
+ }
+ if (branchLoadData->topMidpoint.y > lr->upperBounds.y) {
+ lr->upperBounds.y = branchLoadData->topMidpoint.y;
+ }
+ if (branchLoadData->topMidpoint.x < lr->lowerBounds.x) {
+ lr->lowerBounds.x = branchLoadData->topMidpoint.x;
+ }
+ if (branchLoadData->topMidpoint.y < lr->lowerBounds.y) {
+ lr->lowerBounds.y = branchLoadData->topMidpoint.y;
+ }
+
+ TreeBranchUpdateData* branchUpdateData = &updateData[*branchIndex];
+ branchUpdateData->tier = branchLevel;
+ branchUpdateData->periodOffset = randomFloatBetween(0.f, 2.f * PI);
+ branchUpdateData->period = randomFloatBetween(3.f, 5.f);
+ branchUpdateData->amplitude = randomFloatBetween(0.01f, 0.05f);
+ branchUpdateData->branchToFollow = parent;
+ branchUpdateData->vertices = &vertices[(*branchIndex) * 6];
+
+ if (branchLevel == ld->numBranchLevels) {
+ return;
+ }
+
+ for (int division = 0; division < ld->divisionsPerBranch; division++) {
+ // Weight between [0, 1]
+ float weight = static_cast<f32>(division) / static_cast<f32>(ld->divisionsPerBranch - 1);
+
+ // Normalize the weight between [-1, 1]
+ f32 normalizedWeight = (0.5f - (weight)) * 2.f;
+
+ // We want a rotation that takes the current rotation of the branch, and averages it between the two branches.
+ f32 branchRotationAmount = randomFloatBetween(PI / 8.f, PI / 3.f);
+ f32 branchRotation = branchLoadData->rotation + (normalizedWeight * branchRotationAmount);
+
+ // Since trees are taller vertically, we will find a normalized value that describes how far the direction is from
+ // being horizontal. If it is closer to 1, we will make the branch taller on average.
+ f32 verticalHeightScaler = (fabs(fabs(branchRotation) - ninetyDegreeRotation) / ninetyDegreeRotation) * 0.1;
+ f32 branchWidth = width * randomFloatBetween(ld->trunkWidthScalerMin, ld->trunkWidthScalerMax);
+ f32 branchHeight = height * randomFloatBetween(ld->trunkHeightScalerMin + verticalHeightScaler, ld->trunkHeightScalerMax + verticalHeightScaler);
+
+
+ // We want the branch to start within the previous branch, so we drop it down into it based off of the rotation.
+ Vector2 branchOffsetVertical = Vector2{ 0, branchWidth }.rotate(branchRotation);
+
+ Vector2 branchPosition = branchLoadData->topLeft + ((branchLoadData->topRight - branchLoadData->topLeft) * weight) - branchOffsetVertical; // Position of branch along the top of the parent branch
+
+ (*branchIndex)++;
+ createBranch(ld, generationData, numBranches, branchIndex, branchLevel + 1, branchWidth, branchHeight, branchPosition, branchRotation, branchUpdateData, vertices, lr);
+ }
+}
+
+void TreeShape::update(f32 dtSeconds) {
+ timeElapsedSeconds += dtSeconds;
+
+ for (i32 bIdx = 0; bIdx < numBranches; bIdx++) {
+ TreeBranchUpdateData* branchUpdataData = &updateData[bIdx];
+
+ // Fade in simulation. We fade in based on the tier.
+ f32 animationStart = (branchUpdataData->tier * animateStaggerPerTier);
+ f32 animationEnd = animationStart + animateTimePerTier;
+
+ f32 alpha = 0.f;
+ if (timeElapsedSeconds < animationStart) {
+ alpha = 0.f;
+ }
+ else if (timeElapsedSeconds > animationEnd) {
+ alpha = 1.f;
+ }
+ else {
+ alpha = (1.f - (animationEnd - timeElapsedSeconds)) / animateTimePerTier;
+ }
+
+ i32 startParentIndex = bIdx * 6;
+
+ branchUpdataData->currentOffset.x = branchUpdataData->amplitude * cosf(branchUpdataData->periodOffset + branchUpdataData->period * timeElapsedSeconds);
+ branchUpdataData->currentOffset.y = branchUpdataData->amplitude * sinf(branchUpdataData->periodOffset + branchUpdataData->period * timeElapsedSeconds);
+
+ if (branchUpdataData->branchToFollow != NULL) {
+ branchUpdataData->currentOffset += branchUpdataData->branchToFollow->currentOffset;
+
+ // The root of the branch only moves according to the change of the end of the parent.
+ branchUpdataData->vertices[0].color.w = alpha;
+ branchUpdataData->vertices[0].position += branchUpdataData->branchToFollow->currentOffset;
+ branchUpdataData->vertices[1].color.w = alpha;
+ branchUpdataData->vertices[1].position += branchUpdataData->branchToFollow->currentOffset;
+ branchUpdataData->vertices[5].color.w = alpha;
+ branchUpdataData->vertices[5].position += branchUpdataData->branchToFollow->currentOffset;
+ }
+ else {
+ branchUpdataData->vertices[0].color.w = alpha;
+ branchUpdataData->vertices[1].color.w = alpha;
+ branchUpdataData->vertices[5].color.w = alpha;
+ }
+
+
+ branchUpdataData->vertices[2].color.w = alpha;
+ branchUpdataData->vertices[2].position += branchUpdataData->currentOffset;
+ branchUpdataData->vertices[3].color.w = alpha;
+ branchUpdataData->vertices[3].position += branchUpdataData->currentOffset;
+ branchUpdataData->vertices[4].color.w = alpha;
+ branchUpdataData->vertices[4].position += branchUpdataData->currentOffset;
+ }
+}
+
+void TreeShape::render(Renderer2d* renderer) {
+ setShaderMat4(renderer->uniforms.model, model);
+
+ glBindBuffer(GL_ARRAY_BUFFER, vbo);
+ glBufferSubData(GL_ARRAY_BUFFER, 0, numVertices * sizeof(Vertex2D), &vertices[0]);
+
+ glBindVertexArray(vao);
+ glDrawArrays(GL_TRIANGLES, 0, numVertices);
+ glBindVertexArray(0);
+}
+
+void TreeShape::unload() {
+ glDeleteVertexArrays(1, &vao);
+ glDeleteBuffers(1, &vbo);
+ delete[] vertices;
+ delete [] updateData;
+ timeElapsedSeconds = 0;
+ vertices = NULL;
+ updateData = NULL;
+}
diff --git a/themes/src/TreeShape.h b/themes/src/TreeShape.h
new file mode 100644
index 0000000..32b00d3
--- /dev/null
+++ b/themes/src/TreeShape.h
@@ -0,0 +1,74 @@
+#include "Renderer2d.h"
+#include "types.h"
+#include "mathlib.h"
+
+struct TreeLoadData {
+ f32 trunkHeight = 96.f; // Height of the trunk start
+ f32 trunkWidth = 32.f; // Width of the trunk start
+ f32 trunkHeightScalerMin = 0.7f;
+ f32 trunkHeightScalerMax = 0.8f;
+ f32 trunkWidthScalerMin = 0.35f;
+ f32 trunkWidthScalerMax = 0.75f;
+ i32 divisionsPerBranch = 2; // How many branches to split into at each branch split
+ i32 numBranchLevels = 8; // How many branch levels to display
+};
+
+struct TreeBranchLoadData {
+ f32 width = 0.f;
+ f32 height = 0.f;
+ Vector2 position; // Center point
+ f32 rotation = 0; // How much we are rotated off of the center point in radians
+ Vector4 color = Vector4(101,56,24, 1.f).toNormalizedColor();
+
+ // Calculated while filling in vertices
+ Vector2 bottomLeft;
+ Vector2 bottomRight;
+ Vector2 topLeft;
+ Vector2 topRight;
+ Vector2 topMidpoint;
+
+ void fillVertices(Vertex2D* vertices, int branchTier);
+};
+
+struct TreeBranchUpdateData {
+ i32 tier = 0;
+ f32 periodOffset = 0;
+ f32 period = 0;
+ f32 amplitude = 0;
+ Vector2 currentOffset;
+ Vertex2D* vertices = NULL;
+ TreeBranchUpdateData* branchToFollow = NULL;
+};
+
+struct TreeShapeLoadResult {
+ Vector2 lowerBounds;
+ Vector2 upperBounds;
+ Vector2 center;
+ TreeBranchUpdateData* updateData;
+ u32 numBranches = 0;
+};
+
+struct TreeShape {
+ // Update data
+ TreeBranchUpdateData* updateData = NULL;
+ Vertex2D* vertices = NULL;
+ f32 timeElapsedSeconds = 0.f;
+ f32 animateTimePerTier = 1.f;
+ f32 animateStaggerPerTier = 0.2f;
+ u32 numBranches = 0;
+
+ // Render data
+ u32 vao;
+ u32 vbo;
+ u32 numVertices = 0;
+ Mat4x4 model;
+
+ TreeShapeLoadResult load(Renderer2d* renderer);
+ void createBranch(TreeLoadData* ld, TreeBranchLoadData* branchList, i32 numBranches,
+ i32* branchIndex, i32 branchLevel, f32 width, f32 height,
+ Vector2 position, f32 rotation, TreeBranchUpdateData* parent, Vertex2D* vertices, TreeShapeLoadResult* lr);
+ void update(f32 dtSeconds);
+ void render(Renderer2d* renderer);
+ void unload();
+};
+
diff --git a/themes/src/WebglContext.cpp b/themes/src/WebglContext.cpp
new file mode 100644
index 0000000..df49c2d
--- /dev/null
+++ b/themes/src/WebglContext.cpp
@@ -0,0 +1,46 @@
+#include "WebglContext.h"
+#include <cstdio>
+
+
+EM_BOOL onResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData) {
+ WebglContext* context = (WebglContext*)userData;
+
+ f64 inWidth, inHeight;
+ emscripten_get_element_css_size(context->query, &inWidth, &inHeight);
+
+ context->width = static_cast<f32>(inWidth);
+ context->height = static_cast<f32>(inHeight);
+
+ return true;
+}
+
+void WebglContext::init(const char* inQuery) {
+ strcpy(query, inQuery);
+ f64 inWidth, inHeight;
+ emscripten_get_element_css_size(query, &inWidth, &inHeight);
+ width = static_cast<f32>(inWidth);
+ height = static_cast<f32>(inHeight);
+ emscripten_set_canvas_element_size( query, width, height);
+
+ EmscriptenWebGLContextAttributes attrs;
+ emscripten_webgl_init_context_attributes(&attrs);
+
+ attrs.enableExtensionsByDefault = 1;
+ attrs.majorVersion = 3;
+ attrs.minorVersion = 0;
+
+ context = emscripten_webgl_create_context(query, &attrs);
+ makeCurrentContext();
+
+ glClearColor(0, 0, 0, 0.0f);
+
+ emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, this, false, onResize);
+};
+
+void WebglContext::makeCurrentContext() {
+ emscripten_webgl_make_context_current(context);
+};
+
+void WebglContext::destroy() {
+ emscripten_webgl_destroy_context(context);
+}
diff --git a/themes/src/WebglContext.h b/themes/src/WebglContext.h
new file mode 100644
index 0000000..1956092
--- /dev/null
+++ b/themes/src/WebglContext.h
@@ -0,0 +1,18 @@
+#pragma once
+#include "types.h"
+#include <cstring>
+#include <emscripten.h>
+#include <emscripten/html5.h>
+#include <GLES2/gl2.h>
+#include <EGL/egl.h>
+
+struct WebglContext {
+ EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context;
+ f32 width = 800;
+ f32 height = 600;
+ char query[128];;
+
+ void init(const char* inQuery);
+ void makeCurrentContext();
+ void destroy() ;
+};
diff --git a/themes/src/list.h b/themes/src/list.h
new file mode 100644
index 0000000..25b236a
--- /dev/null
+++ b/themes/src/list.h
@@ -0,0 +1,245 @@
+#pragma once
+#include <cstdlib>
+#include <cstring>
+#include "Logger.h"
+
+#define FOREACH(list) \
+ for (i32 idx = 0; idx < list.numElements; idx++) \
+ if (auto value = &list[idx]) \
+
+namespace matte {
+ 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 set(T&& value, size_t index);
+ 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)(const T& first, const 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) {
+ logger_error("Cannot grow list: growDynamically is disabled");
+ return false;
+ }
+
+ if (newSize == 0) {
+ logger_error("Cannot grow list: newSize is zero!");
+ 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>::set(T&& value, size_t index) {
+ if (index >= capacity && !grow(index * 2)) {
+ return;
+ }
+
+ data[index] = value;
+ }
+
+ 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)(const T& first, const 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(data[idx], data[innerIdx]) > 0) {
+ minIdx= innerIdx;
+ }
+ }
+
+ T temp = data[minIdx];
+ memmove(&data[minIdx], &data[idx], sizeof(T));
+ memmove(&data[idx], &temp, sizeof(T));
+ }
+ }
+}
diff --git a/themes/src/main.cpp b/themes/src/main.cpp
new file mode 100644
index 0000000..f8771d4
--- /dev/null
+++ b/themes/src/main.cpp
@@ -0,0 +1,445 @@
+#include "WebglContext.h"
+#include "MainLoop.h"
+#include "Renderer2d.h"
+#include "Renderer3d.h"
+#include "mathlib.h"
+#include "types.h"
+#include "TreeShape.h"
+#include "SummerTheme.h"
+#include "LeafParticleRender.h"
+#include "Snowflake.h"
+#include <cstdio>
+#include <emscripten/fetch.h>
+
+
+enum Theme {
+ Default = 0,
+ Autumn,
+ Winter,
+ Spring,
+ Summer
+};
+
+struct AutumnTheme {
+ TreeShape tree;
+ LeafParticleRender leafParticles;
+
+ void load(Renderer2d* renderer);
+ void update(f32 dtSeconds);
+ void render(Renderer2d* renderer);
+ void unload();
+};
+
+struct WinterTheme {
+ SnowflakeParticleRenderer spr;
+
+ void load(Renderer2d* renderer);
+ void update(f32 dtSeconds);
+ void render(Renderer2d* renderer);
+ void unload();
+};
+
+enum class BunnyAnimationState {
+ Loading = 0,
+ Loaded,
+ PreHop,
+ Hopping,
+ Idle
+};
+
+struct SpringTheme {
+ BunnyAnimationState state;
+ f32 bunnySpeed = 5.f;
+ Vector3 bunnyPosition = Vector3(0, 0, 0);
+ Vector3 bunnyTarget = Vector3(0, 0, 0);
+ Vector3 hopIncrement = Vector3(0, 0, 0);
+
+ f32 numHops = 0;
+ f32 hopCount = 0;
+ f32 bunnyHopAnimationTimer = 0.f;
+ f32 stateTimer = 0.f;
+ f32 bunnyRotation = 0.f;
+ f32 targetRotation = 0.f;
+
+ Mesh3d bunnyMesh;
+
+ void load(Renderer3D* renderer);
+ void update(f32 dtSeconds);
+ void render(Renderer3D* renderer);
+ void unload();
+};
+
+void load(Theme theme);
+void unload();
+void update(f32 dtSeconds, void* userData);
+EM_BOOL selectNone(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData);
+EM_BOOL selectAutumn(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData);
+EM_BOOL selectWinter(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData);
+EM_BOOL selectSpring(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData);
+EM_BOOL selectSummer(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData);
+
+WebglContext context;
+Renderer2d renderer2d;
+Renderer3D renderer3d;
+MainLoop mainLoop;
+Theme activeTheme = Theme::Default;
+AutumnTheme autumnTheme;
+WinterTheme winterTheme;
+SpringTheme springTheme;
+SummerTheme summerTheme;
+
+int main() {
+ context.init("#theme_canvas");
+ emscripten_set_click_callback("#theme_button_default", NULL, false, selectNone);
+ emscripten_set_click_callback("#theme_button_autumn", NULL, false, selectAutumn);
+ emscripten_set_click_callback("#theme_button_winter", NULL, false, selectWinter);
+ emscripten_set_click_callback("#theme_button_spring", NULL, false, selectSpring);
+ emscripten_set_click_callback("#theme_button_summer", NULL, false, selectSummer);
+
+ return 0;
+}
+
+// -- Scene loading, updating, and unloading logic
+void load(Theme theme) {
+ if (activeTheme == theme) {
+ printf("This theme is already active.\n");
+ return;
+ }
+
+ unload(); // Try and unload before we load, so that we start fresh
+
+ activeTheme = theme;
+ mainLoop.run(update);
+
+ switch (activeTheme) {
+ case Theme::Autumn:
+ renderer2d.load(&context);
+ autumnTheme.load(&renderer2d);
+ break;
+ case Theme::Winter:
+ renderer2d.load(&context);
+ winterTheme.load(&renderer2d);
+ break;
+ case Theme::Spring:
+ renderer3d.load(&context);
+ springTheme.load(&renderer3d);
+ break;
+ case Theme::Summer:
+ renderer2d.load(&context);
+ summerTheme.load(&renderer2d);
+ break;
+ default:
+ break;
+ }
+}
+
+void update(f32 dtSeconds, void* userData) {
+ // -- Update
+ switch (activeTheme) {
+ case Theme::Autumn:
+ autumnTheme.update(dtSeconds);
+ break;
+ case Theme::Winter:
+ winterTheme.update(dtSeconds);
+ break;
+ case Theme::Spring:
+ springTheme.update(dtSeconds);
+ break;
+ case Theme::Summer:
+ summerTheme.update(dtSeconds);
+ break;
+ default:
+ break;
+ }
+
+ // -- Render
+ switch (activeTheme) {
+ case Theme::Autumn:
+ renderer2d.render();
+ autumnTheme.render(&renderer2d);
+ break;
+ case Theme::Winter:
+ renderer2d.render();
+ winterTheme.render(&renderer2d);
+ break;
+ case Theme::Spring:
+ renderer3d.render();
+ springTheme.render(&renderer3d);
+ break;
+ case Theme::Summer:
+ renderer2d.render();
+ summerTheme.render(&renderer2d);
+ break;
+ default:
+ break;
+ }
+}
+
+void unload() {
+ switch (activeTheme) {
+ case Theme::Autumn:
+ autumnTheme.unload();
+ break;
+ case Theme::Winter:
+ winterTheme.unload();
+ break;
+ case Theme::Spring:
+ springTheme.unload();
+ break;
+ case Theme::Summer:
+ summerTheme.unload();
+ break;
+ default:
+ break;
+ }
+
+ activeTheme = Theme::Default;
+ if (mainLoop.isRunning) {
+ mainLoop.stop();
+ renderer2d.unload();
+ renderer3d.unload();
+ }
+}
+
+// -- HTML5 callbacks
+EM_BOOL selectNone(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData) {
+ printf("Default theme selected\n");
+ unload();
+ return true;
+}
+
+EM_BOOL selectAutumn(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData) {
+ printf("Autumn theme selected\n");
+ load(Theme::Autumn);
+ return true;
+}
+
+EM_BOOL selectWinter(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData) {
+ printf("Winter theme selected\n");
+ load(Theme::Winter);
+ return true;
+}
+
+EM_BOOL selectSpring(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData) {
+ printf("Spring theme selected\n");
+ load(Theme::Spring);
+ return true;
+}
+
+EM_BOOL selectSummer(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData) {
+ printf("Summer theme selected\n");
+ load(Theme::Summer);
+ return true;
+}
+
+// -- Autumn theme
+void AutumnTheme::load(Renderer2d* renderer) {
+ renderer->clearColor = Vector4(252, 210, 153, 255).toNormalizedColor();
+ auto lr = tree.load(renderer);
+ leafParticles.load(renderer, &lr);
+}
+
+void AutumnTheme::update(f32 dtSeconds) {
+ tree.update(dtSeconds);
+ leafParticles.update(dtSeconds);
+}
+
+void AutumnTheme::render(Renderer2d* renderer) {
+ tree.render(renderer);
+ leafParticles.render(renderer);
+}
+
+void AutumnTheme::unload() {
+ tree.unload();
+ leafParticles.unload();
+}
+
+// -- Winter theme
+void WinterTheme::load(Renderer2d* renderer) {
+ renderer->clearColor = Vector4(200, 229, 239, 255).toNormalizedColor();
+ SnowflakeLoadParameters lp;
+ spr.load(lp, renderer);
+}
+
+void WinterTheme::update(f32 dtSeconds) {
+ spr.update(dtSeconds);
+}
+
+void WinterTheme::render(Renderer2d* renderer) {
+ spr.render(renderer);
+}
+
+void WinterTheme::unload() {
+ spr.unload();
+}
+
+// -- Spring theme
+void onBunnySuccess(emscripten_fetch_t *fetch) {
+ springTheme.state = BunnyAnimationState::Loaded;
+ printf("Finished downloading %llu bytes from URL %s.\n", fetch->numBytes, fetch->url);
+ const i32 len = fetch->numBytes;
+ springTheme.bunnyMesh = Mesh3d_fromObj(&renderer3d, fetch->data, len);
+ // The data is now available at fetch->data[0] through fetch->data[fetch->numBytes-1];
+ emscripten_fetch_close(fetch); // Free data associated with the fetch.
+}
+
+void onBunnyFail(emscripten_fetch_t *fetch) {
+ printf("Downloading %s failed, HTTP failure status code: %d.\n", fetch->url, fetch->status);
+ emscripten_fetch_close(fetch); // Also free data on failure.
+}
+
+void SpringTheme::load(Renderer3D* renderer) {
+ springTheme.state = BunnyAnimationState::Loading;
+ renderer->clearColor = Vector4(160, 231, 160, 255.f).toNormalizedColor();
+
+ emscripten_fetch_attr_t attr;
+ emscripten_fetch_attr_init(&attr);
+ strcpy(attr.requestMethod, "GET");
+ attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY;
+ attr.onsuccess = onBunnySuccess;
+ attr.onerror = onBunnyFail;
+ emscripten_fetch(&attr, "themes/resources/bunny.obj");
+}
+
+inline Vector3 bunnyLerp(Vector3& start, Vector3& target, f32 t) {
+ t = 3 * t *t - 2 * t * t * t;
+ return start + ((target - start) * t);
+}
+
+inline f32 verticalHopLerp(f32 start, f32 target, f32 t) {
+ f32 ogt = t;
+ t = 3 * t *t - 2 * t * t * t;
+ if (ogt >= 0.5f) t = 1.f - t;
+ return start + ((target - start) * t);
+}
+
+inline f32 rotationLerp(f32 start, f32 target, f32 t) {
+ return start + ((target - start) * t);
+}
+
+void SpringTheme::update(f32 dtSeconds) {
+ switch (state) {
+ case BunnyAnimationState::Loading: return;
+ case BunnyAnimationState::Loaded:
+ state = BunnyAnimationState::Idle;
+ stateTimer = 0.f;
+ bunnyHopAnimationTimer = 0.f;
+ break;
+ case BunnyAnimationState::Idle: {
+ bunnyHopAnimationTimer += dtSeconds;
+ const f32 HOP_FREQUENCY = 6.f;
+
+ if (bunnyHopAnimationTimer > stateTimer) {
+ state = BunnyAnimationState::PreHop;
+ f32 xDir = 1;
+ f32 yDir = 1;
+ if (bunnyTarget.x > 0) xDir = -1;
+ if (bunnyTarget.z > 0) yDir = -1;
+ bunnyTarget = bunnyPosition + Vector3(randomFloatBetween(0, xDir * 25), 0, randomFloatBetween(0, yDir * 25));
+ auto direction = (bunnyTarget - bunnyPosition);
+ auto distance = direction.length();
+ direction = direction.normalize();
+ numHops = ceil(distance / HOP_FREQUENCY);
+ hopCount = 0;
+
+ targetRotation = PI - atan2(direction.y, direction.x);
+ stateTimer = ((bunnyTarget - bunnyPosition).length() / bunnySpeed) / numHops;
+ bunnyHopAnimationTimer = 0.f;
+ hopIncrement = (bunnyTarget - bunnyPosition) / numHops;
+ }
+ break;
+ }
+ case BunnyAnimationState::PreHop: {
+ const f32 ROTATION_TIME = 0.5f;
+ bunnyHopAnimationTimer += dtSeconds;
+ f32 current = bunnyRotation + (targetRotation - bunnyRotation) * (bunnyHopAnimationTimer / ROTATION_TIME);
+ bunnyMesh.model = Mat4x4().rotate(0, current, 0).translate(bunnyPosition);
+
+ if (bunnyHopAnimationTimer > ROTATION_TIME) {
+ bunnyRotation = targetRotation;
+ bunnyHopAnimationTimer = 0;
+ state = BunnyAnimationState::Hopping;
+ }
+ break;
+ }
+ case BunnyAnimationState::Hopping: {
+ bunnyHopAnimationTimer += dtSeconds;
+ f32 t = bunnyHopAnimationTimer / stateTimer;
+
+ Vector3 nextPosition = bunnyPosition + hopIncrement;
+ auto renderPos = bunnyLerp(bunnyPosition, nextPosition, t);
+ if ((renderPos - nextPosition).length() < 0.01f) {
+ hopCount += 1;
+ bunnyHopAnimationTimer = 0.f;
+ bunnyPosition = nextPosition;
+ }
+
+ renderPos.y = verticalHopLerp(0.f, 4.f, t);
+
+ const f32 RMAX = PI / 16.f;
+ f32 zRotation = 0;
+ f32 start = 0.f;
+ f32 end = PI / 8.f;
+ f32 startTime = 0.f;
+ f32 endTime = 0.f;
+ bool disableRot = false;
+
+ if (t >= 0.9f) {
+ disableRot = true;
+ }
+ else if (t >= 0.7f) {
+ start = -RMAX;
+ end = 0.f;
+ startTime = 0.7f;
+ endTime = 0.9f;
+ }
+ else if (t >= 0.50f) {
+ start = 0.f;
+ end = -RMAX;
+ startTime = 0.50f;
+ endTime = 0.70f;
+ }
+ else if (t >= 0.40f) {
+ disableRot = true;
+ }
+ else if (t >= 0.20f) {
+ start = RMAX;
+ end = 0.f;
+ startTime = 0.20f;
+ endTime = 0.40f;
+ }
+ else {
+ start = 0.f;
+ end = RMAX;
+ startTime = 0.f;
+ endTime = 0.20f;
+ }
+
+
+ if (!disableRot) {
+ f32 totalTime = endTime - startTime;
+ zRotation = rotationLerp(start, end, (totalTime - (endTime - t)) / totalTime);
+ }
+
+ bunnyMesh.model = Mat4x4().getZRotationMatrix(zRotation).rotate(0, bunnyRotation, 0).translate(renderPos);
+ if (hopCount == numHops) {
+ bunnyPosition = bunnyTarget;
+ bunnyHopAnimationTimer = 0.f;
+ state = BunnyAnimationState::Idle;
+ stateTimer = randomFloatBetween(0.5f, 1.f);
+ }
+ break;
+ }
+ }
+}
+
+void SpringTheme::render(Renderer3D* renderer) {
+ renderer->render();
+ if (state != BunnyAnimationState::Loading) {
+ bunnyMesh.render(renderer);
+ }
+}
+
+void SpringTheme::unload() {
+ bunnyMesh.unload();
+}
diff --git a/themes/src/mathlib.cpp b/themes/src/mathlib.cpp
new file mode 100644
index 0000000..9e86833
--- /dev/null
+++ b/themes/src/mathlib.cpp
@@ -0,0 +1,779 @@
+// mathlib.cpp
+//
+// Created by Matt Kosarek
+//
+// A file containing some common math functionality that I find
+// useful in my projects. I don't like that I don't know what's happening
+// in glm, so I wrote most of this myself. All mistakes are my own.
+//
+
+#include "mathlib.h"
+#include <cstdlib>
+
+// ***************************************
+// Vector2
+Vector2::Vector2() { }
+
+Vector2::Vector2(float inX, float inY) {
+ x = inX;
+ y = inY;
+}
+
+Vector2 getRandomNormalVector2() {
+ Vector2 retval = {
+ static_cast<float>(rand()) / static_cast<float>(RAND_MAX),
+ static_cast<float>(rand()) / static_cast<float>(RAND_MAX)
+ };
+
+ return retval.normalize();
+}
+
+Vector2 Vector2::operator+(Vector2 other) {
+ return { x + other.x, y + other.y };
+}
+
+Vector2& Vector2::operator+=(Vector2 other) {
+ x += other.x;
+ y += other.y;
+ return *this;
+}
+
+Vector2 Vector2::operator-(Vector2 other) {
+ return { x - other.x, y - other.y };
+}
+
+Vector2 Vector2::operator*(float s) {
+ return { x * s, y * s };
+}
+
+Vector2 Vector2::operator/(float s) {
+ return { x / s, y / s };
+}
+
+float Vector2::dot(Vector2 other) {
+ return x * other.x + y * other.y;
+}
+
+float Vector2::length() {
+ return sqrtf(x * x + y * y);
+}
+
+Vector2 Vector2::normalize() {
+ float len = length();
+ float inverseLength = len == 0 ? 1.0 : 1.0 / len;
+
+ return { x * inverseLength, y * inverseLength };
+}
+
+Vector2 Vector2::negate() {
+ return { -x, -y };
+}
+
+Vector2 Vector2::getPerp() {
+ return { y, -x };
+}
+
+Vector2 Vector2::rotate(float angle) {
+ return {
+ x * cosf(angle) - y * sinf(angle),
+ x * sinf(angle) + y * cosf(angle)
+ };
+}
+
+Vector2 Vector2::rotateAround(float angle, const Vector2& other) {
+ Vector2 point = { x - other.x, y - other.y };
+ point = {
+ point.x * cosf(angle) - point.y * sinf(angle),
+ point.x * sinf(angle) + point.y * cosf(angle)
+ };
+ point = point + other;
+ return point;
+}
+
+void Vector2::printDebug(const char* name) {
+ printf("%s=Vector2(%f, %f)\n", name, x, y);
+}
+
+
+// ***************************************
+// Vector3
+Vector3::Vector3() { };
+
+Vector3::Vector3(float value) {
+ x = value;
+ y = value;
+ z = value;
+}
+
+Vector3::Vector3(float inX, float inY, float inZ) {
+ x = inX;
+ y = inY;
+ z = inZ;
+}
+
+float Vector3::length() {
+ return sqrtf(x * x + y * y + z * z);
+}
+
+float Vector3::dot(const Vector3& other) {
+ return x * other.x + y * other.y + z * other.z;
+}
+
+Vector3 Vector3::scale(float scalar) {
+ return {
+ x * scalar,
+ y * scalar,
+ z * scalar
+ };
+}
+
+Vector3 Vector3::add(const Vector3& other) {
+ return {
+ x + other.x,
+ y + other.y,
+ z + other.z
+ };
+}
+
+Vector3 Vector3::subtract(const Vector3& other) {
+ return {
+ x - other.x,
+ y - other.y,
+ z - other.z
+ };
+}
+
+Vector3 Vector3::negate() {
+ return {
+ -x,
+ -y,
+ -z
+ };
+}
+
+Vector3 Vector3::cross(const Vector3& other) {
+ return {
+ y * other.z - z * other.y,
+ z * other.x - x * other.z,
+ x * other.y - y * other.x
+ };
+}
+
+Vector3 Vector3::normalize() {
+ float len = 1.f / length();
+ return {
+ x * len,
+ y * len,
+ z * len
+ };
+}
+
+Vector3 Vector3::operator+(const Vector3& v2) {
+ return add(v2);
+}
+
+Vector3& Vector3::operator+=(Vector3 other) {
+ x += other.x;
+ y += other.y;
+ z += other.z;
+ return *this;
+}
+
+Vector3 Vector3::operator-(const Vector3& v2) {
+ return subtract(v2);
+}
+
+Vector3 Vector3::operator-() {
+ return negate();
+}
+
+Vector3 Vector3::operator*(float value) {
+ return scale(value);
+}
+
+Vector3 Vector3::operator/(const Vector3& v2) {
+ return {
+ x / v2.x,
+ y / v2.y,
+ z / v2.z
+ };
+}
+
+Vector3 Vector3::operator*(const Vector3& v2) {
+ return {
+ x * v2.x,
+ y * v2.y,
+ z * v2.z
+ };
+}
+
+float Vector3::operator [](int index) {
+ switch (index) {
+ case 0:
+ return x;
+ case 1:
+ return y;
+ case 2:
+ return z;
+ default:
+ return 0;
+ }
+}
+
+void Vector2::operator=(const Vector4& other) {
+ x = other.x;
+ y = other.y;
+}
+
+void Vector3::printDebug(const char* name) {
+ printf("%s=Vector3(%f, %f, %f)\n", name, x, y, z);
+}
+
+
+// ***************************************
+// Vector4
+Vector4::Vector4() { }
+
+Vector4::Vector4(float value) {
+ x = value;
+ y = value;
+ z = value;
+ w = value;
+}
+
+Vector4::Vector4(float inX, float inY, float inZ, float inW) {
+ x = inX;
+ y = inY;
+ z = inZ;
+ w = inW;
+}
+
+Vector4::Vector4(Vector2& v) {
+ x = v.x;
+ y = v.y;
+ z = 0;
+ w = 1;
+}
+
+Vector4::Vector4(Vector3& v) {
+ x = v.x;
+ y = v.y;
+ z = v.z;
+ w = 1;
+}
+
+Vector4 Vector4::fromColor(float r, float g, float b, float a) {
+ float scale = 1.f / 255.f;
+ return { r * scale, g * scale, b * scale, a * scale };
+}
+
+Vector4 Vector4::toNormalizedColor() {
+ return fromColor(x, y, z, w);
+}
+
+
+Vector3 Vector4::toVector3() {
+ return { x, y, z };
+}
+
+float Vector4::length() {
+ return sqrtf(x * x + y * y + z * z + w * w);
+}
+
+Vector4 Vector4::normalize() {
+ float len = length();
+ float inverseLength = len == 0 ? 1.0 : 1.0 / len;
+
+ return { x * inverseLength, y * inverseLength, z * inverseLength, w * inverseLength };
+}
+
+float Vector4::dot(const Vector4& other) {
+ return x * other.x + y * other.y + z * other.z + w * other.w;
+}
+
+Vector4 Vector4::scale(float scalar) {
+ return {
+ x * scalar,
+ y * scalar,
+ z * scalar,
+ w * scalar
+ };
+}
+
+Vector4 Vector4::add(const Vector4& other) {
+ return {
+ x + other.x,
+ y + other.y,
+ z + other.z,
+ w + other.w
+ };
+}
+
+Vector4 Vector4::subtract(const Vector4& other) {
+ return {
+ x - other.x,
+ y - other.y,
+ z - other.z,
+ w - other.w
+ };
+}
+
+Vector4 Vector4::negate() {
+ return { -x, -y, -z, -w };
+}
+
+Vector4 Vector4::cross(const Vector4& other) {
+ return {
+ y * other.z - z * other.y,
+ z * other.x - x * other.z,
+ x * other.y - y * other.x,
+ 1.f
+ };
+}
+
+Vector4 lerp(Vector4 start, Vector4 end, float t) {
+ return (end - start) * t + start;
+}
+
+void Vector4::operator=(const Vector2& v2) {
+ x = v2.x;
+ y = v2.y;
+ z = 0;
+ w = 1;
+}
+
+void Vector4::operator=(const Vector3& v2) {
+ x = v2.x;
+ y = v2.y;
+ z = v2.z;
+ w = 1;
+}
+
+Vector4 Vector4::operator+(const Vector4& v2) {
+ return add(v2);
+}
+
+Vector4 Vector4::operator-(const Vector4& v2) {
+ return subtract(v2);
+}
+
+Vector4 Vector4::operator-() {
+ return negate();
+}
+
+Vector4 Vector4::operator*(float value) {
+ return scale(value);
+}
+
+Vector4 Vector4::operator*(const Vector4& v2) {
+ return {
+ x * v2.x,
+ y * v2.y,
+ z * v2.z,
+ w * v2.w
+ };
+}
+
+float Vector4::operator[](int index) {
+ switch (index) {
+ case 0:
+ return x;
+ case 1:
+ return y;
+ case 2:
+ return z;
+ case 3:
+ return w;
+ default:
+ return 0;
+ }
+}
+
+void Vector4::printDebug(const char* name) {
+ printf("%s=Vector4(%f, %f, %f, %f)\n", name, x, y, z, w);
+}
+
+
+// ***************************************
+// Mat4x4
+Mat4x4 Mat4x4::copy() {
+ Mat4x4 result;
+ memcpy(result.m, m, sizeof(float) * 16);
+ return result;
+}
+
+Mat4x4 Mat4x4::scale(Vector3 v) {
+ Mat4x4 result = copy();
+ result.m[0] = result.m[0] * v.x;
+ result.m[5] = result.m[5] *v.y;
+ result.m[10] = result.m[10] * v.z;
+ return result;
+}
+
+Mat4x4 Mat4x4::translate(Vector3 v) {
+ Mat4x4 result = copy();
+ result.m[12] += v.x;
+ result.m[13] += v.y;
+ result.m[14] += v.z;
+ return result;
+}
+
+Mat4x4 Mat4x4::translateByVec2(Vector2 v) {
+ Mat4x4 result = copy();
+ result.m[12] += v.x;
+ result.m[13] += v.y;
+ return result;
+}
+
+Mat4x4 Mat4x4::rotate2D(float angle) {
+ Mat4x4 result = copy();
+ result.m[0] = cos(angle);
+ result.m[1] = -sin(angle);
+ result.m[4] = sin(angle);
+ result.m[5] = cos(angle);
+ return result;
+}
+
+Mat4x4 Mat4x4::getXRotationMatrix(float angleRadians) {
+ return {
+ { 1, 0, 0, 0,
+ 0, cos(angleRadians), -sin(angleRadians), 0,
+ 0, sin(angleRadians), cos(angleRadians), 0,
+ 0, 0, 0, 1 }
+ };
+}
+
+Mat4x4 Mat4x4::getYRotationMatrix(float angleRadians) {
+ return {
+ { cos(angleRadians), 0, sin(angleRadians), 0,
+ 0, 1, 0, 0,
+ -sin(angleRadians), 0, cos(angleRadians), 0,
+ 0, 0, 0, 1 }
+ };
+}
+
+Mat4x4 Mat4x4::getZRotationMatrix(float angleRadians) {
+ return {
+ { cos(angleRadians), -sin(angleRadians), 0, 0,
+ sin(angleRadians), cos(angleRadians), 0, 0,
+ 0, 0, 1, 0,
+ 0, 0, 0, 1 }
+ };
+}
+
+Mat4x4 Mat4x4::rotate(float xRadians, float yRadians, float zRadians) {
+ Mat4x4 result = copy();
+
+ Mat4x4 rotationMatrix;
+ if (xRadians != 0) {
+ rotationMatrix = getXRotationMatrix(xRadians);
+ result = result * rotationMatrix;
+ }
+
+ if (yRadians != 0) {
+ rotationMatrix = getYRotationMatrix(yRadians);
+ result = result * rotationMatrix;
+ }
+
+ if (zRadians != 0) {
+ rotationMatrix = getZRotationMatrix(zRadians);
+ result = result * rotationMatrix;
+ }
+
+ return result;
+}
+
+Vector2 Mat4x4::multByVec2(Vector2 v) {
+ Vector4 vec4 = { v.x, v.y, 0.0, 1.0 };
+ return {
+ vec4.x * m[0] + vec4.y * m[4] + vec4.z * m[8] + vec4.w * m[12],
+ vec4.x * m[1] + vec4.y * m[5] + vec4.z * m[9] + vec4.w * m[13]
+ };
+}
+
+Vector2 Mat4x4::operator*(Vector2 v) {
+ return multByVec2(v);
+}
+
+Mat4x4 Mat4x4::multMat4x4(const Mat4x4& other) {
+ Mat4x4 result;
+ for (int i = 0; i < 4; ++i) {
+ for (int j = 0; j < 4; ++j) {
+ int row = i * 4;
+ result.m[row + j] = m[row + 0] * other.m[0 + j] + m[row + 1] * other.m[4 + j] + m[row + 2] * other.m[8 + j] + m[row + 3] * other.m[12 + j];
+ }
+ }
+
+ return result;
+}
+
+Mat4x4 Mat4x4::operator*(const Mat4x4& other) {
+ return multMat4x4(other);
+}
+
+Mat4x4 Mat4x4::getOrthographicMatrix(float left, float right, float bottom, float top) {
+ Mat4x4 result;
+ result.m[0] = 2.0 / (right - left);
+ result.m[5] = 2.0 / (top - bottom);
+ result.m[10] = 1.0;
+ result.m[12] = -(right + left) / (right - left);
+ result.m[13] = -(top + bottom) / (top - bottom);
+ return result;
+}
+
+Mat4x4 Mat4x4::inverse() {
+ Mat4x4 inv;
+
+ inv.m[0] = m[5] * m[10] * m[15] - m[5] * m[11] * m[14] - m[9] * m[6] * m[15] + m[9] * m[7] * m[14] + m[13] * m[6] * m[11] - m[13] * m[7] * m[10];
+ inv.m[4] = -m[4] * m[10] * m[15] + m[4] * m[11] * m[14] + m[8] * m[6] * m[15] - m[8] * m[7] * m[14] - m[12] * m[6] * m[11] + m[12] * m[7] * m[10];
+ inv.m[8] = m[4] * m[9] * m[15] - m[4] * m[11] * m[13] - m[8] * m[5] * m[15] + m[8] * m[7] * m[13] + m[12] * m[5] * m[11] - m[12] * m[7] * m[9];
+ inv.m[12] = -m[4] * m[9] * m[14] + m[4] * m[10] * m[13] + m[8] * m[5] * m[14] - m[8] * m[6] * m[13] - m[12] * m[5] * m[10] + m[12] * m[6] * m[9];
+ inv.m[1] = -m[1] * m[10] * m[15] + m[1] * m[11] * m[14] + m[9] * m[2] * m[15] - m[9] * m[3] * m[14] - m[13] * m[2] * m[11] + m[13] * m[3] * m[10];
+ inv.m[5] = m[0] * m[10] * m[15] - m[0] * m[11] * m[14] - m[8] * m[2] * m[15] + m[8] * m[3] * m[14] + m[12] * m[2] * m[11] - m[12] * m[3] * m[10];
+ inv.m[9] = -m[0] * m[9] * m[15] + m[0] * m[11] * m[13] + m[8] * m[1] * m[15] - m[8] * m[3] * m[13] - m[12] * m[1] * m[11] + m[12] * m[3] * m[9];
+ inv.m[13] = m[0] * m[9] * m[14] - m[0] * m[10] * m[13] - m[8] * m[1] * m[14] + m[8] * m[2] * m[13] + m[12] * m[1] * m[10] - m[12] * m[2] * m[9];
+ inv.m[2] = m[1] * m[6] * m[15] - m[1] * m[7] * m[14] - m[5] * m[2] * m[15] + m[5] * m[3] * m[14] + m[13] * m[2] * m[7] - m[13] * m[3] * m[6];
+ inv.m[6] = -m[0] * m[6] * m[15] + m[0] * m[7] * m[14] + m[4] * m[2] * m[15] - m[4] * m[3] * m[14] - m[12] * m[2] * m[7] + m[12] * m[3] * m[6];
+ inv.m[10] = m[0] * m[5] * m[15] - m[0] * m[7] * m[13] - m[4] * m[1] * m[15] + m[4] * m[3] * m[13] + m[12] * m[1] * m[7] - m[12] * m[3] * m[5];
+ inv.m[14] = -m[0] * m[5] * m[14] + m[0] * m[6] * m[13] + m[4] * m[1] * m[14] - m[4] * m[2] * m[13] - m[12] * m[1] * m[6] + m[12] * m[2] * m[5];
+ inv.m[3] = -m[1] * m[6] * m[11] + m[1] * m[7] * m[10] + m[5] * m[2] * m[11] - m[5] * m[3] * m[10] - m[9] * m[2] * m[7] + m[9] * m[3] * m[6];
+ inv.m[7] = m[0] * m[6] * m[11] - m[0] * m[7] * m[10] - m[4] * m[2] * m[11] + m[4] * m[3] * m[10] + m[8] * m[2] * m[7] - m[8] * m[3] * m[6];
+ inv.m[11] = -m[0] * m[5] * m[11] + m[0] * m[7] * m[9] + m[4] * m[1] * m[11] - m[4] * m[3] * m[9] - m[8] * m[1] * m[7] + m[8] * m[3] * m[5];
+ inv.m[15] = m[0] * m[5] * m[10] - m[0] * m[6] * m[9] - m[4] * m[1] * m[10] + m[4] * m[2] * m[9] + m[8] * m[1] * m[6] - m[8] * m[2] * m[5];
+
+ float det = m[0] * inv.m[0] + m[1] * inv.m[4] + m[2] * inv.m[8] + m[3] * inv.m[12];
+
+ if (det == 0)
+ return Mat4x4();
+
+ det = 1.f / det;
+
+ for (int i = 0; i < 16; i++)
+ inv.m[i] = inv.m[i] * det;
+
+ return inv;
+}
+
+Mat4x4 Mat4x4::getPerspectiveProjection(float near, float far, float fieldOfViewRadians, float aspectRatio) {
+ float halfFieldOfView = fieldOfViewRadians * 0.5f;
+ float top = tan(halfFieldOfView) * near;
+ float bottom = -top;
+ float right = top * aspectRatio;
+ float left = -right;
+
+ return {
+ { (2 * near) / (right - left), 0, 0, 0,
+ 0, (2 * near) / (top - bottom), 0, 0,
+ (right + left) / (right - left), (top + bottom) / (top - bottom), -(far + near) / (far - near), -1,
+ 0, 0, (-2 * far * near) / (far - near), 0 }
+ };
+}
+
+void Mat4x4::print() {
+ printf("[ ");
+ for (int idx = 0; idx < 16; idx++) {
+ printf("%f, ", m[idx]);
+ }
+ printf(" ]\n");
+}
+
+// See https://stackoverflow.com/questions/349050/calculating-a-lookat-matrix for why the dot product is in the bottom
+Mat4x4 Mat4x4::getLookAt(Vector3 eye,Vector3 pointToLookAt, Vector3 up) {
+ Vector3 zAxis = (pointToLookAt - eye).normalize();
+ Vector3 xAxis = zAxis.cross(up).normalize();
+ Vector3 yAxis = xAxis.cross(zAxis).normalize();
+
+ return {
+ { xAxis.x, yAxis.x, -zAxis.x, 0,
+ xAxis.y, yAxis.y, -zAxis.y, 0,
+ xAxis.z, yAxis.z, -zAxis.z, 0,
+ -xAxis.dot(eye), -yAxis.dot(eye), zAxis.dot(eye), 1 }
+ };
+}
+
+// ***************************************
+// Quaternion
+Quaternion::Quaternion() { };
+
+Quaternion::Quaternion(float inW, float inX, float inY, float inZ) {
+ w = inW;
+ x = inX;
+ y = inY;
+ z = inZ;
+}
+
+float Quaternion::operator [](int index) {
+ switch (index) {
+ case 0:
+ return x;
+ case 1:
+ return y;
+ case 2:
+ return z;
+ case 3:
+ return w;
+ default:
+ return 0;
+ }
+}
+
+Quaternion Quaternion::operator*(const Quaternion& other) const {
+ return {
+ w * other.w - x * other.x - y * other.y - z * other.z, // w
+ w * other.x + x * other.w + y * other.z - z * other.y, // i
+ w * other.y - x * other.z + y * other.w + z * other.x, // j
+ w * other.z + x * other.y - y * other.x + z * other.w // k
+ };
+}
+
+Quaternion Quaternion::operator*(const float& scale) const {
+ return {
+ w * scale,
+ x * scale,
+ y * scale,
+ z * scale
+ };
+}
+
+Quaternion Quaternion::operator+(const Quaternion& other) const {
+ return {
+ w + other.w,
+ x + other.x,
+ y + other.y,
+ z + other.z
+ };
+}
+
+Quaternion Quaternion::operator-(const Quaternion& other) const {
+ return {
+ w - other.w,
+ x - other.x,
+ y - other.y,
+ z - other.z
+ };
+}
+
+const float DOT_THRESHOLD = 0.9999f;
+
+// Using a slerp here, mostly taken from here: http://number-none.com/product/Understanding%20Slerp,%20Then%20Not%20Using%20It/.
+// As JBlow says, it's expensive as heck. I will address this if it becomes a problem.
+Quaternion Quaternion::interpolate(const Quaternion& other, const float factor) {
+ Quaternion newOther = other;
+ float dotProduct = dot(other);
+
+ if (dotProduct < 0) {
+ newOther = other * -1;
+ dotProduct *= -1;
+ }
+
+ if (dotProduct > DOT_THRESHOLD) {
+ return (*this + ((newOther - *this) * factor)).normalize();
+ }
+
+ float thetaZero = acos(dotProduct); // angle between input vectors
+ float theta = thetaZero * factor;
+
+ Quaternion v2 = (newOther - (*this * dotProduct)).normalize();
+
+ return (*this * cos(theta)) + (v2 * sin(theta));
+}
+
+float Quaternion::length() const {
+ return sqrtf(x * x + y * y + z * z + w * w);
+}
+
+Quaternion Quaternion::normalize() const {
+ float l = length();
+ return {
+ w / l,
+ x / l,
+ y / l,
+ z / l,
+ };
+}
+
+/*Mat4x4 Quaternion::toMatrix() const {
+ return {
+ {
+ 1 - 2 * (y * y - z * z),
+ 2 * (x * y - z * w),
+ 2 * (x * z + w * y),
+ 0,
+
+ 2 * (x * y + w * z),
+ 1 - 2 * (x * x - z * z),
+ 2 * (y * z - w * x),
+ 0,
+
+ 2 * (x * z - w * y),
+ 2 * (y * z + w * x),
+ 1 - 2 * (x * x - y * y),
+ 0,
+
+ 0,
+ 0,
+ 0,
+ 1
+ }
+ };
+}*/
+
+Mat4x4 Quaternion::toMatrix() const {
+ return {
+ {
+ 1 - 2 * (y * y + z * z),
+ 2 * (x * y + z * w),
+ 2 * (x * z - y * w),
+ 0,
+ 2 * (x * y - w * z),
+ 1 - 2 * (x * x + z * z),
+ 2 * (y * z + w * x),
+ 0,
+ 2 * (x * z + w * y),
+ 2 * (y * z - w * x),
+ 1 - 2 * (x * x + y * y),
+ 0,
+ 0,
+ 0,
+ 0,
+ 1
+ }
+ };
+}
+
+float Quaternion::dot(const Quaternion& other) const {
+ return w * other.w + x * other.x + y * other.y + z * other.z;
+}
+
+Quaternion quaternionFromRotation(Vector3 axis, float angleRadians) {
+ float halfAngleRadians = angleRadians / 2.f;
+ float cosHalfAngRad = cosf(halfAngleRadians);
+ float sinHalfAngRad = sinf(halfAngleRadians);
+
+ return {
+ cosHalfAngRad,
+ axis.x * sinHalfAngRad,
+ axis.y * sinHalfAngRad,
+ axis.z * sinHalfAngRad
+ };
+}
+
+Quaternion quaternionFromEulerAngle(float yaw, float pitch, float roll) {
+ float cy = cosf(yaw * 0.5f);
+ float sy = sinf(yaw * 0.5f);
+ float cp = cosf(pitch * 0.5f);
+ float sp = sinf(pitch * 0.5f);
+ float cr = cosf(roll * 0.5f);
+ float sr = sinf(roll * 0.5f);
+
+ return {
+ cr * cp * cy + sr * sp * sy,
+ sr * cp * cy - cr * sp * sy,
+ cr * sp * cy + sr * cp * sy,
+ cr * cp * sy - sr * sp * cy
+ };
+}
+
+void Quaternion::printDebug(const char* name) {
+ printf("%s=Quaternion(%f, %f, %f, %f)\n", name, x, y, z, w);
+}
diff --git a/themes/src/mathlib.h b/themes/src/mathlib.h
new file mode 100644
index 0000000..bb62f39
--- /dev/null
+++ b/themes/src/mathlib.h
@@ -0,0 +1,181 @@
+// mathlib.h
+//
+// Created by Matt Kosarek
+//
+// A file containing some common math functionality that I find
+// useful in my projects. I don't like that I don't know what's happening
+// in glm, so I wrote most of this myself. All mistakes are my own.
+//
+
+#pragma once
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <cmath>
+
+#define MAX(x, y) (((x) > (y)) ? (x) : (y))
+#define MIN(x, y) (((x) < (y)) ? (x) : (y))
+#define ABS(x) (x < 0 ? -x : x)
+#define SIGN(x) (x < 0 ? -1 : 1)
+#define PI 3.141592653589793238463
+#define E 2.71828182845904523536
+#define DEG_TO_RAD(x) (x * (PI / 180.f))
+#define RAD_TO_DEG(x) (x * (180.f / PI))
+
+struct Vector4;
+
+
+// -- Random
+inline float randomFloatBetween(float min, float max) {
+ float random = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
+ return (max - min) * random + min;
+}
+
+inline int randomIntBetween(int min, int max) {
+ return static_cast<int>(randomFloatBetween(min, max));
+}
+
+struct Vector2 {
+ float x = 0;
+ float y = 0;
+
+ Vector2();
+ Vector2(float inX, float inY);
+ Vector2 operator+(Vector2 other);
+ Vector2& operator+=(Vector2 other);
+ Vector2 operator-(Vector2 other);
+ Vector2 operator*(float s);
+ Vector2 operator/(float s);
+ void operator=(const Vector4& other);
+ float dot(Vector2 other);
+ float length();
+ Vector2 normalize();
+ Vector2 negate();
+ Vector2 getPerp();
+ Vector2 rotate(float angle);
+ Vector2 rotateAround(float angle, const Vector2& other);
+ float determinant(Vector2 other);
+
+ void printDebug(const char* name);
+};
+
+struct Vector3 {
+ float x = 0.f;
+ float y = 0.f;
+ float z = 0.f;
+
+ Vector3();
+ Vector3(float value);
+ Vector3(float x, float y, float z);
+
+ float length();
+ float dot(const Vector3& other);
+ Vector3 scale(float scalar);
+ Vector3 add(const Vector3& other);
+ Vector3 subtract(const Vector3& other);
+ Vector3 negate();
+ Vector3 cross(const Vector3& other);
+ Vector3 normalize();
+
+ Vector3 operator+(const Vector3& v2);
+ Vector3& operator+=(Vector3 other);
+ Vector3 operator-(const Vector3& v2);
+ Vector3 operator-();
+ Vector3 operator*(float value);
+ Vector3 operator*(const Vector3& v2);
+ Vector3 operator/(const Vector3& v2);
+ float operator[](int index);
+
+ void printDebug(const char* name);
+};
+
+struct Vector4 {
+ float x = 0.f;
+ float y = 0.f;
+ float z = 0.f;
+ float w = 1.f;
+
+ Vector4();
+ Vector4(float value);
+ Vector4(float inX, float inY, float inZ, float inW);
+ Vector4 fromColor(float r, float g, float b, float a);
+ Vector4 toNormalizedColor();
+ Vector4(Vector2& v);
+ Vector4(Vector3& v);
+ Vector3 toVector3();
+
+ float length();
+ Vector4 normalize();
+ float dot(const Vector4& other);
+ Vector4 scale(float scalar);
+ Vector4 add(const Vector4& other);
+ Vector4 subtract(const Vector4& other);
+ Vector4 negate();
+ Vector4 cross(const Vector4& other);
+
+ void operator=(const Vector2& v2);
+ void operator=(const Vector3& v2);
+ Vector4 operator+(const Vector4& v2);
+ Vector4 operator-(const Vector4& v2);
+ Vector4 operator-();
+ Vector4 operator*(float value);
+ Vector4 operator*(const Vector4& v2);
+ float operator[](int index);
+
+ void printDebug(const char* name);
+};
+
+Vector4 lerp(Vector4 start, Vector4 end, float t);
+
+struct Mat4x4 {
+ float m[16] = {
+ 1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 1, 0,
+ 0, 0, 0, 1
+ };
+
+ Mat4x4 copy();
+ Mat4x4 scale(Vector3 v);
+ Mat4x4 translate(Vector3 v);
+ Mat4x4 translateByVec2(Vector2 v);
+ Mat4x4 rotate2D(float angle);
+ Mat4x4 getXRotationMatrix(float angleRadians);
+ Mat4x4 getYRotationMatrix(float angleRadians);
+ Mat4x4 getZRotationMatrix(float angleRadians);
+ Mat4x4 rotate(float xRadians, float yRadians, float zRadians);
+ Vector2 multByVec2(Vector2 v);
+ Vector2 operator*(Vector2 v);
+ Mat4x4 multMat4x4(const Mat4x4& other);
+ Mat4x4 operator*(const Mat4x4& other);
+ Mat4x4 getOrthographicMatrix(float left, float right, float bottom, float top);
+ Mat4x4 inverse();
+ Mat4x4 getPerspectiveProjection(float near, float far, float fieldOfViewRadians, float aspectRatio);
+ Mat4x4 getLookAt(Vector3 eye,Vector3 pointToLookAt, Vector3 up);
+ void print();
+};
+
+struct Quaternion {
+ float w = 1;
+ float x = 0;
+ float y = 0;
+ float z = 0;
+
+ Quaternion();
+ Quaternion(float inW, float inX, float inY, float inZ);
+ float operator [](int index);
+ Quaternion operator*(const Quaternion& other) const;
+ Quaternion operator*(const float& scale) const;
+ Quaternion operator+(const Quaternion& other) const;
+ Quaternion operator-(const Quaternion& other) const;
+ Quaternion interpolate(const Quaternion& other, const float factor);
+ Mat4x4 toMatrix() const;
+ Quaternion normalize() const;
+ float length() const;
+ float dot(const Quaternion& other) const;
+
+ void printDebug(const char* name);
+};
+
+Quaternion quaternionFromRotation(Vector3 axis, float angleRadians);
+Quaternion quaternionFromEulerAngle(float yaw, float pitch, float roll);
diff --git a/themes/src/types.h b/themes/src/types.h
new file mode 100644
index 0000000..ae8aa4e
--- /dev/null
+++ b/themes/src/types.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include <stdint.h>
+
+typedef int8_t i8;
+typedef int16_t i16;
+typedef int32_t i32;
+typedef int64_t i64;
+
+typedef uint8_t u8;
+typedef uint16_t u16;
+typedef uint32_t u32;
+typedef unsigned long u64;
+
+typedef float f32;
+typedef double f64;