diff options
Diffstat (limited to 'themes/src')
58 files changed, 1409 insertions, 780 deletions
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 <cstdio> -#include <cstdlib> - -EM_BOOL loop(double time, void* loop) { - MainLoop* mainLoop = (MainLoop*) loop; - if (!mainLoop->isRunning) { - return false; - } - - if (mainLoop->lastTime == 0) { - mainLoop->lastTime = time; - return true; - } - - long deltaTime = time - mainLoop->lastTime; - mainLoop->lastTime = time; - mainLoop->elapsedTime += deltaTime; - mainLoop->numFrames++; - float deltaTimeSeconds = static_cast<float>(deltaTime) / 1000.f; - - if (mainLoop->elapsedTime >= 1000.0) { - printf("FPS: %d\n", mainLoop->numFrames); - - mainLoop->elapsedTime = 0.0; - mainLoop->numFrames = 0; - } - - mainLoop->updateFunc(deltaTimeSeconds, NULL); - return true; -}
\ No newline at end of file diff --git a/themes/src/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/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 <cstdio> - - -EM_BOOL onResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData) { - WebglContext* context = (WebglContext*)userData; - - f64 inWidth, inHeight; - emscripten_get_element_css_size(context->query, &inWidth, &inHeight); - - context->width = static_cast<f32>(inWidth); - context->height = static_cast<f32>(inHeight); - - return true; -} - -void WebglContext::init(const char* inQuery) { - strcpy(query, inQuery); - f64 inWidth, inHeight; - emscripten_get_element_css_size(query, &inWidth, &inHeight); - width = static_cast<f32>(inWidth); - height = static_cast<f32>(inHeight); - emscripten_set_canvas_element_size( query, width, height); - - EmscriptenWebGLContextAttributes attrs; - emscripten_webgl_init_context_attributes(&attrs); - - attrs.enableExtensionsByDefault = 1; - attrs.majorVersion = 3; - attrs.minorVersion = 0; - - context = emscripten_webgl_create_context(query, &attrs); - makeCurrentContext(); - - glClearColor(0, 0, 0, 0.0f); - - emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, this, false, onResize); -}; - -void WebglContext::makeCurrentContext() { - emscripten_webgl_make_context_current(context); -}; - -void WebglContext::destroy() { - emscripten_webgl_destroy_context(context); -} diff --git a/themes/src/_shaders/grass.frag b/themes/src/_shaders/grass.frag new file mode 100644 index 0000000..a72f078 --- /dev/null +++ b/themes/src/_shaders/grass.frag @@ -0,0 +1,11 @@ +varying lowp vec2 vUV; + +void main() { + lowp float halfWidth = 0.5 * (1.0 - vUV.y); + lowp float distFromCenter = abs(vUV.x - 0.5); + if (distFromCenter > halfWidth) discard; + + lowp vec3 baseColor = vec3(0.15, 0.45, 0.10); + lowp vec3 tipColor = vec3(0.40, 0.75, 0.20); + gl_FragColor = vec4(mix(baseColor, tipColor, vUV.y), 1.0); +} diff --git a/themes/src/_shaders/grass.vert b/themes/src/_shaders/grass.vert new file mode 100644 index 0000000..0cf0285 --- /dev/null +++ b/themes/src/_shaders/grass.vert @@ -0,0 +1,25 @@ +attribute vec2 position; // Local quad vertex: x in [-0.5, 0.5], y in [0, 1] +attribute vec3 instancePos; // Per-instance: world-space base of blade +attribute float instancePhase; // Per-instance: random phase offset for sway +attribute float instanceHeight; // Per-instance: height scale multiplier + +uniform mat4 projection; +uniform mat4 view; +uniform float time; +uniform float bladeWidth; +uniform float bladeHeight; +uniform float swayAmount; + +varying lowp vec2 vUV; + +void main() { + vec3 cameraRight = vec3(view[0][0], view[1][0], view[2][0]); + float h = bladeHeight * instanceHeight; + float sway = sin(time * 1.5 + instancePhase) * swayAmount * position.y; + vec3 worldPos = instancePos + + cameraRight * (position.x + sway) * bladeWidth + + vec3(0.0, 1.0, 0.0) * position.y * h; + + gl_Position = projection * view * vec4(worldPos, 1.0); + vUV = vec2(position.x + 0.5, position.y); +} diff --git a/themes/src/_shaders/snowflake.frag b/themes/src/_shaders/snowflake.frag new file mode 100644 index 0000000..d887cf5 --- /dev/null +++ b/themes/src/_shaders/snowflake.frag @@ -0,0 +1,73 @@ +// Procedural star fragment shader +varying lowp vec2 vUV; +varying lowp float vSeed; +varying lowp float vScale; + +const lowp float PI = 3.14159265359; + +// Simple hash function for deterministic randomness +lowp float hash(lowp float n) { + return fract(sin(n) * 43758.5453123); +} + +// Generate star pattern procedurally +lowp float starPattern(lowp vec2 uv) { + lowp float dist = length(uv); + lowp float angle = atan(uv.y, uv.x); + + // Number of star points (4 or 5) + lowp float numPoints = 4.0 + floor(hash(vSeed) * 2.0); + + // Create sharp star with triangular points + lowp float angleStep = 2.0 * PI / numPoints; + lowp float currentAngle = mod(angle + PI / numPoints, angleStep) - angleStep * 0.5; + + // Create triangular points - distance from center to edge varies linearly with angle + lowp float normalizedAngle = abs(currentAngle) / (angleStep * 0.5); + + // Outer tip radius and inner valley radius + lowp float tipRadius = 0.5; + lowp float valleyRadius = 0.15; + + // Linear interpolation creates sharp triangular points + lowp float rayEdge = mix(tipRadius, valleyRadius, normalizedAngle); + + // Hard cutoff for sharp edges + lowp float star = step(dist, rayEdge); + + return star; +} + +void main() { + lowp float pattern = starPattern(vUV); + + // Color variation based on seed - white and blue tints + lowp float colorVar = hash(vSeed * 3.0); + lowp vec3 starColor; + + if (colorVar < 0.5) { + // Pure white + starColor = vec3(1.0, 1.0, 1.0); + } else if (colorVar < 0.75) { + // Light blue tint + starColor = vec3(0.9, 0.95, 1.0); + } else { + // Stronger blue tint + starColor = vec3(0.85, 0.92, 1.0); + } + + // Scale alpha based on size - smaller stars are more transparent + // Normalize scale from range [16, 48] to [0, 1] + lowp float sizeRatio = (vScale - 16.0) / (48.0 - 16.0); + // Map to alpha range [0.3, 1.0] - smaller stars at 30% opacity, larger at 100% + lowp float alpha = mix(0.3, 1.0, sizeRatio); + + lowp vec4 color = vec4(starColor, pattern * alpha); + + // Discard fully transparent pixels for performance + if (color.a < 0.01) { + discard; + } + + gl_FragColor = color; +} diff --git a/themes/src/_shaders/snowflake.vert b/themes/src/_shaders/snowflake.vert new file mode 100644 index 0000000..7cbfb99 --- /dev/null +++ b/themes/src/_shaders/snowflake.vert @@ -0,0 +1,30 @@ +// Instanced snowflake vertex shader +attribute vec2 position; // Base quad vertex position +attribute vec2 instancePos; // Per-instance: snowflake center position +attribute float instanceRot; // Per-instance: rotation angle +attribute float instanceScale; // Per-instance: size scale +attribute float instanceSeed; // Per-instance: random seed for variation + +uniform mat4 projection; +uniform mat4 model; + +varying lowp vec2 vUV; // UV coordinates for fragment shader +varying lowp float vSeed; // Pass seed to fragment shader +varying lowp float vScale; // Pass scale to fragment shader + +void main() { + // Rotate and scale the base quad + float c = cos(instanceRot); + float s = sin(instanceRot); + mat2 rotation = mat2(c, s, -s, c); + + vec2 rotatedPos = rotation * (position * instanceScale); + vec2 worldPos = instancePos + rotatedPos; + + gl_Position = projection * model * vec4(worldPos, 0.0, 1.0); + + // Pass UV in range [-1, 1] for procedural generation + vUV = position; + vSeed = instanceSeed; + vScale = instanceScale; +} diff --git a/themes/src/_shaders/sun.frag b/themes/src/_shaders/sun.frag index 8463e06..2170b39 100644 --- a/themes/src/_shaders/sun.frag +++ b/themes/src/_shaders/sun.frag @@ -1,5 +1,43 @@ varying lowp vec4 VertexColor; +varying lowp vec2 TexCoord; +uniform lowp float time; + +// Simple noise function for edge distortion +lowp float noise(lowp vec2 p) { + return sin(p.x * 10.0 + time) * cos(p.y * 10.0 + time * 0.5) * 0.5 + 0.5; +} void main() { - gl_FragColor = VertexColor; + // TexCoord is now normalized: center is (0,0), edges are at distance ~1 + lowp float dist = length(TexCoord); + + // Add animated noise to the edge + lowp float angle = atan(TexCoord.y, TexCoord.x); + lowp float wave = sin(angle * 8.0 + time * 2.0) * 0.05 + sin(angle * 4.0 - time * 1.5) * 0.03; + lowp float noiseValue = noise(TexCoord + time * 0.1) * 0.02; + + // Create soft edge using smoothstep - ensure fade reaches zero at the actual edge + lowp float innerEdge = 0.8; + lowp float outerEdge = 1.0; + lowp float alpha = 1.0 - smoothstep(innerEdge, outerEdge, dist); + + // Apply wave distortion to the edge + alpha *= 1.0 - smoothstep(0.85 + wave + noiseValue * 2.0, 1.0, dist); + + // Make edges more transparent but not too much + alpha = alpha * alpha; + + // Add slight glow effect at the edge + lowp float glow = smoothstep(0.5, 0.8, dist) * (1.0 - smoothstep(0.8, 1.0, dist)); + + // Create orange gradient from center + lowp vec3 orangeColor = vec3(1.0, 0.5, 0.1); + lowp float centerGradient = smoothstep(0.6, 0.0, dist); + lowp vec3 baseColor = mix(VertexColor.rgb, orangeColor, centerGradient * 0.6); + + // Mix in the glow with a brighter color + lowp vec3 glowColor = baseColor * 1.3; + lowp vec3 finalColor = mix(baseColor, glowColor, glow * 0.5); + + gl_FragColor = vec4(finalColor, VertexColor.a * alpha); } diff --git a/themes/src/_shaders/sun.vert b/themes/src/_shaders/sun.vert index 76150f0..5ed77d7 100644 --- a/themes/src/_shaders/sun.vert +++ b/themes/src/_shaders/sun.vert @@ -1,13 +1,22 @@ -attribute vec2 position; -attribute vec4 color; -attribute mat4 vMatrix; -uniform mat4 projection; -uniform mat4 model; +attribute vec2 position; +attribute vec4 color; +attribute mat4 vMatrix; +uniform mat4 projection; +uniform mat4 model; varying lowp vec4 VertexColor; +varying lowp vec2 TexCoord; -void main() { - vec4 fragmentPosition = projection * model * vMatrix * vec4(position.x, position.y, 0, 1); - gl_Position = fragmentPosition; - VertexColor = color; +void main() { + vec4 fragmentPosition = projection * model * vMatrix * vec4(position.x, position.y, 0, 1); + gl_Position = fragmentPosition; + VertexColor = color; + // Normalize the position - the center is at (0,0) and edge vertices are at distance 'radius' + // We want TexCoord to be in the range roughly [-1, 1] at the edges + lowp float maxDist = length(position); + if (maxDist > 0.1) { + TexCoord = position / maxDist; + } else { + TexCoord = vec2(0.0, 0.0); + } } diff --git a/themes/src/autumn/AutumnTheme.cpp b/themes/src/autumn/autumn_theme.cpp index 4b7a2e2..d88b265 100644 --- a/themes/src/autumn/AutumnTheme.cpp +++ b/themes/src/autumn/autumn_theme.cpp @@ -1,4 +1,4 @@ -#include "AutumnTheme.hpp" +#include "autumn_theme.hpp" #include "../shapes_2d.h" #include <memory> diff --git a/themes/src/autumn/AutumnTheme.hpp b/themes/src/autumn/autumn_theme.hpp index e3f5748..b61c0f3 100644 --- a/themes/src/autumn/AutumnTheme.hpp +++ b/themes/src/autumn/autumn_theme.hpp @@ -1,11 +1,11 @@ #ifndef AUTUMN_THEME_HPP #define AUTUMN_THEME_HPP -#include "TreeShape.h" -#include "LeafParticleRender.h" +#include "tree_shape.h" +#include "leaf_particle_render.h" #include "../types.h" #include "../theme.h" -#include "../Renderer2d.h" +#include "../renderer_2d.h" #include <memory> #include <vector> diff --git a/themes/src/autumn/LeafParticleRender.cpp b/themes/src/autumn/leaf_particle_render.cpp index fee3df2..569bb2d 100644 --- a/themes/src/autumn/LeafParticleRender.cpp +++ b/themes/src/autumn/leaf_particle_render.cpp @@ -1,7 +1,7 @@ -#include "LeafParticleRender.h" -#include "../Renderer2d.h" +#include "leaf_particle_render.h" +#include "../renderer_2d.h" #include "../mathlib.h" -#include "TreeShape.h" +#include "tree_shape.h" #include "../types.h" #include <math.h> diff --git a/themes/src/autumn/LeafParticleRender.h b/themes/src/autumn/leaf_particle_render.h index f6efe1f..1209e1b 100644 --- a/themes/src/autumn/LeafParticleRender.h +++ b/themes/src/autumn/leaf_particle_render.h @@ -1,4 +1,4 @@ -#include "../Renderer2d.h" +#include "../renderer_2d.h" #include "../mathlib.h" #include "../types.h" diff --git a/themes/src/autumn/TreeShape.cpp b/themes/src/autumn/tree_shape.cpp index 7c80929..622751b 100644 --- a/themes/src/autumn/TreeShape.cpp +++ b/themes/src/autumn/tree_shape.cpp @@ -1,4 +1,4 @@ -#include "TreeShape.h" +#include "tree_shape.h" #include "../mathlib.h" #include <cstdio> #include <cstdlib> diff --git a/themes/src/autumn/TreeShape.h b/themes/src/autumn/tree_shape.h index fc0d11e..0d18415 100644 --- a/themes/src/autumn/TreeShape.h +++ b/themes/src/autumn/tree_shape.h @@ -1,4 +1,4 @@ -#include "../Renderer2d.h" +#include "../renderer_2d.h" #include "../types.h" #include "../mathlib.h" 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 <cstdlib> #include <cstring> -#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 index 1068d88..bead282 100644 --- a/themes/src/Logger.cpp +++ b/themes/src/logger.cpp @@ -1,4 +1,4 @@ -#include "Logger.h" +#include "logger.h" #include <chrono> #include <cstdarg> #include <cstdio> diff --git a/themes/src/Logger.h b/themes/src/logger.h index 7596b6f..7596b6f 100644 --- a/themes/src/Logger.h +++ b/themes/src/logger.h diff --git a/themes/src/main.cpp b/themes/src/main.cpp index 14227c9..ec7630b 100644 --- a/themes/src/main.cpp +++ b/themes/src/main.cpp @@ -1,112 +1,132 @@ -#include "WebglContext.h" -#include "MainLoop.h" -#include "Renderer2d.h" +#include "autumn/autumn_theme.hpp" +#include "main_loop.h" #include "mathlib.h" +#include "renderer_2d.h" +#include "spring/spring_theme.hpp" +#include "summer/summer_theme.h" #include "theme.h" #include "types.h" -#include "summer/SummerTheme.h" -#include "autumn/AutumnTheme.hpp" -#include "spring/SpringTheme.hpp" -#include "winter/WinterTheme.hpp" +#include "webgl_context.h" +#include "winter/winter_theme.hpp" #include <cstdio> #include <emscripten/fetch.h> void load(ThemeType theme); void unload(); -void update(f32 dtSeconds, void* userData); -EM_BOOL selectNone(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData); -EM_BOOL selectAutumn(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData); -EM_BOOL selectWinter(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData); -EM_BOOL selectSpring(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData); -EM_BOOL selectSummer(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData); +void update(f32 dtSeconds, void *userData); +EM_BOOL selectNone(int eventType, const EmscriptenMouseEvent *mouseEvent, + void *userData); +EM_BOOL selectAutumn(int eventType, const EmscriptenMouseEvent *mouseEvent, + void *userData); +EM_BOOL selectWinter(int eventType, const EmscriptenMouseEvent *mouseEvent, + void *userData); +EM_BOOL selectSpring(int eventType, const EmscriptenMouseEvent *mouseEvent, + void *userData); +EM_BOOL selectSummer(int eventType, const EmscriptenMouseEvent *mouseEvent, + void *userData); WebglContext context; MainLoop mainLoop; ThemeType type; -Theme* active_theme; +Theme *active_theme; int main() { - context.init("#theme_canvas"); - emscripten_set_click_callback("#theme_button_default", NULL, false, selectNone); - emscripten_set_click_callback("#theme_button_autumn", NULL, false, selectAutumn); - emscripten_set_click_callback("#theme_button_winter", NULL, false, selectWinter); - emscripten_set_click_callback("#theme_button_spring", NULL, false, selectSpring); - emscripten_set_click_callback("#theme_button_summer", NULL, false, selectSummer); - - return 0; + context.init("#theme_canvas"); + emscripten_set_click_callback("#theme_button_default", NULL, false, + selectNone); + emscripten_set_click_callback("#theme_button_autumn", NULL, false, + selectAutumn); + emscripten_set_click_callback("#theme_button_winter", NULL, false, + selectWinter); + emscripten_set_click_callback("#theme_button_spring", NULL, false, + selectSpring); + emscripten_set_click_callback("#theme_button_summer", NULL, false, + selectSummer); + + return 0; } // -- Scene loading, updating, and unloading logic void load(ThemeType theme) { - if (type == theme) { - printf("This theme is already active.\n"); - return; - } - - unload(); // Try and unload before we load, so that we start fresh - - type = theme; - mainLoop.run(update); - - switch (type) { - case ThemeType::Autumn: - active_theme = new AutumnTheme(&context); - break; - case ThemeType::Winter: - active_theme = new WinterTheme(&context); - break; - case ThemeType::Spring: - active_theme = new SpringTheme(&context); - break; - case ThemeType::Summer: - active_theme = new SummerTheme(&context); - break; - default: - break; - } + if (type == theme) { + printf("This theme is already active.\n"); + return; + } + + unload(); // Try and unload before we load, so that we start fresh + + type = theme; + mainLoop.run(update); + + switch (type) { + case ThemeType::Autumn: + active_theme = new AutumnTheme(&context); + break; + case ThemeType::Winter: + active_theme = new WinterTheme(&context); + break; + case ThemeType::Spring: + active_theme = new SpringTheme(&context); + break; + case ThemeType::Summer: + active_theme = new SummerTheme(&context); + break; + default: + break; + } } -void update(f32 dtSeconds, void* userData) { - active_theme->update(dtSeconds); - active_theme->render(); +void update(f32 dtSeconds, void *userData) { + if (!active_theme) + return; + active_theme->update(dtSeconds); + active_theme->render(); } void unload() { - delete active_theme; - - type = ThemeType::Default; - if (mainLoop.isRunning) { - mainLoop.stop(); - } + delete active_theme; + active_theme = nullptr; + + type = ThemeType::Default; + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + if (mainLoop.isRunning) { + mainLoop.stop(); + } } // -- HTML5 callbacks -EM_BOOL selectNone(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData) { - printf("Default theme selected\n"); - unload(); - return true; +EM_BOOL selectNone(int eventType, const EmscriptenMouseEvent *mouseEvent, + void *userData) { + printf("Default theme selected\n"); + unload(); + return true; } -EM_BOOL selectAutumn(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData) { - printf("Autumn theme selected\n"); - load(ThemeType::Autumn); - return true; +EM_BOOL selectAutumn(int eventType, const EmscriptenMouseEvent *mouseEvent, + void *userData) { + printf("Autumn theme selected\n"); + load(ThemeType::Autumn); + return true; } -EM_BOOL selectWinter(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData) { - printf("Winter theme selected\n"); - load(ThemeType::Winter); - return true; +EM_BOOL selectWinter(int eventType, const EmscriptenMouseEvent *mouseEvent, + void *userData) { + printf("Winter theme selected\n"); + load(ThemeType::Winter); + return true; } -EM_BOOL selectSpring(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData) { - printf("Spring theme selected\n"); - load(ThemeType::Spring); - return true; +EM_BOOL selectSpring(int eventType, const EmscriptenMouseEvent *mouseEvent, + void *userData) { + printf("Spring theme selected\n"); + load(ThemeType::Spring); + return true; } -EM_BOOL selectSummer(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData) { - printf("Summer theme selected\n"); - load(ThemeType::Summer); - return true; +EM_BOOL selectSummer(int eventType, const EmscriptenMouseEvent *mouseEvent, + void *userData) { + printf("Summer theme selected\n"); + load(ThemeType::Summer); + return true; } diff --git a/themes/src/main_loop.cpp b/themes/src/main_loop.cpp new file mode 100644 index 0000000..743892e --- /dev/null +++ b/themes/src/main_loop.cpp @@ -0,0 +1,37 @@ +#include "main_loop.h" +#include <cstdio> +#include <cstdlib> + +EM_BOOL loop(double time, void *loop) { + MainLoop *mainLoop = (MainLoop *)loop; + if (!mainLoop->isRunning) { + return false; + } + + if (mainLoop->lastTime == 0) { + mainLoop->lastTime = time; + return true; + } + + long deltaTime = time - mainLoop->lastTime; + mainLoop->lastTime = time; + mainLoop->elapsedTime += deltaTime; + mainLoop->numFrames++; + float deltaTimeSeconds = static_cast<float>(deltaTime) / 1000.f; + + if (mainLoop->elapsedTime >= 1000.0) { + printf("FPS: %d\n", mainLoop->numFrames); + + mainLoop->elapsedTime = 0.0; + mainLoop->numFrames = 0; + } + + // Ignore any update with a greater than 0.1 change. We were + // probably tabbed away, so this is uninteresting to us. + if (deltaTimeSeconds > 0.1) { + return true; + } + + mainLoop->updateFunc(deltaTimeSeconds, NULL); + return true; +} diff --git a/themes/src/MainLoop.h b/themes/src/main_loop.h index 2573bb8..07520a2 100644 --- a/themes/src/MainLoop.h +++ b/themes/src/main_loop.h @@ -26,4 +26,4 @@ struct MainLoop { void stop() { isRunning = false; } -};
\ No newline at end of file +}; diff --git a/themes/src/Renderer2d.cpp b/themes/src/renderer_2d.cpp index f1d78e3..7200669 100644 --- a/themes/src/Renderer2d.cpp +++ b/themes/src/renderer_2d.cpp @@ -1,6 +1,6 @@ -#include "Renderer2d.h" -#include "Shader.h" -#include "WebglContext.h" +#include "renderer_2d.h" +#include "shader.h" +#include "webgl_context.h" #include "mathlib.h" #include <cstdio> #include "shaders/renderer2d_vert.h" diff --git a/themes/src/renderer_2d.h b/themes/src/renderer_2d.h new file mode 100644 index 0000000..16c5cbe --- /dev/null +++ b/themes/src/renderer_2d.h @@ -0,0 +1,59 @@ +#pragma once + +#include "mathlib.h" +#include "shader.h" +#include "types.h" +#include "webgl_context.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/renderer_3d.cpp index 00315de..cc79940 100644 --- a/themes/src/Renderer3d.cpp +++ b/themes/src/renderer_3d.cpp @@ -1,9 +1,9 @@ -#include "Renderer3d.h" -#include "Shader.h" +#include "renderer_3d.h" +#include "shader.h" #include "list.h" #include "mathlib.h" -#include "WebglContext.h" -#include "Logger.h" +#include "webgl_context.h" +#include "logger.h" #include <cstdio> // Note: In the 'transform' attribute, the transform.x is the scale, diff --git a/themes/src/Renderer3d.h b/themes/src/renderer_3d.h index 5b2c8c8..5b2c8c8 100644 --- a/themes/src/Renderer3d.h +++ b/themes/src/renderer_3d.h diff --git a/themes/src/Shader.cpp b/themes/src/shader.cpp index 5f2b00e..ed2cab5 100644 --- a/themes/src/Shader.cpp +++ b/themes/src/shader.cpp @@ -1,4 +1,4 @@ -#include "Shader.h" +#include "shader.h" #include <string> GLuint loadIndividualShader(GLenum shaderType, const GLchar* cCode) { diff --git a/themes/src/Shader.h b/themes/src/shader.h index bc81764..bc81764 100644 --- a/themes/src/Shader.h +++ b/themes/src/shader.h diff --git a/themes/src/shaders/grass_frag.cpp b/themes/src/shaders/grass_frag.cpp new file mode 100644 index 0000000..5a62cf2 --- /dev/null +++ b/themes/src/shaders/grass_frag.cpp @@ -0,0 +1,14 @@ +#include "grass_frag.h" + +const char* shader_grass_frag = "varying lowp vec2 vUV; \n" +" \n" +"void main() { \n" +" lowp float halfWidth = 0.5 * (1.0 - vUV.y); \n" +" lowp float distFromCenter = abs(vUV.x - 0.5); \n" +" if (distFromCenter > halfWidth) discard; \n" +" \n" +" lowp vec3 baseColor = vec3(0.15, 0.45, 0.10); \n" +" lowp vec3 tipColor = vec3(0.40, 0.75, 0.20); \n" +" gl_FragColor = vec4(mix(baseColor, tipColor, vUV.y), 1.0); \n" +"} \n" +" \n"; diff --git a/themes/src/shaders/grass_frag.h b/themes/src/shaders/grass_frag.h new file mode 100644 index 0000000..16cc29a --- /dev/null +++ b/themes/src/shaders/grass_frag.h @@ -0,0 +1,4 @@ +#ifndef SHADER_GRASS_FRAG +#define SHADER_GRASS_FRAG +extern const char* shader_grass_frag; +#endif diff --git a/themes/src/shaders/grass_vert.cpp b/themes/src/shaders/grass_vert.cpp new file mode 100644 index 0000000..c9d2955 --- /dev/null +++ b/themes/src/shaders/grass_vert.cpp @@ -0,0 +1,28 @@ +#include "grass_vert.h" + +const char* shader_grass_vert = "attribute vec2 position; // Local quad vertex: x in [-0.5, 0.5], y in [0, 1] \n" +"attribute vec3 instancePos; // Per-instance: world-space base of blade \n" +"attribute float instancePhase; // Per-instance: random phase offset for sway \n" +"attribute float instanceHeight; // Per-instance: height scale multiplier \n" +" \n" +"uniform mat4 projection; \n" +"uniform mat4 view; \n" +"uniform float time; \n" +"uniform float bladeWidth; \n" +"uniform float bladeHeight; \n" +"uniform float swayAmount; \n" +" \n" +"varying lowp vec2 vUV; \n" +" \n" +"void main() { \n" +" vec3 cameraRight = vec3(view[0][0], view[1][0], view[2][0]); \n" +" float h = bladeHeight * instanceHeight; \n" +" float sway = sin(time * 1.5 + instancePhase) * swayAmount * position.y; \n" +" vec3 worldPos = instancePos \n" +" + cameraRight * (position.x + sway) * bladeWidth \n" +" + vec3(0.0, 1.0, 0.0) * position.y * h; \n" +" \n" +" gl_Position = projection * view * vec4(worldPos, 1.0); \n" +" vUV = vec2(position.x + 0.5, position.y); \n" +"} \n" +" \n"; diff --git a/themes/src/shaders/grass_vert.h b/themes/src/shaders/grass_vert.h new file mode 100644 index 0000000..7ab52b6 --- /dev/null +++ b/themes/src/shaders/grass_vert.h @@ -0,0 +1,4 @@ +#ifndef SHADER_GRASS_VERT +#define SHADER_GRASS_VERT +extern const char* shader_grass_vert; +#endif diff --git a/themes/src/shaders/snowflake_frag.cpp b/themes/src/shaders/snowflake_frag.cpp new file mode 100644 index 0000000..52d535d --- /dev/null +++ b/themes/src/shaders/snowflake_frag.cpp @@ -0,0 +1,76 @@ +#include "snowflake_frag.h" + +const char* shader_snowflake_frag = "// Procedural star fragment shader \n" +"varying lowp vec2 vUV; \n" +"varying lowp float vSeed; \n" +"varying lowp float vScale; \n" +" \n" +"const lowp float PI = 3.14159265359; \n" +" \n" +"// Simple hash function for deterministic randomness \n" +"lowp float hash(lowp float n) { \n" +" return fract(sin(n) * 43758.5453123); \n" +"} \n" +" \n" +"// Generate star pattern procedurally \n" +"lowp float starPattern(lowp vec2 uv) { \n" +" lowp float dist = length(uv); \n" +" lowp float angle = atan(uv.y, uv.x); \n" +" \n" +" // Number of star points (4 or 5) \n" +" lowp float numPoints = 4.0 + floor(hash(vSeed) * 2.0); \n" +" \n" +" // Create sharp star with triangular points \n" +" lowp float angleStep = 2.0 * PI / numPoints; \n" +" lowp float currentAngle = mod(angle + PI / numPoints, angleStep) - angleStep * 0.5; \n" +" \n" +" // Create triangular points - distance from center to edge varies linearly with angle \n" +" lowp float normalizedAngle = abs(currentAngle) / (angleStep * 0.5); \n" +" \n" +" // Outer tip radius and inner valley radius \n" +" lowp float tipRadius = 0.5; \n" +" lowp float valleyRadius = 0.15; \n" +" \n" +" // Linear interpolation creates sharp triangular points \n" +" lowp float rayEdge = mix(tipRadius, valleyRadius, normalizedAngle); \n" +" \n" +" // Hard cutoff for sharp edges \n" +" lowp float star = step(dist, rayEdge); \n" +" \n" +" return star; \n" +"} \n" +" \n" +"void main() { \n" +" lowp float pattern = starPattern(vUV); \n" +" \n" +" // Color variation based on seed - white and blue tints \n" +" lowp float colorVar = hash(vSeed * 3.0); \n" +" lowp vec3 starColor; \n" +" \n" +" if (colorVar < 0.5) { \n" +" // Pure white \n" +" starColor = vec3(1.0, 1.0, 1.0); \n" +" } else if (colorVar < 0.75) { \n" +" // Light blue tint \n" +" starColor = vec3(0.9, 0.95, 1.0); \n" +" } else { \n" +" // Stronger blue tint \n" +" starColor = vec3(0.85, 0.92, 1.0); \n" +" } \n" +" \n" +" // Scale alpha based on size - smaller stars are more transparent \n" +" // Normalize scale from range [16, 48] to [0, 1] \n" +" lowp float sizeRatio = (vScale - 16.0) / (48.0 - 16.0); \n" +" // Map to alpha range [0.3, 1.0] - smaller stars at 30% opacity, larger at 100% \n" +" lowp float alpha = mix(0.3, 1.0, sizeRatio); \n" +" \n" +" lowp vec4 color = vec4(starColor, pattern * alpha); \n" +" \n" +" // Discard fully transparent pixels for performance \n" +" if (color.a < 0.01) { \n" +" discard; \n" +" } \n" +" \n" +" gl_FragColor = color; \n" +"} \n" +" \n"; diff --git a/themes/src/shaders/snowflake_frag.h b/themes/src/shaders/snowflake_frag.h new file mode 100644 index 0000000..b34328f --- /dev/null +++ b/themes/src/shaders/snowflake_frag.h @@ -0,0 +1,4 @@ +#ifndef SHADER_SNOWFLAKE_FRAG +#define SHADER_SNOWFLAKE_FRAG +extern const char* shader_snowflake_frag; +#endif diff --git a/themes/src/shaders/snowflake_vert.cpp b/themes/src/shaders/snowflake_vert.cpp new file mode 100644 index 0000000..199009d --- /dev/null +++ b/themes/src/shaders/snowflake_vert.cpp @@ -0,0 +1,33 @@ +#include "snowflake_vert.h" + +const char* shader_snowflake_vert = "// Instanced snowflake vertex shader \n" +"attribute vec2 position; // Base quad vertex position \n" +"attribute vec2 instancePos; // Per-instance: snowflake center position \n" +"attribute float instanceRot; // Per-instance: rotation angle \n" +"attribute float instanceScale; // Per-instance: size scale \n" +"attribute float instanceSeed; // Per-instance: random seed for variation \n" +" \n" +"uniform mat4 projection; \n" +"uniform mat4 model; \n" +" \n" +"varying lowp vec2 vUV; // UV coordinates for fragment shader \n" +"varying lowp float vSeed; // Pass seed to fragment shader \n" +"varying lowp float vScale; // Pass scale to fragment shader \n" +" \n" +"void main() { \n" +" // Rotate and scale the base quad \n" +" float c = cos(instanceRot); \n" +" float s = sin(instanceRot); \n" +" mat2 rotation = mat2(c, s, -s, c); \n" +" \n" +" vec2 rotatedPos = rotation * (position * instanceScale); \n" +" vec2 worldPos = instancePos + rotatedPos; \n" +" \n" +" gl_Position = projection * model * vec4(worldPos, 0.0, 1.0); \n" +" \n" +" // Pass UV in range [-1, 1] for procedural generation \n" +" vUV = position; \n" +" vSeed = instanceSeed; \n" +" vScale = instanceScale; \n" +"} \n" +" \n"; diff --git a/themes/src/shaders/snowflake_vert.h b/themes/src/shaders/snowflake_vert.h new file mode 100644 index 0000000..36bf8b0 --- /dev/null +++ b/themes/src/shaders/snowflake_vert.h @@ -0,0 +1,4 @@ +#ifndef SHADER_SNOWFLAKE_VERT +#define SHADER_SNOWFLAKE_VERT +extern const char* shader_snowflake_vert; +#endif diff --git a/themes/src/shaders/sun_frag.cpp b/themes/src/shaders/sun_frag.cpp index d1ea160..696b3b9 100644 --- a/themes/src/shaders/sun_frag.cpp +++ b/themes/src/shaders/sun_frag.cpp @@ -1,8 +1,46 @@ #include "sun_frag.h" const char* shader_sun_frag = "varying lowp vec4 VertexColor; \n" +"varying lowp vec2 TexCoord; \n" +"uniform lowp float time; \n" +" \n" +"// Simple noise function for edge distortion \n" +"lowp float noise(lowp vec2 p) { \n" +" return sin(p.x * 10.0 + time) * cos(p.y * 10.0 + time * 0.5) * 0.5 + 0.5; \n" +"} \n" " \n" "void main() { \n" -" gl_FragColor = VertexColor; \n" +" // TexCoord is now normalized: center is (0,0), edges are at distance ~1 \n" +" lowp float dist = length(TexCoord); \n" +" \n" +" // Add animated noise to the edge \n" +" lowp float angle = atan(TexCoord.y, TexCoord.x); \n" +" lowp float wave = sin(angle * 8.0 + time * 2.0) * 0.05 + sin(angle * 4.0 - time * 1.5) * 0.03; \n" +" lowp float noiseValue = noise(TexCoord + time * 0.1) * 0.02; \n" +" \n" +" // Create soft edge using smoothstep - ensure fade reaches zero at the actual edge \n" +" lowp float innerEdge = 0.8; \n" +" lowp float outerEdge = 1.0; \n" +" lowp float alpha = 1.0 - smoothstep(innerEdge, outerEdge, dist); \n" +" \n" +" // Apply wave distortion to the edge \n" +" alpha *= 1.0 - smoothstep(0.85 + wave + noiseValue * 2.0, 1.0, dist); \n" +" \n" +" // Make edges more transparent but not too much \n" +" alpha = alpha * alpha; \n" +" \n" +" // Add slight glow effect at the edge \n" +" lowp float glow = smoothstep(0.5, 0.8, dist) * (1.0 - smoothstep(0.8, 1.0, dist)); \n" +" \n" +" // Create orange gradient from center \n" +" lowp vec3 orangeColor = vec3(1.0, 0.5, 0.1); \n" +" lowp float centerGradient = smoothstep(0.6, 0.0, dist); \n" +" lowp vec3 baseColor = mix(VertexColor.rgb, orangeColor, centerGradient * 0.6); \n" +" \n" +" // Mix in the glow with a brighter color \n" +" lowp vec3 glowColor = baseColor * 1.3; \n" +" lowp vec3 finalColor = mix(baseColor, glowColor, glow * 0.5); \n" +" \n" +" gl_FragColor = vec4(finalColor, VertexColor.a * alpha); \n" "} \n" " \n"; diff --git a/themes/src/shaders/sun_vert.cpp b/themes/src/shaders/sun_vert.cpp index ca617c0..bacf3a6 100644 --- a/themes/src/shaders/sun_vert.cpp +++ b/themes/src/shaders/sun_vert.cpp @@ -1,16 +1,25 @@ #include "sun_vert.h" const char* shader_sun_vert = " \n" -"attribute vec2 position; \n" -"attribute vec4 color; \n" -"attribute mat4 vMatrix; \n" -"uniform mat4 projection; \n" -"uniform mat4 model; \n" +"attribute vec2 position; \n" +"attribute vec4 color; \n" +"attribute mat4 vMatrix; \n" +"uniform mat4 projection; \n" +"uniform mat4 model; \n" "varying lowp vec4 VertexColor; \n" +"varying lowp vec2 TexCoord; \n" " \n" -"void main() { \n" -" vec4 fragmentPosition = projection * model * vMatrix * vec4(position.x, position.y, 0, 1); \n" -" gl_Position = fragmentPosition; \n" -" VertexColor = color; \n" +"void main() { \n" +" vec4 fragmentPosition = projection * model * vMatrix * vec4(position.x, position.y, 0, 1); \n" +" gl_Position = fragmentPosition; \n" +" VertexColor = color; \n" +" // Normalize the position - the center is at (0,0) and edge vertices are at distance 'radius' \n" +" // We want TexCoord to be in the range roughly [-1, 1] at the edges \n" +" lowp float maxDist = length(position); \n" +" if (maxDist > 0.1) { \n" +" TexCoord = position / maxDist; \n" +" } else { \n" +" TexCoord = vec2(0.0, 0.0); \n" +" } \n" "} \n" " \n"; 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 <cstdio> -#include <emscripten/fetch.h> - -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..e4a210c --- /dev/null +++ b/themes/src/spring/grass_renderer.cpp @@ -0,0 +1,138 @@ +#include "grass_renderer.hpp" +#include "../renderer_3d.h" +#include "../shader.h" +#include "../shaders/grass_frag.h" +#include "../shaders/grass_vert.h" +#include "mathlib.h" +#include <cmath> +#include <cstddef> + +void GrassRenderer::load(GrassRendererLoadData params, Renderer3d *renderer) { + bladeHeight = params.grassHeight; + + // Place blades randomly within a circle. Using r = R*sqrt(u) with a + // uniform u in [0,1] gives uniform areal density (no center clustering). + const f32 radius = fminf(params.area.x, params.area.y) * 0.5f; + for (i32 i = 0; i < NUM_GRASS_BLADES; i++) { + f32 r = radius * sqrtf(randomFloatBetween(0.f, 1.f)); + f32 theta = randomFloatBetween(0.f, 2.f * PI); + f32 x = params.origin.x + r * cosf(theta); + f32 z = params.origin.y + r * sinf(theta); + grassBlades[i].position = Vector3(x, 0, z); + grassBlades[i].top_offset = + Vector2(randomFloatBetween(0.f, 2.f * PI), // sway phase + randomFloatBetween(0.5f, 1.5f) // height scale + ); + } + + // Compile grass shader + shader = loadShader(shader_grass_vert, shader_grass_frag); + useShader(shader); + + // Attribute locations + attributes.position = getShaderAttribute(shader, "position"); + attributes.instancePos = getShaderAttribute(shader, "instancePos"); + attributes.instancePhase = getShaderAttribute(shader, "instancePhase"); + attributes.instanceHeight = getShaderAttribute(shader, "instanceHeight"); + + // Uniform locations + uniforms.projection = getShaderUniform(shader, "projection"); + uniforms.view = getShaderUniform(shader, "view"); + uniforms.time = getShaderUniform(shader, "time"); + uniforms.bladeWidth = getShaderUniform(shader, "bladeWidth"); + uniforms.bladeHeight = getShaderUniform(shader, "bladeHeight"); + uniforms.swayAmount = getShaderUniform(shader, "swayAmount"); + + // Base quad: two triangles forming a unit quad + // x in [-0.5, 0.5], y in [0, 1] + Vector2 quadVertices[] = {Vector2(-0.5f, 0.0f), Vector2(0.5f, 0.0f), + Vector2(0.5f, 1.0f), Vector2(-0.5f, 0.0f), + Vector2(0.5f, 1.0f), Vector2(-0.5f, 1.0f)}; + + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + + // Static quad VBO + glGenBuffers(1, &quadVbo); + glBindBuffer(GL_ARRAY_BUFFER, quadVbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), quadVertices, + GL_STATIC_DRAW); + glEnableVertexAttribArray(attributes.position); + glVertexAttribPointer(attributes.position, 2, GL_FLOAT, GL_FALSE, + sizeof(Vector2), (GLvoid *)0); + + // Dynamic instance VBO + glGenBuffers(1, &instanceVbo); + glBindBuffer(GL_ARRAY_BUFFER, instanceVbo); + glBufferData(GL_ARRAY_BUFFER, NUM_GRASS_BLADES * sizeof(GrassInstanceData), + NULL, GL_DYNAMIC_DRAW); + + // instancePos: vec3 (x, y, z) at offset 0 + glEnableVertexAttribArray(attributes.instancePos); + glVertexAttribPointer(attributes.instancePos, 3, GL_FLOAT, GL_FALSE, + sizeof(GrassInstanceData), + (GLvoid *)offsetof(GrassInstanceData, x)); + glVertexAttribDivisor(attributes.instancePos, 1); + + // instancePhase: float at offset 12 (after 3 floats) + glEnableVertexAttribArray(attributes.instancePhase); + glVertexAttribPointer(attributes.instancePhase, 1, GL_FLOAT, GL_FALSE, + sizeof(GrassInstanceData), + (GLvoid *)offsetof(GrassInstanceData, phaseOffset)); + glVertexAttribDivisor(attributes.instancePhase, 1); + + // instanceHeight: float at offset 16 (after phaseOffset) + glEnableVertexAttribArray(attributes.instanceHeight); + glVertexAttribPointer(attributes.instanceHeight, 1, GL_FLOAT, GL_FALSE, + sizeof(GrassInstanceData), + (GLvoid *)offsetof(GrassInstanceData, heightScale)); + glVertexAttribDivisor(attributes.instanceHeight, 1); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); +} + +void GrassRenderer::update(f32 dtSeconds) { time += dtSeconds; } + +void GrassRenderer::render(Renderer3d *renderer) { + useShader(shader); + setShaderMat4(uniforms.projection, renderer->projection); + setShaderMat4(uniforms.view, renderer->view); + setShaderFloat(uniforms.time, time); + setShaderFloat(uniforms.bladeWidth, bladeWidth); + setShaderFloat(uniforms.bladeHeight, bladeHeight); + setShaderFloat(uniforms.swayAmount, swayAmount); + + // Build and upload instance data + GrassInstanceData instanceData[NUM_GRASS_BLADES]; + for (i32 i = 0; i < NUM_GRASS_BLADES; i++) { + instanceData[i].x = grassBlades[i].position.x; + instanceData[i].y = grassBlades[i].position.y; + instanceData[i].z = grassBlades[i].position.z; + instanceData[i].phaseOffset = grassBlades[i].top_offset.x; + instanceData[i].heightScale = grassBlades[i].top_offset.y; + } + + glBindBuffer(GL_ARRAY_BUFFER, instanceVbo); + glBufferSubData(GL_ARRAY_BUFFER, 0, + NUM_GRASS_BLADES * sizeof(GrassInstanceData), instanceData); + + glBindVertexArray(vao); + glDrawArraysInstanced(GL_TRIANGLES, 0, 6, NUM_GRASS_BLADES); + glBindVertexArray(0); +} + +void GrassRenderer::unload() { + if (vao) + glDeleteVertexArrays(1, &vao); + if (quadVbo) + glDeleteBuffers(1, &quadVbo); + if (instanceVbo) + glDeleteBuffers(1, &instanceVbo); + if (shader) + glDeleteProgram(shader); + vao = 0; + quadVbo = 0; + instanceVbo = 0; + shader = 0; +} diff --git a/themes/src/spring/grass_renderer.hpp b/themes/src/spring/grass_renderer.hpp new file mode 100644 index 0000000..14ef067 --- /dev/null +++ b/themes/src/spring/grass_renderer.hpp @@ -0,0 +1,63 @@ +#ifndef GRASS_RENDERER_HPP +#define GRASS_RENDERER_HPP + +#include "../renderer_3d.h" +#include "mathlib.h" +#include "types.h" + +const i32 GRASS_BLADES_PER_ROW = 48; +const i32 GRASS_BLADES_PER_COL = 48; +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 = 4.f; +}; + +struct GrassUpdateData { + Vector3 position; + Vector2 top_offset; // top_offset.x stores per-blade sway phase offset +}; + +struct GrassInstanceData { + float x, y, z; + float phaseOffset; + float heightScale; +}; + +struct GrassRenderer { + GrassUpdateData grassBlades[NUM_GRASS_BLADES]; + + u32 vao = 0; + u32 quadVbo = 0; + u32 instanceVbo = 0; + u32 shader = 0; + f32 time = 0.f; + f32 bladeWidth = 1.5f; + f32 bladeHeight = 6.f; + f32 swayAmount = 0.3f; + + struct { + i32 position; + i32 instancePos; + i32 instancePhase; + i32 instanceHeight; + } attributes; + + struct { + i32 projection; + i32 view; + i32 time; + i32 bladeWidth; + i32 bladeHeight; + i32 swayAmount; + } uniforms; + + 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..4b62795 --- /dev/null +++ b/themes/src/spring/spring_theme.cpp @@ -0,0 +1,247 @@ +#include "spring_theme.hpp" +#include "../renderer_3d.h" +#include "../shader.h" +#include "../shader_fetcher.hpp" +#include "../shapes_2d.h" +#include <cstdio> +#include <emscripten/fetch.h> + +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, "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(174, 216, 230, 255.f).toNormalizedColor(); + + renderer2d.load(context); + background = new RectangularGradient( + renderer2d, Vector4(174, 216, 230, 255).toNormalizedColor(), + Vector4(144, 238, 144, 255).toNormalizedColor(), renderer2d.get_width(), + renderer2d.get_height(), {0, 0}); + + grassRenderer.load({Vector2(0, -20), Vector2(96, 96), 3.f}, &renderer); + + 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) { + if (state != SpringThemeState::Loading) { + grassRenderer.update(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)); + // Clamp bunnyTarget to within the grass circle (origin=(0,-20), radius=48) + const Vector3 grassCenter(0, 0, -20); + const f32 grassRadius = 48.f; + Vector3 toTarget = bunnyTarget - grassCenter; + toTarget.y = 0; + if (toTarget.length() > grassRadius) { + toTarget = toTarget.normalize() * grassRadius; + bunnyTarget = Vector3(grassCenter.x + toTarget.x, 0, grassCenter.z + toTarget.z); + } + 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(); + + // Draw the 2D gradient background without writing to the depth buffer so + // the 3D content rendered afterwards is unobstructed. + glDepthMask(GL_FALSE); + useShader(renderer2d.shader); + setShaderMat4(renderer2d.uniforms.projection, renderer2d.projection); + background->render(); + glDepthMask(GL_TRUE); + + if (state != SpringThemeState::Loading) { + grassRenderer.render(&renderer); + // Restore the 3D renderer's shader after the grass shader took over + useShader(renderer.shader); + setShaderMat4(renderer.uniforms.projection, renderer.projection); + setShaderMat4(renderer.uniforms.view, renderer.view); + bunnyMesh.render(&renderer); + } +} + +void SpringTheme::unload() { + renderer.unload(); + renderer2d.unload(); + delete background; + bunnyMesh.unload(); + grassRenderer.unload(); +} diff --git a/themes/src/spring/spring_theme.hpp b/themes/src/spring/spring_theme.hpp new file mode 100644 index 0000000..4ee5684 --- /dev/null +++ b/themes/src/spring/spring_theme.hpp @@ -0,0 +1,51 @@ +#ifndef SPRING_THEME_HPP +#define SPRING_THEME_HPP + +#include "../mathlib.h" +#include "../renderer_2d.h" +#include "../renderer_3d.h" +#include "../theme.h" +#include "../types.h" +#include "grass_renderer.hpp" + +class RectangularGradient; + +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; + GrassRenderer grassRenderer; + Renderer2d renderer2d; + RectangularGradient *background; + + void load(WebglContext *); + void update(f32 dtSeconds); + void render(); + void unload(); +}; + +#endif diff --git a/themes/src/summer/SummerTheme.cpp b/themes/src/summer/summer_theme.cpp index 1f76b56..6d2cfec 100644 --- a/themes/src/summer/SummerTheme.cpp +++ b/themes/src/summer/summer_theme.cpp @@ -1,5 +1,5 @@ -#include "SummerTheme.h" -#include "../Renderer2d.h" +#include "summer_theme.h" +#include "../renderer_2d.h" #include "../list.h" #include "../mathlib.h" #include "../shaders/sun_frag.h" @@ -59,19 +59,23 @@ void Sun::load(Renderer2d* renderer) { 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)); + + timeUniform = getShaderUniform(renderer->shader, "time"); + vertices.deallocate(); indices.deallocate(); } void Sun::update(f32 dtSeconds) { - + elapsedTime += dtSeconds; } void Sun::render(Renderer2d* renderer) { setShaderMat4(renderer->uniforms.model, mesh.model); + glUniform1f(timeUniform, elapsedTime); glBindVertexArray(mesh.vao); glDrawElements(GL_TRIANGLES, mesh.numIndices, GL_UNSIGNED_INT, 0); glBindVertexArray(0); diff --git a/themes/src/summer/SummerTheme.h b/themes/src/summer/summer_theme.h index 2ce6b7f..eb404fd 100644 --- a/themes/src/summer/SummerTheme.h +++ b/themes/src/summer/summer_theme.h @@ -1,6 +1,6 @@ #pragma once #include "../types.h" -#include "../Renderer2d.h" +#include "../renderer_2d.h" #include "../theme.h" #include <vector> @@ -8,6 +8,8 @@ struct Sun { f32 radius = 20.f; i32 sectors = 180; Mesh2D mesh; + f32 elapsedTime = 0.f; + i32 timeUniform = -1; void load(Renderer2d* renderer); void update(f32 dtSeconds); diff --git a/themes/src/webgl_context.cpp b/themes/src/webgl_context.cpp new file mode 100644 index 0000000..cba4b8c --- /dev/null +++ b/themes/src/webgl_context.cpp @@ -0,0 +1,46 @@ +#include "webgl_context.h" +#include <cstdio> + +EM_BOOL onResize(int eventType, const EmscriptenUiEvent *uiEvent, + void *userData) { + WebglContext *context = (WebglContext *)userData; + + f64 inWidth, inHeight; + emscripten_get_element_css_size(context->query, &inWidth, &inHeight); + + context->width = static_cast<f32>(inWidth); + context->height = static_cast<f32>(inHeight); + emscripten_set_canvas_element_size(context->query, context->width, context->height); + + return true; +} + +void WebglContext::init(const char *inQuery) { + strcpy(query, inQuery); + f64 inWidth, inHeight; + emscripten_get_element_css_size(query, &inWidth, &inHeight); + width = static_cast<f32>(inWidth); + height = static_cast<f32>(inHeight); + emscripten_set_canvas_element_size(query, width, height); + + EmscriptenWebGLContextAttributes attrs; + emscripten_webgl_init_context_attributes(&attrs); + + attrs.enableExtensionsByDefault = 1; + attrs.majorVersion = 3; + attrs.minorVersion = 0; + + context = emscripten_webgl_create_context(query, &attrs); + makeCurrentContext(); + + glClearColor(0, 0, 0, 0.0f); + + emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, this, false, + onResize); +}; + +void WebglContext::makeCurrentContext() { + emscripten_webgl_make_context_current(context); +}; + +void WebglContext::destroy() { emscripten_webgl_destroy_context(context); } diff --git a/themes/src/WebglContext.h b/themes/src/webgl_context.h index 1956092..1956092 100644 --- a/themes/src/WebglContext.h +++ b/themes/src/webgl_context.h 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 <cstdio> - -/* - - 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<Vertex2D>* 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<Vertex2D>* 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<f32>(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<f32>(renderer->context->width); - yMax = static_cast<f32>(renderer->context->height); - - vertices.deallocate(); - vertices.growDynamically = true; - - // Initialize each snow flake with its shape - for (i32 s = 0; s < numSnowflakes; s++) { - auto ud = &updateData[s]; - initFlake(this, ud); - } - - useShader(renderer->shader); - - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - - glGenBuffers(1, &vbo); - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBufferData(GL_ARRAY_BUFFER, vertices.numElements * sizeof(Vertex2D), &vertices.data[0], GL_DYNAMIC_DRAW); - - glEnableVertexAttribArray(renderer->attributes.position); - glVertexAttribPointer(renderer->attributes.position, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)0); - - glEnableVertexAttribArray(renderer->attributes.color); - glVertexAttribPointer(renderer->attributes.color, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)offsetof(Vertex2D, color)); - - for (i32 idx = 0; idx < 4; idx++) { - i32 offset = (4 * sizeof(f32)) * idx; - glEnableVertexAttribArray(renderer->attributes.vMatrix + idx); - glVertexAttribPointer(renderer->attributes.vMatrix + idx, - 4, - GL_FLOAT, - GL_FALSE, - sizeof(Vertex2D), - (GLvoid *)(offsetof(Vertex2D, vMatrix) + offset)); - } - - 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.cpp b/themes/src/winter/snowflake.cpp new file mode 100644 index 0000000..26b6549 --- /dev/null +++ b/themes/src/winter/snowflake.cpp @@ -0,0 +1,185 @@ +#include "snowflake.h" +#include "../renderer_2d.h" +#include "../mathlib.h" +#include "../list.h" +#include "../shader.h" +#include "../shaders/snowflake_vert.h" +#include "../shaders/snowflake_frag.h" +#include <cstdio> + +/* + + 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 Vector2 SCALE_RANGE = Vector2(16.f, 48.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 initFlake(SnowflakeParticleRenderer* renderer, SnowflakeUpdateData* ud) { + ud->scale = randomFloatBetween(SCALE_RANGE.x, SCALE_RANGE.y); + ud->seed = randomFloatBetween(0.f, 1000.f); + + // Scale velocity based on star size - larger stars fall faster + f32 sizeRatio = (ud->scale - SCALE_RANGE.x) / (SCALE_RANGE.y - SCALE_RANGE.x); + f32 velocityY = VELOCITY_RANGE_Y.x + (VELOCITY_RANGE_Y.y - VELOCITY_RANGE_Y.x) * (0.5f + sizeRatio * 0.5f); + + ud->velocity = Vector2(randomFloatBetween(VELOCITY_RANGE_X.x, VELOCITY_RANGE_X.y), velocityY); + ud->position = Vector2(randomFloatBetween(0, renderer->xMax), randomFloatBetween(renderer->yMax, 4 * renderer->yMax)); + ud->rotateVelocity = randomFloatBetween(ROTATION_VELOCITY_RANGE.x, ROTATION_VELOCITY_RANGE.y); + ud->rotation = 0.f; +} + +void SnowflakeParticleRenderer::load(SnowflakeLoadParameters params, Renderer2d* renderer) { + numSnowflakes = params.numSnowflakes; + updateData = new SnowflakeUpdateData[params.numSnowflakes]; + + xMax = static_cast<f32>(renderer->context->width); + yMax = static_cast<f32>(renderer->context->height); + + // Initialize each snowflake + for (i32 s = 0; s < numSnowflakes; s++) { + auto ud = &updateData[s]; + initFlake(this, ud); + } + + // Load custom snowflake shader + shader = loadShader(shader_snowflake_vert, shader_snowflake_frag); + useShader(shader); + + // Get attribute and uniform locations + attributes.position = getShaderAttribute(shader, "position"); + attributes.instancePos = getShaderAttribute(shader, "instancePos"); + attributes.instanceRot = getShaderAttribute(shader, "instanceRot"); + attributes.instanceScale = getShaderAttribute(shader, "instanceScale"); + attributes.instanceSeed = getShaderAttribute(shader, "instanceSeed"); + uniforms.projection = getShaderUniform(shader, "projection"); + uniforms.model = getShaderUniform(shader, "model"); + + // Create base quad geometry (just a square from -1 to 1) + Vector2 quadVertices[] = { + Vector2(-1.0f, -1.0f), + Vector2( 1.0f, -1.0f), + Vector2( 1.0f, 1.0f), + Vector2(-1.0f, -1.0f), + Vector2( 1.0f, 1.0f), + Vector2(-1.0f, 1.0f) + }; + + // Setup VAO + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + + // Create and setup quad VBO (static geometry) + glGenBuffers(1, &quadVbo); + glBindBuffer(GL_ARRAY_BUFFER, quadVbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), quadVertices, GL_STATIC_DRAW); + glEnableVertexAttribArray(attributes.position); + glVertexAttribPointer(attributes.position, 2, GL_FLOAT, GL_FALSE, sizeof(Vector2), (GLvoid*)0); + + // Create instance VBO (dynamic data) + glGenBuffers(1, &instanceVbo); + glBindBuffer(GL_ARRAY_BUFFER, instanceVbo); + glBufferData(GL_ARRAY_BUFFER, numSnowflakes * sizeof(SnowflakeInstanceData), NULL, GL_DYNAMIC_DRAW); + + // Setup instance attributes + glEnableVertexAttribArray(attributes.instancePos); + glVertexAttribPointer(attributes.instancePos, 2, GL_FLOAT, GL_FALSE, sizeof(SnowflakeInstanceData), (GLvoid*)offsetof(SnowflakeInstanceData, position)); + glVertexAttribDivisor(attributes.instancePos, 1); + + glEnableVertexAttribArray(attributes.instanceRot); + glVertexAttribPointer(attributes.instanceRot, 1, GL_FLOAT, GL_FALSE, sizeof(SnowflakeInstanceData), (GLvoid*)offsetof(SnowflakeInstanceData, rotation)); + glVertexAttribDivisor(attributes.instanceRot, 1); + + glEnableVertexAttribArray(attributes.instanceScale); + glVertexAttribPointer(attributes.instanceScale, 1, GL_FLOAT, GL_FALSE, sizeof(SnowflakeInstanceData), (GLvoid*)offsetof(SnowflakeInstanceData, scale)); + glVertexAttribDivisor(attributes.instanceScale, 1); + + glEnableVertexAttribArray(attributes.instanceSeed); + glVertexAttribPointer(attributes.instanceSeed, 1, GL_FLOAT, GL_FALSE, sizeof(SnowflakeInstanceData), (GLvoid*)offsetof(SnowflakeInstanceData, seed)); + glVertexAttribDivisor(attributes.instanceSeed, 1); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); +} + +inline void resetFlake(SnowflakeParticleRenderer* renderer, SnowflakeUpdateData* ud) { + ud->position.y = 2 * renderer->yMax; + + // Scale velocity based on star size - larger stars fall faster + f32 sizeRatio = (ud->scale - SCALE_RANGE.x) / (SCALE_RANGE.y - SCALE_RANGE.x); + f32 velocityY = VELOCITY_RANGE_Y.x + (VELOCITY_RANGE_Y.y - VELOCITY_RANGE_Y.x) * (0.5f + sizeRatio * 0.5f); + + ud->velocity = Vector2(randomFloatBetween(VELOCITY_RANGE_X.x, VELOCITY_RANGE_X.y), velocityY); + 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; + + if (ud->position.y <= -ud->scale) { + 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) { + useShader(shader); + setShaderMat4(uniforms.projection, renderer->projection); + setShaderMat4(uniforms.model, model); + + // Prepare instance data + SnowflakeInstanceData* instanceData = new SnowflakeInstanceData[numSnowflakes]; + for (i32 s = 0; s < numSnowflakes; s++) { + instanceData[s].position = updateData[s].position; + instanceData[s].rotation = updateData[s].rotation; + instanceData[s].scale = updateData[s].scale; + instanceData[s].seed = updateData[s].seed; + } + + // Upload instance data + glBindBuffer(GL_ARRAY_BUFFER, instanceVbo); + glBufferSubData(GL_ARRAY_BUFFER, 0, numSnowflakes * sizeof(SnowflakeInstanceData), instanceData); + + // Draw instanced + glBindVertexArray(vao); + glDrawArraysInstanced(GL_TRIANGLES, 0, 6, numSnowflakes); + glBindVertexArray(0); + + delete[] instanceData; +} + +void SnowflakeParticleRenderer::unload() { + glDeleteVertexArrays(1, &vao); + glDeleteBuffers(1, &quadVbo); + glDeleteBuffers(1, &instanceVbo); + glDeleteProgram(shader); + vao = 0; + quadVbo = 0; + instanceVbo = 0; + shader = 0; + delete[] updateData; +} diff --git a/themes/src/winter/Snowflake.h b/themes/src/winter/snowflake.h index ad027f6..e4118f6 100644 --- a/themes/src/winter/Snowflake.h +++ b/themes/src/winter/snowflake.h @@ -4,7 +4,7 @@ #include "../types.h" #include "../mathlib.h" #include "../list.h" -#include "Windfield.hpp" +#include "windfield.hpp" struct Renderer2d; struct Vertex2D; @@ -14,15 +14,20 @@ struct SnowflakeLoadParameters { f32 windIntervalSeconds = 1.5f; }; +struct SnowflakeInstanceData { + Vector2 position; + f32 rotation; + f32 scale; + f32 seed; +}; + struct SnowflakeUpdateData { Vector2 velocity; Vector2 position; f32 rotateVelocity = 0.f; f32 rotation = 0; - f32 radius; - - i32 vtxIdx = 0; - i32 numVertices = 0; + f32 scale; + f32 seed; }; struct SnowflakeParticleRenderer { @@ -35,10 +40,24 @@ struct SnowflakeParticleRenderer { SnowflakeUpdateData* updateData; u32 vao; - u32 vbo; + u32 quadVbo; // Base quad geometry + u32 instanceVbo; // Instance data (position, rotation, scale, seed) + u32 shader; // Custom snowflake shader Mat4x4 model; - matte::List<Vertex2D> vertices; - + + struct { + i32 position; + i32 instancePos; + i32 instanceRot; + i32 instanceScale; + i32 instanceSeed; + } attributes; + + struct { + i32 projection; + i32 model; + } uniforms; + void load(SnowflakeLoadParameters params, Renderer2d* renderer); void update(f32 dtSeconds); void render(Renderer2d* renderer); diff --git a/themes/src/winter/Windfield.cpp b/themes/src/winter/windfield.cpp index 88fb74b..f6c3be3 100644 --- a/themes/src/winter/Windfield.cpp +++ b/themes/src/winter/windfield.cpp @@ -1,4 +1,4 @@ -#include "Windfield.hpp" +#include "windfield.hpp" template <i32 Width, i32 Height, i32 CellDimension> diff --git a/themes/src/winter/Windfield.hpp b/themes/src/winter/windfield.hpp index 5bf0c38..5bf0c38 100644 --- a/themes/src/winter/Windfield.hpp +++ b/themes/src/winter/windfield.hpp diff --git a/themes/src/winter/WinterTheme.cpp b/themes/src/winter/winter_theme.cpp index 052670e..a628f18 100644 --- a/themes/src/winter/WinterTheme.cpp +++ b/themes/src/winter/winter_theme.cpp @@ -1,5 +1,5 @@ -#include "WinterTheme.hpp" -#include "../Renderer2d.h" +#include "winter_theme.hpp" +#include "../renderer_2d.h" WinterTheme::WinterTheme(WebglContext* context) { diff --git a/themes/src/winter/WinterTheme.hpp b/themes/src/winter/winter_theme.hpp index 5ba6d94..d1c3e05 100644 --- a/themes/src/winter/WinterTheme.hpp +++ b/themes/src/winter/winter_theme.hpp @@ -1,10 +1,10 @@ #ifndef WINTER_THEME_HPP #define WINTER_THEME_HPP -#include "Snowflake.h" +#include "snowflake.h" #include "../types.h" #include "../theme.h" -#include "../Renderer2d.h" +#include "../renderer_2d.h" struct WebglContext; |
