From bf4b3a5c35152c1292757134123b3363d0f81bf6 Mon Sep 17 00:00:00 2001 From: Matt Kosarek Date: Mon, 29 Dec 2025 09:34:00 -0500 Subject: Renamed PascalCase files to snake_case --- themes/src/Logger.cpp | 123 --------------- themes/src/Logger.h | 43 ------ themes/src/MainLoop.cpp | 31 ---- themes/src/MainLoop.h | 29 ---- themes/src/Renderer2d.cpp | 126 --------------- themes/src/Renderer2d.h | 61 -------- themes/src/Renderer3d.cpp | 239 ----------------------------- themes/src/Renderer3d.h | 56 ------- themes/src/Shader.cpp | 61 -------- themes/src/Shader.h | 64 -------- themes/src/WebglContext.cpp | 46 ------ themes/src/WebglContext.h | 18 --- themes/src/autumn/AutumnTheme.cpp | 73 --------- themes/src/autumn/AutumnTheme.hpp | 33 ---- themes/src/autumn/LeafParticleRender.cpp | 166 -------------------- themes/src/autumn/LeafParticleRender.h | 58 ------- themes/src/autumn/TreeShape.cpp | 214 -------------------------- themes/src/autumn/TreeShape.h | 74 --------- themes/src/autumn/autumn_theme.cpp | 73 +++++++++ themes/src/autumn/autumn_theme.hpp | 33 ++++ themes/src/autumn/leaf_particle_render.cpp | 166 ++++++++++++++++++++ themes/src/autumn/leaf_particle_render.h | 58 +++++++ themes/src/autumn/tree_shape.cpp | 214 ++++++++++++++++++++++++++ themes/src/autumn/tree_shape.h | 74 +++++++++ themes/src/list.h | 2 +- themes/src/logger.cpp | 123 +++++++++++++++ themes/src/logger.h | 43 ++++++ themes/src/main.cpp | 14 +- themes/src/main_loop.cpp | 31 ++++ themes/src/main_loop.h | 29 ++++ themes/src/renderer_2d.cpp | 126 +++++++++++++++ themes/src/renderer_2d.h | 61 ++++++++ themes/src/renderer_3d.cpp | 239 +++++++++++++++++++++++++++++ themes/src/renderer_3d.h | 56 +++++++ themes/src/shader.cpp | 61 ++++++++ themes/src/shader.h | 64 ++++++++ themes/src/shapes_2d.cpp | 2 +- themes/src/shapes_2d.h | 2 +- themes/src/spring/GrassRenderer.cpp | 29 ---- themes/src/spring/GrassRenderer.hpp | 33 ---- themes/src/spring/SpringTheme.cpp | 208 ------------------------- themes/src/spring/SpringTheme.hpp | 45 ------ themes/src/spring/grass_renderer.cpp | 29 ++++ themes/src/spring/grass_renderer.hpp | 33 ++++ themes/src/spring/spring_theme.cpp | 208 +++++++++++++++++++++++++ themes/src/spring/spring_theme.hpp | 45 ++++++ themes/src/summer/SummerTheme.cpp | 82 ---------- themes/src/summer/SummerTheme.h | 29 ---- themes/src/summer/summer_theme.cpp | 82 ++++++++++ themes/src/summer/summer_theme.h | 29 ++++ themes/src/webgl_context.cpp | 46 ++++++ themes/src/webgl_context.h | 18 +++ themes/src/winter/Snowflake.cpp | 189 ----------------------- themes/src/winter/Snowflake.h | 48 ------ themes/src/winter/Windfield.cpp | 28 ---- themes/src/winter/Windfield.hpp | 39 ----- themes/src/winter/WinterTheme.cpp | 32 ---- themes/src/winter/WinterTheme.hpp | 25 --- themes/src/winter/snowflake.cpp | 189 +++++++++++++++++++++++ themes/src/winter/snowflake.h | 48 ++++++ themes/src/winter/windfield.cpp | 28 ++++ themes/src/winter/windfield.hpp | 39 +++++ themes/src/winter/winter_theme.cpp | 32 ++++ themes/src/winter/winter_theme.hpp | 25 +++ 64 files changed, 2312 insertions(+), 2312 deletions(-) delete mode 100644 themes/src/Logger.cpp delete mode 100644 themes/src/Logger.h delete mode 100644 themes/src/MainLoop.cpp delete mode 100644 themes/src/MainLoop.h delete mode 100644 themes/src/Renderer2d.cpp delete mode 100644 themes/src/Renderer2d.h delete mode 100644 themes/src/Renderer3d.cpp delete mode 100644 themes/src/Renderer3d.h delete mode 100644 themes/src/Shader.cpp delete mode 100644 themes/src/Shader.h delete mode 100644 themes/src/WebglContext.cpp delete mode 100644 themes/src/WebglContext.h delete mode 100644 themes/src/autumn/AutumnTheme.cpp delete mode 100644 themes/src/autumn/AutumnTheme.hpp delete mode 100644 themes/src/autumn/LeafParticleRender.cpp delete mode 100644 themes/src/autumn/LeafParticleRender.h delete mode 100644 themes/src/autumn/TreeShape.cpp delete mode 100644 themes/src/autumn/TreeShape.h create mode 100644 themes/src/autumn/autumn_theme.cpp create mode 100644 themes/src/autumn/autumn_theme.hpp create mode 100644 themes/src/autumn/leaf_particle_render.cpp create mode 100644 themes/src/autumn/leaf_particle_render.h create mode 100644 themes/src/autumn/tree_shape.cpp create mode 100644 themes/src/autumn/tree_shape.h create mode 100644 themes/src/logger.cpp create mode 100644 themes/src/logger.h create mode 100644 themes/src/main_loop.cpp create mode 100644 themes/src/main_loop.h create mode 100644 themes/src/renderer_2d.cpp create mode 100644 themes/src/renderer_2d.h create mode 100644 themes/src/renderer_3d.cpp create mode 100644 themes/src/renderer_3d.h create mode 100644 themes/src/shader.cpp create mode 100644 themes/src/shader.h delete mode 100644 themes/src/spring/GrassRenderer.cpp delete mode 100644 themes/src/spring/GrassRenderer.hpp delete mode 100644 themes/src/spring/SpringTheme.cpp delete mode 100644 themes/src/spring/SpringTheme.hpp create mode 100644 themes/src/spring/grass_renderer.cpp create mode 100644 themes/src/spring/grass_renderer.hpp create mode 100644 themes/src/spring/spring_theme.cpp create mode 100644 themes/src/spring/spring_theme.hpp delete mode 100644 themes/src/summer/SummerTheme.cpp delete mode 100644 themes/src/summer/SummerTheme.h create mode 100644 themes/src/summer/summer_theme.cpp create mode 100644 themes/src/summer/summer_theme.h create mode 100644 themes/src/webgl_context.cpp create mode 100644 themes/src/webgl_context.h delete mode 100644 themes/src/winter/Snowflake.cpp delete mode 100644 themes/src/winter/Snowflake.h delete mode 100644 themes/src/winter/Windfield.cpp delete mode 100644 themes/src/winter/Windfield.hpp delete mode 100644 themes/src/winter/WinterTheme.cpp delete mode 100644 themes/src/winter/WinterTheme.hpp create mode 100644 themes/src/winter/snowflake.cpp create mode 100644 themes/src/winter/snowflake.h create mode 100644 themes/src/winter/windfield.cpp create mode 100644 themes/src/winter/windfield.hpp create mode 100644 themes/src/winter/winter_theme.cpp create mode 100644 themes/src/winter/winter_theme.hpp (limited to 'themes/src') diff --git a/themes/src/Logger.cpp b/themes/src/Logger.cpp deleted file mode 100644 index 1068d88..0000000 --- a/themes/src/Logger.cpp +++ /dev/null @@ -1,123 +0,0 @@ -#include "Logger.h" -#include -#include -#include - -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 deleted file mode 100644 index 7596b6f..0000000 --- a/themes/src/Logger.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef LOGGER_H -#define LOGGER_H - -#include - -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 deleted file mode 100644 index 09aa643..0000000 --- a/themes/src/MainLoop.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "MainLoop.h" -#include -#include - -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(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 deleted file mode 100644 index 07520a2..0000000 --- a/themes/src/MainLoop.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -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; - } -}; diff --git a/themes/src/Renderer2d.cpp b/themes/src/Renderer2d.cpp deleted file mode 100644 index f1d78e3..0000000 --- a/themes/src/Renderer2d.cpp +++ /dev/null @@ -1,126 +0,0 @@ -#include "Renderer2d.h" -#include "Shader.h" -#include "WebglContext.h" -#include "mathlib.h" -#include -#include "shaders/renderer2d_vert.h" -#include "shaders/renderer2d_frag.h" - -// Note: In the 'transform' attribute, the transform.x is the scale, -// transform.y is the rotation, and transform.zw is the translation. - -void Renderer2d::load(WebglContext* inContext, const char* inVertexShader, const char* inFragmentShader) { - auto vertexShader = inVertexShader ? inVertexShader : shader_renderer2d_vert; - auto fragmentShader = inFragmentShader ? inFragmentShader : shader_renderer2d_frag; - context = inContext; - printf("Compiling Renderer2d shader...\n"); - shader = loadShader(vertexShader, fragmentShader); - - 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); -} - -f32 Renderer2d::get_width() { - return context->width; -} - -f32 Renderer2d::get_height() { - return context->height; -} - - -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 deleted file mode 100644 index 7432894..0000000 --- a/themes/src/Renderer2d.h +++ /dev/null @@ -1,61 +0,0 @@ -#pragma once - -#include "WebglContext.h" -#include "types.h" -#include "Shader.h" -#include "mathlib.h" - -struct WebglContext; - -/// Responsible for rendering Mesh2Ds -struct Renderer2d { - WebglContext* context = NULL; - Mat4x4 projection; - u32 shader; - Vector4 clearColor; - - struct { - i32 position; - i32 color; - - // TODO: vMatrix is not standard and does not belong here - i32 vMatrix; - } attributes; - - struct { - i32 projection; - i32 model; - } uniforms; - - /// Load with the provided context and shader programs. If the shaders are NULL, the default - /// shader is used - void load(WebglContext* context, const char* vertexShader = NULL, const char* fragmentShader = NULL); - void render(); - void unload(); - f32 get_width(); - f32 get_height(); -}; - -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 deleted file mode 100644 index 00315de..0000000 --- a/themes/src/Renderer3d.cpp +++ /dev/null @@ -1,239 +0,0 @@ -#include "Renderer3d.h" -#include "Shader.h" -#include "list.h" -#include "mathlib.h" -#include "WebglContext.h" -#include "Logger.h" -#include - -// Note: In the 'transform' attribute, the transform.x is the scale, -// transform.y is the rotation, and transform.zw is the translatiob. -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, const char* vertexShader, const char* fragmentShader) { - context = inContext; - printf("Compiling Renderer3d 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(context->width) / static_cast(context->height)); - view = Mat4x4().getLookAt({ 0, 25, 75 }, { 0, 15, 0 }, { 0, 1, 0 }); - - logger_info("Renderer3d 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 deleted file mode 100644 index 5b2c8c8..0000000 --- a/themes/src/Renderer3d.h +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef RENDERER3D_H -#define RENDERER3D_H -#include "mathlib.h" -#include "list.h" -#include "types.h" -#include - -struct Renderer3d; - -struct Vertex3d { - Vector4 position; - Vector4 color; - Vector4 normal; -}; - -struct Mesh3d { - u32 vao; - u32 vbo; - u32 ebo; - matte::List vertices; - matte::List 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, const char* vertexShader, const char* fragmentShader); - 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 deleted file mode 100644 index 5f2b00e..0000000 --- a/themes/src/Shader.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include "Shader.h" -#include - -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 deleted file mode 100644 index bc81764..0000000 --- a/themes/src/Shader.h +++ /dev/null @@ -1,64 +0,0 @@ -#pragma once - -#include -#include -#include -#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/WebglContext.cpp b/themes/src/WebglContext.cpp deleted file mode 100644 index df49c2d..0000000 --- a/themes/src/WebglContext.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "WebglContext.h" -#include - - -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(inWidth); - context->height = static_cast(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(inWidth); - height = static_cast(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 deleted file mode 100644 index 1956092..0000000 --- a/themes/src/WebglContext.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once -#include "types.h" -#include -#include -#include -#include -#include - -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/autumn/AutumnTheme.cpp b/themes/src/autumn/AutumnTheme.cpp deleted file mode 100644 index 4b7a2e2..0000000 --- a/themes/src/autumn/AutumnTheme.cpp +++ /dev/null @@ -1,73 +0,0 @@ -#include "AutumnTheme.hpp" -#include "../shapes_2d.h" -#include - -namespace -{ - const int NUM_HILLS = 3; -} - -AutumnTheme::AutumnTheme(WebglContext* context) -{ - renderer.load(context); - load(); -} - -AutumnTheme::~AutumnTheme() -{ - unload(); -} - -void AutumnTheme::load() { - renderer.clearColor = Vector4(252, 210, 153, 255).toNormalizedColor(); - auto lr = tree.load(&renderer); - leafParticles.load(&renderer, &lr); - background = new RectangularGradient( - renderer, - Vector4{135, 206, 235, 255}.toNormalizedColor(), - Vector4(252, 210, 153, 255).toNormalizedColor(), - renderer.get_width(), - renderer.get_height(), - {0, 0}); - - - background_hill = new Circleish( - renderer, - 1000, - Vector4(137, 129, 33, 255).toNormalizedColor(), - 100, - 0, - 50); - background_hill->mesh.model = background_hill->mesh.model.translateByVec2({1200, -700}); - - tree_hill = new Circleish( - renderer, - 500, - Vector4{ 76, 75, 22, 255 }.toNormalizedColor(), - 100, - 0, - 50); - tree_hill->mesh.model = tree_hill->mesh.model.translateByVec2(Vector2(300, -290)); -} - -void AutumnTheme::update(f32 dtSeconds) { - tree.update(dtSeconds); - leafParticles.update(dtSeconds); -} - -void AutumnTheme::render() { - renderer.render(); - background->render(); - background_hill->render(); - tree.render(&renderer); - tree_hill->render(); - leafParticles.render(&renderer); -} - -void AutumnTheme::unload() { - tree.unload(); - leafParticles.unload(); - delete background; - delete background_hill; - delete tree_hill; -} diff --git a/themes/src/autumn/AutumnTheme.hpp b/themes/src/autumn/AutumnTheme.hpp deleted file mode 100644 index e3f5748..0000000 --- a/themes/src/autumn/AutumnTheme.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef AUTUMN_THEME_HPP -#define AUTUMN_THEME_HPP - -#include "TreeShape.h" -#include "LeafParticleRender.h" -#include "../types.h" -#include "../theme.h" -#include "../Renderer2d.h" -#include -#include - -class RectangularGradient; -class Circleish; - -class AutumnTheme : public Theme { -public: - AutumnTheme(WebglContext*); - ~AutumnTheme(); - TreeShape tree; - LeafParticleRender leafParticles; - RectangularGradient* background; - Circleish* tree_hill; - Circleish* background_hill; - - void load(); - void update(f32 dtSeconds); - void render(); - void unload(); -private: - Renderer2d renderer; -}; - -#endif diff --git a/themes/src/autumn/LeafParticleRender.cpp b/themes/src/autumn/LeafParticleRender.cpp deleted file mode 100644 index fee3df2..0000000 --- a/themes/src/autumn/LeafParticleRender.cpp +++ /dev/null @@ -1,166 +0,0 @@ -#include "LeafParticleRender.h" -#include "../Renderer2d.h" -#include "../mathlib.h" -#include "TreeShape.h" -#include "../types.h" -#include - -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/autumn/LeafParticleRender.h b/themes/src/autumn/LeafParticleRender.h deleted file mode 100644 index f6efe1f..0000000 --- a/themes/src/autumn/LeafParticleRender.h +++ /dev/null @@ -1,58 +0,0 @@ -#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/autumn/TreeShape.cpp b/themes/src/autumn/TreeShape.cpp deleted file mode 100644 index 7c80929..0000000 --- a/themes/src/autumn/TreeShape.cpp +++ /dev/null @@ -1,214 +0,0 @@ -#include "TreeShape.h" -#include "../mathlib.h" -#include -#include -#include -#include - -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, 200.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(division) / static_cast(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/autumn/TreeShape.h b/themes/src/autumn/TreeShape.h deleted file mode 100644 index fc0d11e..0000000 --- a/themes/src/autumn/TreeShape.h +++ /dev/null @@ -1,74 +0,0 @@ -#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/autumn/autumn_theme.cpp b/themes/src/autumn/autumn_theme.cpp new file mode 100644 index 0000000..d88b265 --- /dev/null +++ b/themes/src/autumn/autumn_theme.cpp @@ -0,0 +1,73 @@ +#include "autumn_theme.hpp" +#include "../shapes_2d.h" +#include + +namespace +{ + const int NUM_HILLS = 3; +} + +AutumnTheme::AutumnTheme(WebglContext* context) +{ + renderer.load(context); + load(); +} + +AutumnTheme::~AutumnTheme() +{ + unload(); +} + +void AutumnTheme::load() { + renderer.clearColor = Vector4(252, 210, 153, 255).toNormalizedColor(); + auto lr = tree.load(&renderer); + leafParticles.load(&renderer, &lr); + background = new RectangularGradient( + renderer, + Vector4{135, 206, 235, 255}.toNormalizedColor(), + Vector4(252, 210, 153, 255).toNormalizedColor(), + renderer.get_width(), + renderer.get_height(), + {0, 0}); + + + background_hill = new Circleish( + renderer, + 1000, + Vector4(137, 129, 33, 255).toNormalizedColor(), + 100, + 0, + 50); + background_hill->mesh.model = background_hill->mesh.model.translateByVec2({1200, -700}); + + tree_hill = new Circleish( + renderer, + 500, + Vector4{ 76, 75, 22, 255 }.toNormalizedColor(), + 100, + 0, + 50); + tree_hill->mesh.model = tree_hill->mesh.model.translateByVec2(Vector2(300, -290)); +} + +void AutumnTheme::update(f32 dtSeconds) { + tree.update(dtSeconds); + leafParticles.update(dtSeconds); +} + +void AutumnTheme::render() { + renderer.render(); + background->render(); + background_hill->render(); + tree.render(&renderer); + tree_hill->render(); + leafParticles.render(&renderer); +} + +void AutumnTheme::unload() { + tree.unload(); + leafParticles.unload(); + delete background; + delete background_hill; + delete tree_hill; +} diff --git a/themes/src/autumn/autumn_theme.hpp b/themes/src/autumn/autumn_theme.hpp new file mode 100644 index 0000000..b61c0f3 --- /dev/null +++ b/themes/src/autumn/autumn_theme.hpp @@ -0,0 +1,33 @@ +#ifndef AUTUMN_THEME_HPP +#define AUTUMN_THEME_HPP + +#include "tree_shape.h" +#include "leaf_particle_render.h" +#include "../types.h" +#include "../theme.h" +#include "../renderer_2d.h" +#include +#include + +class RectangularGradient; +class Circleish; + +class AutumnTheme : public Theme { +public: + AutumnTheme(WebglContext*); + ~AutumnTheme(); + TreeShape tree; + LeafParticleRender leafParticles; + RectangularGradient* background; + Circleish* tree_hill; + Circleish* background_hill; + + void load(); + void update(f32 dtSeconds); + void render(); + void unload(); +private: + Renderer2d renderer; +}; + +#endif diff --git a/themes/src/autumn/leaf_particle_render.cpp b/themes/src/autumn/leaf_particle_render.cpp new file mode 100644 index 0000000..569bb2d --- /dev/null +++ b/themes/src/autumn/leaf_particle_render.cpp @@ -0,0 +1,166 @@ +#include "leaf_particle_render.h" +#include "../renderer_2d.h" +#include "../mathlib.h" +#include "tree_shape.h" +#include "../types.h" +#include + +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/autumn/leaf_particle_render.h b/themes/src/autumn/leaf_particle_render.h new file mode 100644 index 0000000..1209e1b --- /dev/null +++ b/themes/src/autumn/leaf_particle_render.h @@ -0,0 +1,58 @@ +#include "../renderer_2d.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/autumn/tree_shape.cpp b/themes/src/autumn/tree_shape.cpp new file mode 100644 index 0000000..622751b --- /dev/null +++ b/themes/src/autumn/tree_shape.cpp @@ -0,0 +1,214 @@ +#include "tree_shape.h" +#include "../mathlib.h" +#include +#include +#include +#include + +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, 200.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(division) / static_cast(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/autumn/tree_shape.h b/themes/src/autumn/tree_shape.h new file mode 100644 index 0000000..0d18415 --- /dev/null +++ b/themes/src/autumn/tree_shape.h @@ -0,0 +1,74 @@ +#include "../renderer_2d.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/list.h b/themes/src/list.h index 25b236a..9b6a719 100644 --- a/themes/src/list.h +++ b/themes/src/list.h @@ -1,7 +1,7 @@ #pragma once #include #include -#include "Logger.h" +#include "logger.h" #define FOREACH(list) \ for (i32 idx = 0; idx < list.numElements; idx++) \ diff --git a/themes/src/logger.cpp b/themes/src/logger.cpp new file mode 100644 index 0000000..bead282 --- /dev/null +++ b/themes/src/logger.cpp @@ -0,0 +1,123 @@ +#include "logger.h" +#include +#include +#include + +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 + +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/main.cpp b/themes/src/main.cpp index d9cfc01..60e6aed 100644 --- a/themes/src/main.cpp +++ b/themes/src/main.cpp @@ -1,13 +1,13 @@ -#include "WebglContext.h" -#include "MainLoop.h" -#include "Renderer2d.h" +#include "webgl_context.h" +#include "main_loop.h" +#include "renderer_2d.h" #include "mathlib.h" #include "theme.h" #include "types.h" -#include "summer/SummerTheme.h" -#include "autumn/AutumnTheme.hpp" -#include "spring/SpringTheme.hpp" -#include "winter/WinterTheme.hpp" +#include "summer/summer_theme.h" +#include "autumn/autumn_theme.hpp" +#include "spring/spring_theme.hpp" +#include "winter/winter_theme.hpp" #include #include diff --git a/themes/src/main_loop.cpp b/themes/src/main_loop.cpp new file mode 100644 index 0000000..e5397ca --- /dev/null +++ b/themes/src/main_loop.cpp @@ -0,0 +1,31 @@ +#include "main_loop.h" +#include +#include + +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(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/main_loop.h b/themes/src/main_loop.h new file mode 100644 index 0000000..07520a2 --- /dev/null +++ b/themes/src/main_loop.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include +#include + +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; + } +}; diff --git a/themes/src/renderer_2d.cpp b/themes/src/renderer_2d.cpp new file mode 100644 index 0000000..7200669 --- /dev/null +++ b/themes/src/renderer_2d.cpp @@ -0,0 +1,126 @@ +#include "renderer_2d.h" +#include "shader.h" +#include "webgl_context.h" +#include "mathlib.h" +#include +#include "shaders/renderer2d_vert.h" +#include "shaders/renderer2d_frag.h" + +// Note: In the 'transform' attribute, the transform.x is the scale, +// transform.y is the rotation, and transform.zw is the translation. + +void Renderer2d::load(WebglContext* inContext, const char* inVertexShader, const char* inFragmentShader) { + auto vertexShader = inVertexShader ? inVertexShader : shader_renderer2d_vert; + auto fragmentShader = inFragmentShader ? inFragmentShader : shader_renderer2d_frag; + context = inContext; + printf("Compiling Renderer2d shader...\n"); + shader = loadShader(vertexShader, fragmentShader); + + 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); +} + +f32 Renderer2d::get_width() { + return context->width; +} + +f32 Renderer2d::get_height() { + return context->height; +} + + +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/renderer_2d.h b/themes/src/renderer_2d.h new file mode 100644 index 0000000..d572533 --- /dev/null +++ b/themes/src/renderer_2d.h @@ -0,0 +1,61 @@ +#pragma once + +#include "webgl_context.h" +#include "types.h" +#include "shader.h" +#include "mathlib.h" + +struct WebglContext; + +/// Responsible for rendering Mesh2Ds +struct Renderer2d { + WebglContext* context = NULL; + Mat4x4 projection; + u32 shader; + Vector4 clearColor; + + struct { + i32 position; + i32 color; + + // TODO: vMatrix is not standard and does not belong here + i32 vMatrix; + } attributes; + + struct { + i32 projection; + i32 model; + } uniforms; + + /// Load with the provided context and shader programs. If the shaders are NULL, the default + /// shader is used + void load(WebglContext* context, const char* vertexShader = NULL, const char* fragmentShader = NULL); + void render(); + void unload(); + f32 get_width(); + f32 get_height(); +}; + +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/renderer_3d.cpp b/themes/src/renderer_3d.cpp new file mode 100644 index 0000000..cc79940 --- /dev/null +++ b/themes/src/renderer_3d.cpp @@ -0,0 +1,239 @@ +#include "renderer_3d.h" +#include "shader.h" +#include "list.h" +#include "mathlib.h" +#include "webgl_context.h" +#include "logger.h" +#include + +// Note: In the 'transform' attribute, the transform.x is the scale, +// transform.y is the rotation, and transform.zw is the translatiob. +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, const char* vertexShader, const char* fragmentShader) { + context = inContext; + printf("Compiling Renderer3d 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(context->width) / static_cast(context->height)); + view = Mat4x4().getLookAt({ 0, 25, 75 }, { 0, 15, 0 }, { 0, 1, 0 }); + + logger_info("Renderer3d 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/renderer_3d.h b/themes/src/renderer_3d.h new file mode 100644 index 0000000..5b2c8c8 --- /dev/null +++ b/themes/src/renderer_3d.h @@ -0,0 +1,56 @@ +#ifndef RENDERER3D_H +#define RENDERER3D_H +#include "mathlib.h" +#include "list.h" +#include "types.h" +#include + +struct Renderer3d; + +struct Vertex3d { + Vector4 position; + Vector4 color; + Vector4 normal; +}; + +struct Mesh3d { + u32 vao; + u32 vbo; + u32 ebo; + matte::List vertices; + matte::List 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, const char* vertexShader, const char* fragmentShader); + 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..ed2cab5 --- /dev/null +++ b/themes/src/shader.cpp @@ -0,0 +1,61 @@ +#include "shader.h" +#include + +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 +#include +#include +#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/shapes_2d.cpp b/themes/src/shapes_2d.cpp index d5a29ed..e00c521 100644 --- a/themes/src/shapes_2d.cpp +++ b/themes/src/shapes_2d.cpp @@ -1,5 +1,5 @@ #include "shapes_2d.h" -#include "Renderer2d.h" +#include "renderer_2d.h" #include "mathlib.h" #include "list.h" diff --git a/themes/src/shapes_2d.h b/themes/src/shapes_2d.h index 8e08504..325d525 100644 --- a/themes/src/shapes_2d.h +++ b/themes/src/shapes_2d.h @@ -2,7 +2,7 @@ #define SHAPES_2D #include "mathlib.h" -#include "Renderer2d.h" +#include "renderer_2d.h" #include "types.h" class RectangularGradient diff --git a/themes/src/spring/GrassRenderer.cpp b/themes/src/spring/GrassRenderer.cpp deleted file mode 100644 index b69d111..0000000 --- a/themes/src/spring/GrassRenderer.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "GrassRenderer.hpp" -#include "Renderer3d.h" - -void GrassRenderer::load(GrassRendererLoadData params, Renderer3d* renderer) { - const f32 COLUMN_INCREMENT = GRASS_BLADES_PER_COL / params.area.x; - const f32 ROW_INCREMENT = GRASS_BLADES_PER_ROW / params.area.y; - for (i32 r = 0; r < GRASS_BLADES_PER_ROW; r++) { - i32 indexOffset = r * GRASS_BLADES_PER_ROW; - f32 y = ROW_INCREMENT * r; - for (i32 c = 0; c < GRASS_BLADES_PER_COL; c++) { - f32 x = COLUMN_INCREMENT * c; - i32 index = indexOffset + c; - grassBlades[index].position = Vector3(x, y, 0); - grassBlades[index].top_offset = Vector2(0, 0); - } - } -} - -void GrassRenderer::update(f32 seconds) { - -} - -void GrassRenderer::render(Renderer3d* renderer) { - -} - -void GrassRenderer::unload() { - -} diff --git a/themes/src/spring/GrassRenderer.hpp b/themes/src/spring/GrassRenderer.hpp deleted file mode 100644 index 8c96724..0000000 --- a/themes/src/spring/GrassRenderer.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef GRASS_RENDERER_HPP -#define GRASS_RENDERER_HPP - -#include "Renderer3d.h" -#include "mathlib.h" -#include "types.h" - -const i32 GRASS_BLADES_PER_ROW = 24; -const i32 GRASS_BLADES_PER_COL = 24; -const i32 NUM_GRASS_BLADES = GRASS_BLADES_PER_ROW * GRASS_BLADES_PER_COL; - -struct GrassRendererLoadData { - Vector2 origin = Vector2(0, 0); - Vector2 area = Vector2(480, 480); - f32 grassHeight = 12.f; -}; - -struct GrassUpdateData { - Vector3 position; - Vector2 top_offset; -}; - -struct GrassRenderer { - - GrassUpdateData grassBlades[NUM_GRASS_BLADES]; - - void load(GrassRendererLoadData params, Renderer3d* renderer); - void update(f32 dtSeconds); - void render(Renderer3d* renderer); - void unload(); -}; - -#endif diff --git a/themes/src/spring/SpringTheme.cpp b/themes/src/spring/SpringTheme.cpp deleted file mode 100644 index e39c138..0000000 --- a/themes/src/spring/SpringTheme.cpp +++ /dev/null @@ -1,208 +0,0 @@ -#include "SpringTheme.hpp" -#include "../Renderer3d.h" -#include "../shader_fetcher.hpp" -#include -#include - -void onBunnySuccess(emscripten_fetch_t *fetch) { - SpringTheme* springTheme = (SpringTheme*)fetch->userData; - springTheme->state = SpringThemeState::LoadedBunny; - printf("Finished downloading %llu bytes from URL %s.\n", fetch->numBytes, fetch->url); - const i32 len = fetch->numBytes; - springTheme->bunnyMesh = Mesh3d_fromObj(&springTheme->renderer, 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. -} - -inline void fetch_bunny(SpringTheme* theme) { - 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; - auto* bunny_fetch = emscripten_fetch(&attr, "themes/resources/bunny.obj"); - bunny_fetch->userData = theme; -} - -inline void on_shaders_loader(ShaderFetchResult* result) { - SpringTheme* theme = (SpringTheme*)result->user_data; - theme->renderer.load(theme->renderer.context, result->vertex.c_str(), result->fragment.c_str()); - theme->state = SpringThemeState::LoadedShader; - fetch_bunny(theme); -} - -SpringTheme::SpringTheme(WebglContext* context) -{ - load(context); -} - -SpringTheme::~SpringTheme() -{ - unload(); -} - -void SpringTheme::load(WebglContext* context) { - state = SpringThemeState::Loading; - renderer.context = context; - renderer.clearColor = Vector4(160, 231, 160, 255.f).toNormalizedColor(); - - fetch_shader( - { - "themes/src/_shaders/renderer3d.vert", - "themes/src/_shaders/renderer3d.frag" - }, - on_shaders_loader, - this - ); -} - -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 SpringThemeState::Loading: return; - case SpringThemeState::LoadedShader: return; - case SpringThemeState::LoadedBunny: - state = SpringThemeState::Idle; - stateTimer = 0.f; - bunnyHopAnimationTimer = 0.f; - break; - case SpringThemeState::Idle: { - bunnyHopAnimationTimer += dtSeconds; - const f32 HOP_FREQUENCY = 6.f; - - if (bunnyHopAnimationTimer > stateTimer) { - state = SpringThemeState::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 SpringThemeState::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 = SpringThemeState::Hopping; - } - break; - } - case SpringThemeState::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 = SpringThemeState::Idle; - stateTimer = randomFloatBetween(0.5f, 1.f); - } - break; - } - } -} - -void SpringTheme::render() { - renderer.render(); - if (state != SpringThemeState::Loading) { - bunnyMesh.render(&renderer); - } -} - -void SpringTheme::unload() { - renderer.unload(); - bunnyMesh.unload(); -} diff --git a/themes/src/spring/SpringTheme.hpp b/themes/src/spring/SpringTheme.hpp deleted file mode 100644 index 64f9cb5..0000000 --- a/themes/src/spring/SpringTheme.hpp +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef SPRING_THEME_HPP -#define SPRING_THEME_HPP - -#include "../mathlib.h" -#include "../types.h" -#include "../Renderer3d.h" -#include "../theme.h" - - -enum class SpringThemeState { - Loading = 0, - LoadedShader, - LoadedBunny, - PreHop, - Hopping, - Idle -}; - -class SpringTheme : public Theme { -public: - SpringTheme(WebglContext*); - ~SpringTheme(); - Renderer3d renderer; - SpringThemeState 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(WebglContext*); - void update(f32 dtSeconds); - void render(); - void unload(); -}; - -#endif diff --git a/themes/src/spring/grass_renderer.cpp b/themes/src/spring/grass_renderer.cpp new file mode 100644 index 0000000..685f733 --- /dev/null +++ b/themes/src/spring/grass_renderer.cpp @@ -0,0 +1,29 @@ +#include "grass_renderer.hpp" +#include "../renderer_3d.h" + +void GrassRenderer::load(GrassRendererLoadData params, Renderer3d* renderer) { + const f32 COLUMN_INCREMENT = GRASS_BLADES_PER_COL / params.area.x; + const f32 ROW_INCREMENT = GRASS_BLADES_PER_ROW / params.area.y; + for (i32 r = 0; r < GRASS_BLADES_PER_ROW; r++) { + i32 indexOffset = r * GRASS_BLADES_PER_ROW; + f32 y = ROW_INCREMENT * r; + for (i32 c = 0; c < GRASS_BLADES_PER_COL; c++) { + f32 x = COLUMN_INCREMENT * c; + i32 index = indexOffset + c; + grassBlades[index].position = Vector3(x, y, 0); + grassBlades[index].top_offset = Vector2(0, 0); + } + } +} + +void GrassRenderer::update(f32 seconds) { + +} + +void GrassRenderer::render(Renderer3d* renderer) { + +} + +void GrassRenderer::unload() { + +} diff --git a/themes/src/spring/grass_renderer.hpp b/themes/src/spring/grass_renderer.hpp new file mode 100644 index 0000000..88879f3 --- /dev/null +++ b/themes/src/spring/grass_renderer.hpp @@ -0,0 +1,33 @@ +#ifndef GRASS_RENDERER_HPP +#define GRASS_RENDERER_HPP + +#include "../renderer_3d.h" +#include "mathlib.h" +#include "types.h" + +const i32 GRASS_BLADES_PER_ROW = 24; +const i32 GRASS_BLADES_PER_COL = 24; +const i32 NUM_GRASS_BLADES = GRASS_BLADES_PER_ROW * GRASS_BLADES_PER_COL; + +struct GrassRendererLoadData { + Vector2 origin = Vector2(0, 0); + Vector2 area = Vector2(480, 480); + f32 grassHeight = 12.f; +}; + +struct GrassUpdateData { + Vector3 position; + Vector2 top_offset; +}; + +struct GrassRenderer { + + GrassUpdateData grassBlades[NUM_GRASS_BLADES]; + + void load(GrassRendererLoadData params, Renderer3d* renderer); + void update(f32 dtSeconds); + void render(Renderer3d* renderer); + void unload(); +}; + +#endif diff --git a/themes/src/spring/spring_theme.cpp b/themes/src/spring/spring_theme.cpp new file mode 100644 index 0000000..8507194 --- /dev/null +++ b/themes/src/spring/spring_theme.cpp @@ -0,0 +1,208 @@ +#include "spring_theme.hpp" +#include "../renderer_3d.h" +#include "../shader_fetcher.hpp" +#include +#include + +void onBunnySuccess(emscripten_fetch_t *fetch) { + SpringTheme* springTheme = (SpringTheme*)fetch->userData; + springTheme->state = SpringThemeState::LoadedBunny; + printf("Finished downloading %llu bytes from URL %s.\n", fetch->numBytes, fetch->url); + const i32 len = fetch->numBytes; + springTheme->bunnyMesh = Mesh3d_fromObj(&springTheme->renderer, 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. +} + +inline void fetch_bunny(SpringTheme* theme) { + 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; + auto* bunny_fetch = emscripten_fetch(&attr, "themes/resources/bunny.obj"); + bunny_fetch->userData = theme; +} + +inline void on_shaders_loader(ShaderFetchResult* result) { + SpringTheme* theme = (SpringTheme*)result->user_data; + theme->renderer.load(theme->renderer.context, result->vertex.c_str(), result->fragment.c_str()); + theme->state = SpringThemeState::LoadedShader; + fetch_bunny(theme); +} + +SpringTheme::SpringTheme(WebglContext* context) +{ + load(context); +} + +SpringTheme::~SpringTheme() +{ + unload(); +} + +void SpringTheme::load(WebglContext* context) { + state = SpringThemeState::Loading; + renderer.context = context; + renderer.clearColor = Vector4(160, 231, 160, 255.f).toNormalizedColor(); + + fetch_shader( + { + "themes/src/_shaders/renderer3d.vert", + "themes/src/_shaders/renderer3d.frag" + }, + on_shaders_loader, + this + ); +} + +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 SpringThemeState::Loading: return; + case SpringThemeState::LoadedShader: return; + case SpringThemeState::LoadedBunny: + state = SpringThemeState::Idle; + stateTimer = 0.f; + bunnyHopAnimationTimer = 0.f; + break; + case SpringThemeState::Idle: { + bunnyHopAnimationTimer += dtSeconds; + const f32 HOP_FREQUENCY = 6.f; + + if (bunnyHopAnimationTimer > stateTimer) { + state = SpringThemeState::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 SpringThemeState::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 = SpringThemeState::Hopping; + } + break; + } + case SpringThemeState::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 = SpringThemeState::Idle; + stateTimer = randomFloatBetween(0.5f, 1.f); + } + break; + } + } +} + +void SpringTheme::render() { + renderer.render(); + if (state != SpringThemeState::Loading) { + bunnyMesh.render(&renderer); + } +} + +void SpringTheme::unload() { + renderer.unload(); + bunnyMesh.unload(); +} diff --git a/themes/src/spring/spring_theme.hpp b/themes/src/spring/spring_theme.hpp new file mode 100644 index 0000000..6079958 --- /dev/null +++ b/themes/src/spring/spring_theme.hpp @@ -0,0 +1,45 @@ +#ifndef SPRING_THEME_HPP +#define SPRING_THEME_HPP + +#include "../mathlib.h" +#include "../types.h" +#include "../renderer_3d.h" +#include "../theme.h" + + +enum class SpringThemeState { + Loading = 0, + LoadedShader, + LoadedBunny, + PreHop, + Hopping, + Idle +}; + +class SpringTheme : public Theme { +public: + SpringTheme(WebglContext*); + ~SpringTheme(); + Renderer3d renderer; + SpringThemeState 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(WebglContext*); + void update(f32 dtSeconds); + void render(); + void unload(); +}; + +#endif diff --git a/themes/src/summer/SummerTheme.cpp b/themes/src/summer/SummerTheme.cpp deleted file mode 100644 index 1f76b56..0000000 --- a/themes/src/summer/SummerTheme.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include "SummerTheme.h" -#include "../Renderer2d.h" -#include "../list.h" -#include "../mathlib.h" -#include "../shaders/sun_frag.h" -#include "../shaders/sun_vert.h" -#include - -SummerTheme::SummerTheme(WebglContext* context) -{ - renderer.load(context); - load(context); -} - -SummerTheme::~SummerTheme() -{ - unload(); -} - -void SummerTheme::load(WebglContext* context) { - renderer.load(context, shader_sun_vert, shader_sun_frag); - 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() { - renderer.render(); - sun.render(&renderer); -} - -void SummerTheme::unload() { - sun.unload(); -} - -void Sun::load(Renderer2d* renderer) { - matte::List vertices; - matte::List 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/summer/SummerTheme.h b/themes/src/summer/SummerTheme.h deleted file mode 100644 index 2ce6b7f..0000000 --- a/themes/src/summer/SummerTheme.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once -#include "../types.h" -#include "../Renderer2d.h" -#include "../theme.h" -#include - -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(); -}; - -class SummerTheme : public Theme { -public: - SummerTheme(WebglContext*); - ~SummerTheme(); - Sun sun; - void load(WebglContext*); - void update(f32 dtSeconds); - void render(); - void unload(); -private: - Renderer2d renderer; -}; diff --git a/themes/src/summer/summer_theme.cpp b/themes/src/summer/summer_theme.cpp new file mode 100644 index 0000000..6935ad1 --- /dev/null +++ b/themes/src/summer/summer_theme.cpp @@ -0,0 +1,82 @@ +#include "summer_theme.h" +#include "../renderer_2d.h" +#include "../list.h" +#include "../mathlib.h" +#include "../shaders/sun_frag.h" +#include "../shaders/sun_vert.h" +#include + +SummerTheme::SummerTheme(WebglContext* context) +{ + renderer.load(context); + load(context); +} + +SummerTheme::~SummerTheme() +{ + unload(); +} + +void SummerTheme::load(WebglContext* context) { + renderer.load(context, shader_sun_vert, shader_sun_frag); + 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() { + renderer.render(); + sun.render(&renderer); +} + +void SummerTheme::unload() { + sun.unload(); +} + +void Sun::load(Renderer2d* renderer) { + matte::List vertices; + matte::List 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/summer/summer_theme.h b/themes/src/summer/summer_theme.h new file mode 100644 index 0000000..cd25ff5 --- /dev/null +++ b/themes/src/summer/summer_theme.h @@ -0,0 +1,29 @@ +#pragma once +#include "../types.h" +#include "../renderer_2d.h" +#include "../theme.h" +#include + +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(); +}; + +class SummerTheme : public Theme { +public: + SummerTheme(WebglContext*); + ~SummerTheme(); + Sun sun; + void load(WebglContext*); + void update(f32 dtSeconds); + void render(); + void unload(); +private: + Renderer2d renderer; +}; diff --git a/themes/src/webgl_context.cpp b/themes/src/webgl_context.cpp new file mode 100644 index 0000000..71b983e --- /dev/null +++ b/themes/src/webgl_context.cpp @@ -0,0 +1,46 @@ +#include "webgl_context.h" +#include + + +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(inWidth); + context->height = static_cast(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(inWidth); + height = static_cast(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/webgl_context.h b/themes/src/webgl_context.h new file mode 100644 index 0000000..1956092 --- /dev/null +++ b/themes/src/webgl_context.h @@ -0,0 +1,18 @@ +#pragma once +#include "types.h" +#include +#include +#include +#include +#include + +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/winter/Snowflake.cpp b/themes/src/winter/Snowflake.cpp deleted file mode 100644 index 57f1a8f..0000000 --- a/themes/src/winter/Snowflake.cpp +++ /dev/null @@ -1,189 +0,0 @@ -#include "Snowflake.h" -#include "../Renderer2d.h" -#include "../mathlib.h" -#include "../list.h" -#include - -/* - - What else to do? - - - Windstream that blows a certain selection of snowflakes in a loop-dee-loop pattern - - Snowflakes that land on the ground and melt - - Snowflakes that spin along the Y-axis for a three dimensional effect - - */ - -const Vector4 snowColor = Vector4(1.0, 0.98, 0.98, 1); -const Vector2 NUM_ARMS_RANGE = Vector2(6.f, 8.f); -const Vector2 RADIUS_RANGE = Vector2(8.f, 32.f); -const Vector2 VELOCITY_RANGE_X = Vector2(-10.f, 10.f); -const Vector2 VELOCITY_RANGE_Y = Vector2(-100.f, -85.f); -const Vector2 ROTATION_VELOCITY_RANGE = Vector2(-PI / 8.f, PI / 8.f); -const Vector2 WIND_VELOCITY_RANGE_X = Vector2(-3.f, 3.f); -const Vector2 WIND_VELOCITY_RANGE_Y = Vector2(3.f, 10.f); -const f32 GRAVITY = 5.f; - -inline void generateSnowflakeArm(f32 width, f32 height, f32 angle, matte::List* vertices, Mat4x4 transform = Mat4x4()) { - f32 halfWidth = width / 2.f; - Vector2 leftStart = transform * Vector2(-halfWidth, 0).rotate(angle); - Vector2 leftEnd = transform * Vector2(-halfWidth, height).rotate(angle); - Vector2 rightStart = transform * Vector2(halfWidth, 0).rotate(angle); - Vector2 rightEnd = transform * Vector2(halfWidth, height).rotate(angle); - - vertices->add({ leftStart, snowColor, Mat4x4() }); - vertices->add({ leftEnd, snowColor, Mat4x4() }); - vertices->add({ rightEnd, snowColor, Mat4x4() }); - vertices->add({ leftStart, snowColor, Mat4x4() }); - vertices->add({ rightEnd, snowColor, Mat4x4() }); - vertices->add({ rightStart, snowColor, Mat4x4() }); -} - -/** - Fills in the vertices array vertices that represent a snowflake shape. The snowflake shape consists - of numArms jutting out of the center radially. The center of the flake is connected. The radius is - used to determine the length of the arms. The first third of each arm is barren, after which branches - extends on either side of the arm at an angle of about 60 degrees. Each branch can itself have tiny - sub branches jutting out of it, but these should be not nearly as large as the regular branches. - - With all of this in mind, we should be able to build a convincing snowflake. - - :param vertices List of vertices to be filled in - :param numArms Number of arms radially sticking out of the snowflake - :param radius Length of the snowflake arms - */ -inline void generateSnowflakeShape(matte::List* vertices, i32 numArms, f32 radius, f32 armWidthRatio = 0.08f) { - f32 innerRadius = 0; - f32 outerRadius = 2 * radius; - f32 dx = ((2 * PI) / numArms); - for (i32 armIndex = 0; armIndex < numArms; armIndex++) { - f32 armAngle = dx * armIndex; - generateSnowflakeArm(armWidthRatio * radius, radius, armAngle, vertices); - f32 armLeftAngle = DEG_TO_RAD(60.f); - f32 armRightAngle = DEG_TO_RAD(-60.f); - - const i32 NUM_SUB_ARMS = 4; - for (i32 subArmIndex = 0; subArmIndex < NUM_SUB_ARMS; subArmIndex++) { - f32 height = (radius / static_cast(subArmIndex)); - f32 width = (armWidthRatio / (subArmIndex + 1)) * height; - f32 transY = (radius / (NUM_SUB_ARMS + 1)) * (subArmIndex + 1); - Vector2 translation = Vector2(0, transY).rotate(armAngle); - generateSnowflakeArm(width, height, armAngle, vertices, Mat4x4().translateByVec2(translation).rotate2D(armLeftAngle)); - generateSnowflakeArm(width, height, armAngle, vertices, Mat4x4().translateByVec2(translation).rotate2D(armRightAngle)); - } - } -} - -inline void initFlake(SnowflakeParticleRenderer* renderer, SnowflakeUpdateData* ud) { - ud->radius = randomFloatBetween(RADIUS_RANGE.x, RADIUS_RANGE.y); - ud->vtxIdx = renderer->vertices.numElements; - generateSnowflakeShape(&renderer->vertices, - randomFloatBetween(NUM_ARMS_RANGE.x, NUM_ARMS_RANGE.y), - ud->radius); - - ud->numVertices = renderer->vertices.numElements - ud->vtxIdx; - ud->velocity = Vector2(randomFloatBetween(VELOCITY_RANGE_X.x, VELOCITY_RANGE_X.y), randomFloatBetween(VELOCITY_RANGE_Y.x, VELOCITY_RANGE_Y.y)); - ud->position = Vector2(randomFloatBetween(0, renderer->xMax), randomFloatBetween(renderer->yMax, 4 * renderer->yMax)); - ud->rotateVelocity = randomFloatBetween(ROTATION_VELOCITY_RANGE.x, ROTATION_VELOCITY_RANGE.y); -} - -void SnowflakeParticleRenderer::load(SnowflakeLoadParameters params, Renderer2d* renderer) { - numSnowflakes = params.numSnowflakes; - - updateData = new SnowflakeUpdateData[params.numSnowflakes]; - - xMax = static_cast(renderer->context->width); - yMax = static_cast(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)); - } - - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindVertexArray(0); -} - -inline void resetFlake(SnowflakeParticleRenderer* renderer, SnowflakeUpdateData* ud) { - ud->position.y = 2 * renderer->yMax; - ud->velocity = Vector2(randomFloatBetween(-10, 10), randomFloatBetween(-100, -85)); - ud->rotation = 0; -} - -inline void updateFlake(SnowflakeParticleRenderer* renderer, SnowflakeUpdateData* ud, i32 s, f32 dtSeconds) { - ud->velocity = ud->velocity + Vector2(0, -(GRAVITY * dtSeconds)); - //if (addWind) ud->velocity += renderer->windSpeed; - ud->position += ud->velocity * dtSeconds; - ud->rotation += ud->rotateVelocity * dtSeconds; - - Mat4x4 m = Mat4x4().translateByVec2(ud->position).rotate2D(ud->rotation); - for (i32 v = ud->vtxIdx; v < (ud->vtxIdx + ud->numVertices); v++) { - renderer->vertices.data[v].vMatrix = m; - } - - if (ud->position.y <= -ud->radius) { - resetFlake(renderer, ud); - } -} - -void SnowflakeParticleRenderer::update(f32 dtSeconds) { - timeUntilNextWindSeconds -= dtSeconds; - if (timeUntilNextWindSeconds < 0) { - timeUntilNextWindSeconds = randomFloatBetween(2.5f, 10.f); - } - - for (i32 s = 0; s < numSnowflakes; s++) { - SnowflakeUpdateData* ud = &updateData[s]; - updateFlake(this, ud, s, dtSeconds); - } -} - -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/winter/Snowflake.h b/themes/src/winter/Snowflake.h deleted file mode 100644 index ad027f6..0000000 --- a/themes/src/winter/Snowflake.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef SNOWFLAKE_H -#define SNOWFLAKE_H - -#include "../types.h" -#include "../mathlib.h" -#include "../list.h" -#include "Windfield.hpp" - -struct Renderer2d; -struct Vertex2D; - -struct SnowflakeLoadParameters { - i32 numSnowflakes = 480; - f32 windIntervalSeconds = 1.5f; -}; - -struct SnowflakeUpdateData { - Vector2 velocity; - Vector2 position; - f32 rotateVelocity = 0.f; - f32 rotation = 0; - f32 radius; - - i32 vtxIdx = 0; - i32 numVertices = 0; -}; - -struct SnowflakeParticleRenderer { - f32 xMax = 0; - f32 yMax = 0; - f32 windIntervalSeconds = 1.5; - i32 numSnowflakes = 0; - f32 timeUntilNextWindSeconds = 0; - WindField<100, 100, 10> wind; - SnowflakeUpdateData* updateData; - - u32 vao; - u32 vbo; - Mat4x4 model; - matte::List vertices; - - void load(SnowflakeLoadParameters params, Renderer2d* renderer); - void update(f32 dtSeconds); - void render(Renderer2d* renderer); - void unload(); -}; - -#endif diff --git a/themes/src/winter/Windfield.cpp b/themes/src/winter/Windfield.cpp deleted file mode 100644 index 88fb74b..0000000 --- a/themes/src/winter/Windfield.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "Windfield.hpp" - - -template -void WindField::load(f32 cellSizePixels, i32 fieldWithCells, i32 fieldHeightCells, f32 ttl, Vector2 origin) { - this->ttl = ttl; - this->origin = origin; - this->end = this->origin + Vector2(Width * CellDimension, Height * CellDimension); -} - -template -bool WindField::addVector(i32 x, i32 y, Vector2& v) { - field[x][y] = v; - return false; -} - -template -Vector2 WindField::getWindFactor(Vector2& v) { - if (v.x >= origin.x && v.x <= end.x - && v.y >= origin.y && v.y <= end.y) { - Vector2 positionInField = v - this->origin; - i32 cellX = static_cast(Width / positionInField.x); - i32 cellY = static_cast(Height / positionInField.y); - return field[cellX][cellY]; - } - - return Vector2(); -} diff --git a/themes/src/winter/Windfield.hpp b/themes/src/winter/Windfield.hpp deleted file mode 100644 index 5bf0c38..0000000 --- a/themes/src/winter/Windfield.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef WIND_FIELD_HPP -#define WIND_FIELD_HPP -#include "../types.h" -#include "../mathlib.h" - -/** - A Windfield represents a field of vectors in a rectangular region. - The Width and Height are given in units of CellDimenions. The CellDimension - is given in pixels. - */ -template -struct WindField { - f32 ttl = 0.f; - Vector2 origin; - Vector2 end; - - /* - Granularity of each cell in pixels. - */ - const f32 cellDimension = CellDimension; - - /* - Width of the vector field in CellDimensions. - */ - const f32 width = Width; - - /* - Height of the vector vield in CellDimensions. - */ - const f32 height = Height; - - Vector2** field; - - void load(f32 cellSizePixels, i32 fieldWithCells, i32 fieldHeightCells, f32 ttl, Vector2 origin); - bool addVector(i32 x, i32 y, Vector2& v); - Vector2 getWindFactor(Vector2& v); -}; - -#endif diff --git a/themes/src/winter/WinterTheme.cpp b/themes/src/winter/WinterTheme.cpp deleted file mode 100644 index 052670e..0000000 --- a/themes/src/winter/WinterTheme.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "WinterTheme.hpp" -#include "../Renderer2d.h" - -WinterTheme::WinterTheme(WebglContext* context) -{ - renderer.load(context); - load(); -} - -WinterTheme::~WinterTheme() -{ - unload(); -} - -void WinterTheme::load() { - 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() { - renderer.render(); - spr.render(&renderer); -} - -void WinterTheme::unload() { - spr.unload(); -} diff --git a/themes/src/winter/WinterTheme.hpp b/themes/src/winter/WinterTheme.hpp deleted file mode 100644 index 5ba6d94..0000000 --- a/themes/src/winter/WinterTheme.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef WINTER_THEME_HPP -#define WINTER_THEME_HPP - -#include "Snowflake.h" -#include "../types.h" -#include "../theme.h" -#include "../Renderer2d.h" - -struct WebglContext; - -struct WinterTheme : public Theme { -public: - WinterTheme(WebglContext*); - ~WinterTheme(); - SnowflakeParticleRenderer spr; - - void load(); - void update(f32 dtSeconds); - void render(); - void unload(); -private: - Renderer2d renderer; -}; - -#endif diff --git a/themes/src/winter/snowflake.cpp b/themes/src/winter/snowflake.cpp new file mode 100644 index 0000000..4ce8f3a --- /dev/null +++ b/themes/src/winter/snowflake.cpp @@ -0,0 +1,189 @@ +#include "snowflake.h" +#include "../renderer_2d.h" +#include "../mathlib.h" +#include "../list.h" +#include + +/* + + What else to do? + + - Windstream that blows a certain selection of snowflakes in a loop-dee-loop pattern + - Snowflakes that land on the ground and melt + - Snowflakes that spin along the Y-axis for a three dimensional effect + + */ + +const Vector4 snowColor = Vector4(1.0, 0.98, 0.98, 1); +const Vector2 NUM_ARMS_RANGE = Vector2(6.f, 8.f); +const Vector2 RADIUS_RANGE = Vector2(8.f, 32.f); +const Vector2 VELOCITY_RANGE_X = Vector2(-10.f, 10.f); +const Vector2 VELOCITY_RANGE_Y = Vector2(-100.f, -85.f); +const Vector2 ROTATION_VELOCITY_RANGE = Vector2(-PI / 8.f, PI / 8.f); +const Vector2 WIND_VELOCITY_RANGE_X = Vector2(-3.f, 3.f); +const Vector2 WIND_VELOCITY_RANGE_Y = Vector2(3.f, 10.f); +const f32 GRAVITY = 5.f; + +inline void generateSnowflakeArm(f32 width, f32 height, f32 angle, matte::List* vertices, Mat4x4 transform = Mat4x4()) { + f32 halfWidth = width / 2.f; + Vector2 leftStart = transform * Vector2(-halfWidth, 0).rotate(angle); + Vector2 leftEnd = transform * Vector2(-halfWidth, height).rotate(angle); + Vector2 rightStart = transform * Vector2(halfWidth, 0).rotate(angle); + Vector2 rightEnd = transform * Vector2(halfWidth, height).rotate(angle); + + vertices->add({ leftStart, snowColor, Mat4x4() }); + vertices->add({ leftEnd, snowColor, Mat4x4() }); + vertices->add({ rightEnd, snowColor, Mat4x4() }); + vertices->add({ leftStart, snowColor, Mat4x4() }); + vertices->add({ rightEnd, snowColor, Mat4x4() }); + vertices->add({ rightStart, snowColor, Mat4x4() }); +} + +/** + Fills in the vertices array vertices that represent a snowflake shape. The snowflake shape consists + of numArms jutting out of the center radially. The center of the flake is connected. The radius is + used to determine the length of the arms. The first third of each arm is barren, after which branches + extends on either side of the arm at an angle of about 60 degrees. Each branch can itself have tiny + sub branches jutting out of it, but these should be not nearly as large as the regular branches. + + With all of this in mind, we should be able to build a convincing snowflake. + + :param vertices List of vertices to be filled in + :param numArms Number of arms radially sticking out of the snowflake + :param radius Length of the snowflake arms + */ +inline void generateSnowflakeShape(matte::List* vertices, i32 numArms, f32 radius, f32 armWidthRatio = 0.08f) { + f32 innerRadius = 0; + f32 outerRadius = 2 * radius; + f32 dx = ((2 * PI) / numArms); + for (i32 armIndex = 0; armIndex < numArms; armIndex++) { + f32 armAngle = dx * armIndex; + generateSnowflakeArm(armWidthRatio * radius, radius, armAngle, vertices); + f32 armLeftAngle = DEG_TO_RAD(60.f); + f32 armRightAngle = DEG_TO_RAD(-60.f); + + const i32 NUM_SUB_ARMS = 4; + for (i32 subArmIndex = 0; subArmIndex < NUM_SUB_ARMS; subArmIndex++) { + f32 height = (radius / static_cast(subArmIndex)); + f32 width = (armWidthRatio / (subArmIndex + 1)) * height; + f32 transY = (radius / (NUM_SUB_ARMS + 1)) * (subArmIndex + 1); + Vector2 translation = Vector2(0, transY).rotate(armAngle); + generateSnowflakeArm(width, height, armAngle, vertices, Mat4x4().translateByVec2(translation).rotate2D(armLeftAngle)); + generateSnowflakeArm(width, height, armAngle, vertices, Mat4x4().translateByVec2(translation).rotate2D(armRightAngle)); + } + } +} + +inline void initFlake(SnowflakeParticleRenderer* renderer, SnowflakeUpdateData* ud) { + ud->radius = randomFloatBetween(RADIUS_RANGE.x, RADIUS_RANGE.y); + ud->vtxIdx = renderer->vertices.numElements; + generateSnowflakeShape(&renderer->vertices, + randomFloatBetween(NUM_ARMS_RANGE.x, NUM_ARMS_RANGE.y), + ud->radius); + + ud->numVertices = renderer->vertices.numElements - ud->vtxIdx; + ud->velocity = Vector2(randomFloatBetween(VELOCITY_RANGE_X.x, VELOCITY_RANGE_X.y), randomFloatBetween(VELOCITY_RANGE_Y.x, VELOCITY_RANGE_Y.y)); + ud->position = Vector2(randomFloatBetween(0, renderer->xMax), randomFloatBetween(renderer->yMax, 4 * renderer->yMax)); + ud->rotateVelocity = randomFloatBetween(ROTATION_VELOCITY_RANGE.x, ROTATION_VELOCITY_RANGE.y); +} + +void SnowflakeParticleRenderer::load(SnowflakeLoadParameters params, Renderer2d* renderer) { + numSnowflakes = params.numSnowflakes; + + updateData = new SnowflakeUpdateData[params.numSnowflakes]; + + xMax = static_cast(renderer->context->width); + yMax = static_cast(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)); + } + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); +} + +inline void resetFlake(SnowflakeParticleRenderer* renderer, SnowflakeUpdateData* ud) { + ud->position.y = 2 * renderer->yMax; + ud->velocity = Vector2(randomFloatBetween(-10, 10), randomFloatBetween(-100, -85)); + ud->rotation = 0; +} + +inline void updateFlake(SnowflakeParticleRenderer* renderer, SnowflakeUpdateData* ud, i32 s, f32 dtSeconds) { + ud->velocity = ud->velocity + Vector2(0, -(GRAVITY * dtSeconds)); + //if (addWind) ud->velocity += renderer->windSpeed; + ud->position += ud->velocity * dtSeconds; + ud->rotation += ud->rotateVelocity * dtSeconds; + + Mat4x4 m = Mat4x4().translateByVec2(ud->position).rotate2D(ud->rotation); + for (i32 v = ud->vtxIdx; v < (ud->vtxIdx + ud->numVertices); v++) { + renderer->vertices.data[v].vMatrix = m; + } + + if (ud->position.y <= -ud->radius) { + resetFlake(renderer, ud); + } +} + +void SnowflakeParticleRenderer::update(f32 dtSeconds) { + timeUntilNextWindSeconds -= dtSeconds; + if (timeUntilNextWindSeconds < 0) { + timeUntilNextWindSeconds = randomFloatBetween(2.5f, 10.f); + } + + for (i32 s = 0; s < numSnowflakes; s++) { + SnowflakeUpdateData* ud = &updateData[s]; + updateFlake(this, ud, s, dtSeconds); + } +} + +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/winter/snowflake.h b/themes/src/winter/snowflake.h new file mode 100644 index 0000000..11a1438 --- /dev/null +++ b/themes/src/winter/snowflake.h @@ -0,0 +1,48 @@ +#ifndef SNOWFLAKE_H +#define SNOWFLAKE_H + +#include "../types.h" +#include "../mathlib.h" +#include "../list.h" +#include "windfield.hpp" + +struct Renderer2d; +struct Vertex2D; + +struct SnowflakeLoadParameters { + i32 numSnowflakes = 480; + f32 windIntervalSeconds = 1.5f; +}; + +struct SnowflakeUpdateData { + Vector2 velocity; + Vector2 position; + f32 rotateVelocity = 0.f; + f32 rotation = 0; + f32 radius; + + i32 vtxIdx = 0; + i32 numVertices = 0; +}; + +struct SnowflakeParticleRenderer { + f32 xMax = 0; + f32 yMax = 0; + f32 windIntervalSeconds = 1.5; + i32 numSnowflakes = 0; + f32 timeUntilNextWindSeconds = 0; + WindField<100, 100, 10> wind; + SnowflakeUpdateData* updateData; + + u32 vao; + u32 vbo; + Mat4x4 model; + matte::List vertices; + + void load(SnowflakeLoadParameters params, Renderer2d* renderer); + void update(f32 dtSeconds); + void render(Renderer2d* renderer); + void unload(); +}; + +#endif diff --git a/themes/src/winter/windfield.cpp b/themes/src/winter/windfield.cpp new file mode 100644 index 0000000..f6c3be3 --- /dev/null +++ b/themes/src/winter/windfield.cpp @@ -0,0 +1,28 @@ +#include "windfield.hpp" + + +template +void WindField::load(f32 cellSizePixels, i32 fieldWithCells, i32 fieldHeightCells, f32 ttl, Vector2 origin) { + this->ttl = ttl; + this->origin = origin; + this->end = this->origin + Vector2(Width * CellDimension, Height * CellDimension); +} + +template +bool WindField::addVector(i32 x, i32 y, Vector2& v) { + field[x][y] = v; + return false; +} + +template +Vector2 WindField::getWindFactor(Vector2& v) { + if (v.x >= origin.x && v.x <= end.x + && v.y >= origin.y && v.y <= end.y) { + Vector2 positionInField = v - this->origin; + i32 cellX = static_cast(Width / positionInField.x); + i32 cellY = static_cast(Height / positionInField.y); + return field[cellX][cellY]; + } + + return Vector2(); +} diff --git a/themes/src/winter/windfield.hpp b/themes/src/winter/windfield.hpp new file mode 100644 index 0000000..5bf0c38 --- /dev/null +++ b/themes/src/winter/windfield.hpp @@ -0,0 +1,39 @@ +#ifndef WIND_FIELD_HPP +#define WIND_FIELD_HPP +#include "../types.h" +#include "../mathlib.h" + +/** + A Windfield represents a field of vectors in a rectangular region. + The Width and Height are given in units of CellDimenions. The CellDimension + is given in pixels. + */ +template +struct WindField { + f32 ttl = 0.f; + Vector2 origin; + Vector2 end; + + /* + Granularity of each cell in pixels. + */ + const f32 cellDimension = CellDimension; + + /* + Width of the vector field in CellDimensions. + */ + const f32 width = Width; + + /* + Height of the vector vield in CellDimensions. + */ + const f32 height = Height; + + Vector2** field; + + void load(f32 cellSizePixels, i32 fieldWithCells, i32 fieldHeightCells, f32 ttl, Vector2 origin); + bool addVector(i32 x, i32 y, Vector2& v); + Vector2 getWindFactor(Vector2& v); +}; + +#endif diff --git a/themes/src/winter/winter_theme.cpp b/themes/src/winter/winter_theme.cpp new file mode 100644 index 0000000..a628f18 --- /dev/null +++ b/themes/src/winter/winter_theme.cpp @@ -0,0 +1,32 @@ +#include "winter_theme.hpp" +#include "../renderer_2d.h" + +WinterTheme::WinterTheme(WebglContext* context) +{ + renderer.load(context); + load(); +} + +WinterTheme::~WinterTheme() +{ + unload(); +} + +void WinterTheme::load() { + 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() { + renderer.render(); + spr.render(&renderer); +} + +void WinterTheme::unload() { + spr.unload(); +} diff --git a/themes/src/winter/winter_theme.hpp b/themes/src/winter/winter_theme.hpp new file mode 100644 index 0000000..d1c3e05 --- /dev/null +++ b/themes/src/winter/winter_theme.hpp @@ -0,0 +1,25 @@ +#ifndef WINTER_THEME_HPP +#define WINTER_THEME_HPP + +#include "snowflake.h" +#include "../types.h" +#include "../theme.h" +#include "../renderer_2d.h" + +struct WebglContext; + +struct WinterTheme : public Theme { +public: + WinterTheme(WebglContext*); + ~WinterTheme(); + SnowflakeParticleRenderer spr; + + void load(); + void update(f32 dtSeconds); + void render(); + void unload(); +private: + Renderer2d renderer; +}; + +#endif -- cgit v1.2.1