From 5d3ff36045bfb11af0f373d5ecee7c9b54e4c4f9 Mon Sep 17 00:00:00 2001 From: mattkae Date: Mon, 3 Apr 2023 06:35:27 -0400 Subject: Folderized all of the seasons and loading the shaders from a remote source --- themes/src/LeafParticleRender.cpp | 166 ------------------ themes/src/LeafParticleRender.h | 58 ------- themes/src/Renderer3d.cpp | 42 +---- themes/src/Renderer3d.h | 12 +- themes/src/Snowflake.cpp | 189 --------------------- themes/src/Snowflake.h | 48 ------ themes/src/SummerTheme.cpp | 67 -------- themes/src/SummerTheme.h | 23 --- themes/src/TreeShape.cpp | 214 ----------------------- themes/src/TreeShape.h | 74 -------- themes/src/Windfield.cpp | 28 --- themes/src/Windfield.hpp | 38 ----- themes/src/autumn/AutumnTheme.cpp | 23 +++ themes/src/autumn/AutumnTheme.hpp | 20 +++ 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/main.cpp | 283 +------------------------------ themes/src/shader_fetcher.cpp | 69 ++++++++ themes/src/shader_fetcher.hpp | 19 +++ themes/src/shaders/renderer2d.frag | 0 themes/src/shaders/renderer2d.vert | 0 themes/src/shaders/renderer3d.frag | 7 + themes/src/shaders/renderer3d.vert | 15 ++ themes/src/spring/GrassRenderer.cpp | 29 ++++ themes/src/spring/GrassRenderer.hpp | 33 ++++ themes/src/spring/SpringTheme.cpp | 198 +++++++++++++++++++++ themes/src/spring/SpringTheme.hpp | 41 +++++ themes/src/summer/SummerTheme.cpp | 67 ++++++++ themes/src/summer/SummerTheme.h | 23 +++ 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 | 20 +++ themes/src/winter/WinterTheme.hpp | 18 ++ 37 files changed, 1422 insertions(+), 1218 deletions(-) delete mode 100644 themes/src/LeafParticleRender.cpp delete mode 100644 themes/src/LeafParticleRender.h delete mode 100644 themes/src/Snowflake.cpp delete mode 100644 themes/src/Snowflake.h delete mode 100644 themes/src/SummerTheme.cpp delete mode 100644 themes/src/SummerTheme.h delete mode 100644 themes/src/TreeShape.cpp delete mode 100644 themes/src/TreeShape.h delete mode 100644 themes/src/Windfield.cpp delete mode 100644 themes/src/Windfield.hpp create mode 100644 themes/src/autumn/AutumnTheme.cpp create mode 100644 themes/src/autumn/AutumnTheme.hpp create mode 100644 themes/src/autumn/LeafParticleRender.cpp create mode 100644 themes/src/autumn/LeafParticleRender.h create mode 100644 themes/src/autumn/TreeShape.cpp create mode 100644 themes/src/autumn/TreeShape.h create mode 100644 themes/src/shader_fetcher.cpp create mode 100644 themes/src/shader_fetcher.hpp create mode 100644 themes/src/shaders/renderer2d.frag create mode 100644 themes/src/shaders/renderer2d.vert create mode 100644 themes/src/shaders/renderer3d.frag create mode 100644 themes/src/shaders/renderer3d.vert create mode 100644 themes/src/spring/GrassRenderer.cpp create mode 100644 themes/src/spring/GrassRenderer.hpp create mode 100644 themes/src/spring/SpringTheme.cpp create mode 100644 themes/src/spring/SpringTheme.hpp create mode 100644 themes/src/summer/SummerTheme.cpp create mode 100644 themes/src/summer/SummerTheme.h 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/WinterTheme.cpp create mode 100644 themes/src/winter/WinterTheme.hpp (limited to 'themes/src') diff --git a/themes/src/LeafParticleRender.cpp b/themes/src/LeafParticleRender.cpp deleted file mode 100644 index 0c6fbca..0000000 --- a/themes/src/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/LeafParticleRender.h b/themes/src/LeafParticleRender.h deleted file mode 100644 index 713d9f6..0000000 --- a/themes/src/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/Renderer3d.cpp b/themes/src/Renderer3d.cpp index 5f9ce88..00315de 100644 --- a/themes/src/Renderer3d.cpp +++ b/themes/src/Renderer3d.cpp @@ -8,32 +8,8 @@ // Note: In the 'transform' attribute, the transform.x is the scale, // transform.y is the rotation, and transform.zw is the translatiob. -const char* vertexShader = - "attribute vec4 position; \n" - "attribute vec4 color; \n" - "attribute vec4 normal; \n" - "uniform mat4 projection; \n" - "uniform mat4 view; \n" - "uniform mat4 model; \n" - "varying lowp vec4 VertexColor; \n" - "varying lowp vec4 VertexNormal; \n" - "void main() { \n" - " vec4 fragmentPosition = projection * view * model * position; \n" - " gl_Position = fragmentPosition; \n" - " VertexColor = color; \n" - " VertexNormal = normal; \n" - "}"; - -const char* fragmentShader = - "varying lowp vec4 VertexColor; \n" - "varying lowp vec4 VertexNormal; \n" - "void main() { \n" - " const lowp vec3 lightDirection = vec3(0.0, 1.0, 0.0);\n" - " gl_FragColor = vec4(VertexColor.xyz * dot(VertexNormal.xyz, lightDirection), 1); \n" - "}"; - EM_BOOL onScreenSizeChanged_3D(int eventType, const EmscriptenUiEvent *uiEvent, void *userData) { - Renderer3D* renderer = (Renderer3D*)userData; + Renderer3d* renderer = (Renderer3d*)userData; EMSCRIPTEN_RESULT result = emscripten_set_canvas_element_size( renderer->context->query, uiEvent->documentBodyClientWidth, uiEvent->documentBodyClientHeight); if (result != EMSCRIPTEN_RESULT_SUCCESS) { @@ -44,9 +20,9 @@ EM_BOOL onScreenSizeChanged_3D(int eventType, const EmscriptenUiEvent *uiEvent, return true; } -void Renderer3D::load(WebglContext* inContext) { +void Renderer3d::load(WebglContext* inContext, const char* vertexShader, const char* fragmentShader) { context = inContext; - printf("Compiling Renderer2d shader...\n"); + printf("Compiling Renderer3d shader...\n"); shader = loadShader(vertexShader, fragmentShader); useShader(shader); @@ -59,12 +35,12 @@ void Renderer3D::load(WebglContext* inContext) { 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("Renderer2d shader compiled.\n"); + logger_info("Renderer3d shader compiled.\n"); emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, this, false, onScreenSizeChanged_3D); } -void Renderer3D::render() { +void Renderer3d::render() { glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); glDepthMask(GL_TRUE); @@ -78,7 +54,7 @@ void Renderer3D::render() { setShaderMat4(uniforms.view, view); } -void Renderer3D::unload() { +void Renderer3d::unload() { glClearColor(0.f, 0.f, 0.f, 0.f); glClear(GL_COLOR_BUFFER_BIT); glDeleteProgram(shader); @@ -126,7 +102,7 @@ inline i32 readToken(i32 i, const char* content, char* output) { return i; } -Mesh3d Mesh3d_fromObj(Renderer3D* renderer, const char* content, const i32 len) { +Mesh3d Mesh3d_fromObj(Renderer3d* renderer, const char* content, const i32 len) { Mesh3d result; result.vertices.allocate(2048); result.indices.allocate(2048); @@ -218,7 +194,7 @@ Mesh3d Mesh3d_fromObj(Renderer3D* renderer, const char* content, const i32 len) return result; } -void Mesh3d::load(Renderer3D* renderer) { +void Mesh3d::load(Renderer3d* renderer) { glGenVertexArrays(1, &vao); glGenBuffers(1, &vbo); glGenBuffers(1, &ebo); @@ -254,7 +230,7 @@ void Mesh3d::unload() { indices.deallocate(); } -void Mesh3d::render(Renderer3D* renderer) { +void Mesh3d::render(Renderer3d* renderer) { setShaderMat4(renderer->uniforms.model, model); glBindVertexArray(vao); diff --git a/themes/src/Renderer3d.h b/themes/src/Renderer3d.h index 7e89c93..5b2c8c8 100644 --- a/themes/src/Renderer3d.h +++ b/themes/src/Renderer3d.h @@ -5,7 +5,7 @@ #include "types.h" #include -struct Renderer3D; +struct Renderer3d; struct Vertex3d { Vector4 position; @@ -21,13 +21,13 @@ struct Mesh3d { matte::List indices; Mat4x4 model; - void load(Renderer3D* renderer); - void render(Renderer3D* renderer); + void load(Renderer3d* renderer); + void render(Renderer3d* renderer); void unload(); }; struct WebglContext; -struct Renderer3D { +struct Renderer3d { WebglContext* context = NULL; Mat4x4 projection; Mat4x4 view; @@ -46,11 +46,11 @@ struct Renderer3D { i32 model; } uniforms; - void load(WebglContext* context); + 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); +Mesh3d Mesh3d_fromObj(Renderer3d* renderer, const char* content, const i32 len); #endif diff --git a/themes/src/Snowflake.cpp b/themes/src/Snowflake.cpp deleted file mode 100644 index 452a716..0000000 --- a/themes/src/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/Snowflake.h b/themes/src/Snowflake.h deleted file mode 100644 index c147469..0000000 --- a/themes/src/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 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/SummerTheme.cpp b/themes/src/SummerTheme.cpp deleted file mode 100644 index 20bb310..0000000 --- a/themes/src/SummerTheme.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#include "SummerTheme.h" -#include "Renderer2d.h" -#include "list.h" -#include "mathlib.h" -#include - -void SummerTheme::load(Renderer2d* renderer) { - renderer->clearColor = Vector4(0, 181, 286, 255.f).toNormalizedColor(); - sun.sectors = 180; - sun.radius = renderer->context->width / 4.f; - sun.load(renderer); -} - -void SummerTheme::update(f32 dtSeconds) { - sun.update(dtSeconds); -} - -void SummerTheme::render(Renderer2d* renderer) { - sun.render(renderer); -} - -void SummerTheme::unload() { - sun.unload(); -} - -void Sun::load(Renderer2d* renderer) { - matte::List 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/SummerTheme.h b/themes/src/SummerTheme.h deleted file mode 100644 index 1d9093a..0000000 --- a/themes/src/SummerTheme.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once -#include "types.h" -#include "Renderer2d.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(); -}; - -struct SummerTheme { - Sun sun; - void load(Renderer2d* renderer); - void update(f32 dtSeconds); - void render(Renderer2d* renderer); - void unload(); -}; diff --git a/themes/src/TreeShape.cpp b/themes/src/TreeShape.cpp deleted file mode 100644 index a3ae8f7..0000000 --- a/themes/src/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, 50.f }, 0, NULL, vertices, &lr); - - useShader(renderer->shader); - - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - - glGenBuffers(1, &vbo); - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBufferData(GL_ARRAY_BUFFER, numVertices * sizeof(Vertex2D), &vertices[0], GL_DYNAMIC_DRAW); - - glEnableVertexAttribArray(renderer->attributes.position); - glVertexAttribPointer(renderer->attributes.position, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)0); - - glEnableVertexAttribArray(renderer->attributes.color); - glVertexAttribPointer(renderer->attributes.color, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)offsetof(Vertex2D, color)); - - for (i32 idx = 0; idx < 4; idx++) { - glEnableVertexAttribArray(renderer->attributes.vMatrix + idx); - glVertexAttribPointer(renderer->attributes.vMatrix + idx, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)(offsetof(Vertex2D, vMatrix) + (idx * 16))); - glVertexAttribDivisor(renderer->attributes.vMatrix + idx, 1); - } - - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindVertexArray(0); - - delete [] generationData; - - return lr; -} - -const f32 ninetyDegreeRotation = PI / 2.f; - -void TreeShape::createBranch(TreeLoadData* ld, TreeBranchLoadData* generationData, i32 numBranches, i32* branchIndex, - i32 branchLevel, f32 width, f32 height, Vector2 position, f32 rotation, - TreeBranchUpdateData* parent, Vertex2D* vertices, TreeShapeLoadResult* lr) { - TreeBranchLoadData* branchLoadData = &generationData[*branchIndex]; - branchLoadData->width = width; - branchLoadData->height = height; - branchLoadData->position = position; - branchLoadData->rotation = rotation; - branchLoadData->fillVertices(&vertices[(*branchIndex) * 6], branchLevel); - - // Fil in the bounds for the LeafRenderer later. - if (branchLoadData->topMidpoint.x > lr->upperBounds.x) { - lr->upperBounds.x = branchLoadData->topMidpoint.x; - } - if (branchLoadData->topMidpoint.y > lr->upperBounds.y) { - lr->upperBounds.y = branchLoadData->topMidpoint.y; - } - if (branchLoadData->topMidpoint.x < lr->lowerBounds.x) { - lr->lowerBounds.x = branchLoadData->topMidpoint.x; - } - if (branchLoadData->topMidpoint.y < lr->lowerBounds.y) { - lr->lowerBounds.y = branchLoadData->topMidpoint.y; - } - - TreeBranchUpdateData* branchUpdateData = &updateData[*branchIndex]; - branchUpdateData->tier = branchLevel; - branchUpdateData->periodOffset = randomFloatBetween(0.f, 2.f * PI); - branchUpdateData->period = randomFloatBetween(3.f, 5.f); - branchUpdateData->amplitude = randomFloatBetween(0.01f, 0.05f); - branchUpdateData->branchToFollow = parent; - branchUpdateData->vertices = &vertices[(*branchIndex) * 6]; - - if (branchLevel == ld->numBranchLevels) { - return; - } - - for (int division = 0; division < ld->divisionsPerBranch; division++) { - // Weight between [0, 1] - float weight = static_cast(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/TreeShape.h b/themes/src/TreeShape.h deleted file mode 100644 index 32b00d3..0000000 --- a/themes/src/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/Windfield.cpp b/themes/src/Windfield.cpp deleted file mode 100644 index 3a7563f..0000000 --- a/themes/src/Windfield.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "Windfield.hpp" - - -template -void WindField::load(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/Windfield.hpp b/themes/src/Windfield.hpp deleted file mode 100644 index 5935c5d..0000000 --- a/themes/src/Windfield.hpp +++ /dev/null @@ -1,38 +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. - */ -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/autumn/AutumnTheme.cpp b/themes/src/autumn/AutumnTheme.cpp new file mode 100644 index 0000000..6e6fe2b --- /dev/null +++ b/themes/src/autumn/AutumnTheme.cpp @@ -0,0 +1,23 @@ +#include "AutumnTheme.hpp" +#include "../Renderer2d.h" + +void AutumnTheme::load(Renderer2d* renderer) { + renderer->clearColor = Vector4(252, 210, 153, 255).toNormalizedColor(); + auto lr = tree.load(renderer); + leafParticles.load(renderer, &lr); +} + +void AutumnTheme::update(f32 dtSeconds) { + tree.update(dtSeconds); + leafParticles.update(dtSeconds); +} + +void AutumnTheme::render(Renderer2d* renderer) { + tree.render(renderer); + leafParticles.render(renderer); +} + +void AutumnTheme::unload() { + tree.unload(); + leafParticles.unload(); +} \ No newline at end of file diff --git a/themes/src/autumn/AutumnTheme.hpp b/themes/src/autumn/AutumnTheme.hpp new file mode 100644 index 0000000..18da959 --- /dev/null +++ b/themes/src/autumn/AutumnTheme.hpp @@ -0,0 +1,20 @@ +#ifndef AUTUMN_THEME_HPP +#define AUTUMN_THEME_HPP + +#include "TreeShape.h" +#include "LeafParticleRender.h" +#include "../types.h" + +struct Renderer2d; + +struct AutumnTheme { + TreeShape tree; + LeafParticleRender leafParticles; + + void load(Renderer2d* renderer); + void update(f32 dtSeconds); + void render(Renderer2d* renderer); + void unload(); +}; + +#endif \ No newline at end of file diff --git a/themes/src/autumn/LeafParticleRender.cpp b/themes/src/autumn/LeafParticleRender.cpp new file mode 100644 index 0000000..fee3df2 --- /dev/null +++ b/themes/src/autumn/LeafParticleRender.cpp @@ -0,0 +1,166 @@ +#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 new file mode 100644 index 0000000..f6efe1f --- /dev/null +++ b/themes/src/autumn/LeafParticleRender.h @@ -0,0 +1,58 @@ +#include "../Renderer2d.h" +#include "../mathlib.h" +#include "../types.h" + +struct TreeShapeLoadResult; + +struct LeafParticleLoadData { + Vector2 initPosition; + Vector4 initColor; + int numLeaves = 48; +}; + +enum LeafParticleState { + OnTree, + Falling, + OnGround, + Remerging +}; + +struct LeafParticleUpdateData { + LeafParticleState state = LeafParticleState::Remerging; + + Vertex2D* vertexToFollow = NULL; + Vector4 color = Vector4(1.f, 0.f, 0.f, 0.f); + f32 scale = 1.f; + + f32 timeElapsedSeconds = 0.f; + i32 fallChance = -1; + Vector2 fallPosition; + f32 fallVerticalVelocity; + f32 fallHorizontalFrequency; + + f32 resetTime = 0.f; + + Vertex2D* vertexPtr = NULL; +}; + +struct LeafParticleRender { + f32 elapsedTimeSeconds = 0.5; + f32 fallIntervalSeconds = 1.f; + + // Update data + i32 numLeaves = 0; + + LeafParticleUpdateData* updateData = NULL; + Vertex2D* vertices = NULL; + + // Render data + u32 vao; + u32 vbo; + u32 numVertices = 0; + Mat4x4 model; + + void load(Renderer2d* renderer, TreeShapeLoadResult* lr); + void update(f32 dtSeconds); + void render(Renderer2d* renderer); + void unload(); +}; \ No newline at end of file diff --git a/themes/src/autumn/TreeShape.cpp b/themes/src/autumn/TreeShape.cpp new file mode 100644 index 0000000..9738fd5 --- /dev/null +++ b/themes/src/autumn/TreeShape.cpp @@ -0,0 +1,214 @@ +#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, 50.f }, 0, NULL, vertices, &lr); + + useShader(renderer->shader); + + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + + glGenBuffers(1, &vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, numVertices * sizeof(Vertex2D), &vertices[0], GL_DYNAMIC_DRAW); + + glEnableVertexAttribArray(renderer->attributes.position); + glVertexAttribPointer(renderer->attributes.position, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)0); + + glEnableVertexAttribArray(renderer->attributes.color); + glVertexAttribPointer(renderer->attributes.color, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)offsetof(Vertex2D, color)); + + for (i32 idx = 0; idx < 4; idx++) { + glEnableVertexAttribArray(renderer->attributes.vMatrix + idx); + glVertexAttribPointer(renderer->attributes.vMatrix + idx, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)(offsetof(Vertex2D, vMatrix) + (idx * 16))); + glVertexAttribDivisor(renderer->attributes.vMatrix + idx, 1); + } + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + + delete [] generationData; + + return lr; +} + +const f32 ninetyDegreeRotation = PI / 2.f; + +void TreeShape::createBranch(TreeLoadData* ld, TreeBranchLoadData* generationData, i32 numBranches, i32* branchIndex, + i32 branchLevel, f32 width, f32 height, Vector2 position, f32 rotation, + TreeBranchUpdateData* parent, Vertex2D* vertices, TreeShapeLoadResult* lr) { + TreeBranchLoadData* branchLoadData = &generationData[*branchIndex]; + branchLoadData->width = width; + branchLoadData->height = height; + branchLoadData->position = position; + branchLoadData->rotation = rotation; + branchLoadData->fillVertices(&vertices[(*branchIndex) * 6], branchLevel); + + // Fil in the bounds for the LeafRenderer later. + if (branchLoadData->topMidpoint.x > lr->upperBounds.x) { + lr->upperBounds.x = branchLoadData->topMidpoint.x; + } + if (branchLoadData->topMidpoint.y > lr->upperBounds.y) { + lr->upperBounds.y = branchLoadData->topMidpoint.y; + } + if (branchLoadData->topMidpoint.x < lr->lowerBounds.x) { + lr->lowerBounds.x = branchLoadData->topMidpoint.x; + } + if (branchLoadData->topMidpoint.y < lr->lowerBounds.y) { + lr->lowerBounds.y = branchLoadData->topMidpoint.y; + } + + TreeBranchUpdateData* branchUpdateData = &updateData[*branchIndex]; + branchUpdateData->tier = branchLevel; + branchUpdateData->periodOffset = randomFloatBetween(0.f, 2.f * PI); + branchUpdateData->period = randomFloatBetween(3.f, 5.f); + branchUpdateData->amplitude = randomFloatBetween(0.01f, 0.05f); + branchUpdateData->branchToFollow = parent; + branchUpdateData->vertices = &vertices[(*branchIndex) * 6]; + + if (branchLevel == ld->numBranchLevels) { + return; + } + + for (int division = 0; division < ld->divisionsPerBranch; division++) { + // Weight between [0, 1] + float weight = static_cast(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 new file mode 100644 index 0000000..fc0d11e --- /dev/null +++ b/themes/src/autumn/TreeShape.h @@ -0,0 +1,74 @@ +#include "../Renderer2d.h" +#include "../types.h" +#include "../mathlib.h" + +struct TreeLoadData { + f32 trunkHeight = 96.f; // Height of the trunk start + f32 trunkWidth = 32.f; // Width of the trunk start + f32 trunkHeightScalerMin = 0.7f; + f32 trunkHeightScalerMax = 0.8f; + f32 trunkWidthScalerMin = 0.35f; + f32 trunkWidthScalerMax = 0.75f; + i32 divisionsPerBranch = 2; // How many branches to split into at each branch split + i32 numBranchLevels = 8; // How many branch levels to display +}; + +struct TreeBranchLoadData { + f32 width = 0.f; + f32 height = 0.f; + Vector2 position; // Center point + f32 rotation = 0; // How much we are rotated off of the center point in radians + Vector4 color = Vector4(101,56,24, 1.f).toNormalizedColor(); + + // Calculated while filling in vertices + Vector2 bottomLeft; + Vector2 bottomRight; + Vector2 topLeft; + Vector2 topRight; + Vector2 topMidpoint; + + void fillVertices(Vertex2D* vertices, int branchTier); +}; + +struct TreeBranchUpdateData { + i32 tier = 0; + f32 periodOffset = 0; + f32 period = 0; + f32 amplitude = 0; + Vector2 currentOffset; + Vertex2D* vertices = NULL; + TreeBranchUpdateData* branchToFollow = NULL; +}; + +struct TreeShapeLoadResult { + Vector2 lowerBounds; + Vector2 upperBounds; + Vector2 center; + TreeBranchUpdateData* updateData; + u32 numBranches = 0; +}; + +struct TreeShape { + // Update data + TreeBranchUpdateData* updateData = NULL; + Vertex2D* vertices = NULL; + f32 timeElapsedSeconds = 0.f; + f32 animateTimePerTier = 1.f; + f32 animateStaggerPerTier = 0.2f; + u32 numBranches = 0; + + // Render data + u32 vao; + u32 vbo; + u32 numVertices = 0; + Mat4x4 model; + + TreeShapeLoadResult load(Renderer2d* renderer); + void createBranch(TreeLoadData* ld, TreeBranchLoadData* branchList, i32 numBranches, + i32* branchIndex, i32 branchLevel, f32 width, f32 height, + Vector2 position, f32 rotation, TreeBranchUpdateData* parent, Vertex2D* vertices, TreeShapeLoadResult* lr); + void update(f32 dtSeconds); + void render(Renderer2d* renderer); + void unload(); +}; + diff --git a/themes/src/main.cpp b/themes/src/main.cpp index f8771d4..4e1a646 100644 --- a/themes/src/main.cpp +++ b/themes/src/main.cpp @@ -1,13 +1,12 @@ #include "WebglContext.h" #include "MainLoop.h" #include "Renderer2d.h" -#include "Renderer3d.h" #include "mathlib.h" #include "types.h" -#include "TreeShape.h" -#include "SummerTheme.h" -#include "LeafParticleRender.h" -#include "Snowflake.h" +#include "summer/SummerTheme.h" +#include "autumn/AutumnTheme.hpp" +#include "spring/SpringTheme.hpp" +#include "winter/WinterTheme.hpp" #include #include @@ -20,55 +19,6 @@ enum Theme { Summer }; -struct AutumnTheme { - TreeShape tree; - LeafParticleRender leafParticles; - - void load(Renderer2d* renderer); - void update(f32 dtSeconds); - void render(Renderer2d* renderer); - void unload(); -}; - -struct WinterTheme { - SnowflakeParticleRenderer spr; - - void load(Renderer2d* renderer); - void update(f32 dtSeconds); - void render(Renderer2d* renderer); - void unload(); -}; - -enum class BunnyAnimationState { - Loading = 0, - Loaded, - PreHop, - Hopping, - Idle -}; - -struct SpringTheme { - BunnyAnimationState state; - f32 bunnySpeed = 5.f; - Vector3 bunnyPosition = Vector3(0, 0, 0); - Vector3 bunnyTarget = Vector3(0, 0, 0); - Vector3 hopIncrement = Vector3(0, 0, 0); - - f32 numHops = 0; - f32 hopCount = 0; - f32 bunnyHopAnimationTimer = 0.f; - f32 stateTimer = 0.f; - f32 bunnyRotation = 0.f; - f32 targetRotation = 0.f; - - Mesh3d bunnyMesh; - - void load(Renderer3D* renderer); - void update(f32 dtSeconds); - void render(Renderer3D* renderer); - void unload(); -}; - void load(Theme theme); void unload(); void update(f32 dtSeconds, void* userData); @@ -80,7 +30,6 @@ EM_BOOL selectSummer(int eventType, const EmscriptenMouseEvent* mouseEvent, void WebglContext context; Renderer2d renderer2d; -Renderer3D renderer3d; MainLoop mainLoop; Theme activeTheme = Theme::Default; AutumnTheme autumnTheme; @@ -120,10 +69,10 @@ void load(Theme theme) { renderer2d.load(&context); winterTheme.load(&renderer2d); break; - case Theme::Spring: - renderer3d.load(&context); - springTheme.load(&renderer3d); + case Theme::Spring: { + springTheme.load(&context); break; + } case Theme::Summer: renderer2d.load(&context); summerTheme.load(&renderer2d); @@ -163,8 +112,7 @@ void update(f32 dtSeconds, void* userData) { winterTheme.render(&renderer2d); break; case Theme::Spring: - renderer3d.render(); - springTheme.render(&renderer3d); + springTheme.render(); break; case Theme::Summer: renderer2d.render(); @@ -197,7 +145,6 @@ void unload() { if (mainLoop.isRunning) { mainLoop.stop(); renderer2d.unload(); - renderer3d.unload(); } } @@ -230,216 +177,4 @@ EM_BOOL selectSummer(int eventType, const EmscriptenMouseEvent* mouseEvent, void printf("Summer theme selected\n"); load(Theme::Summer); return true; -} - -// -- Autumn theme -void AutumnTheme::load(Renderer2d* renderer) { - renderer->clearColor = Vector4(252, 210, 153, 255).toNormalizedColor(); - auto lr = tree.load(renderer); - leafParticles.load(renderer, &lr); -} - -void AutumnTheme::update(f32 dtSeconds) { - tree.update(dtSeconds); - leafParticles.update(dtSeconds); -} - -void AutumnTheme::render(Renderer2d* renderer) { - tree.render(renderer); - leafParticles.render(renderer); -} - -void AutumnTheme::unload() { - tree.unload(); - leafParticles.unload(); -} - -// -- Winter theme -void WinterTheme::load(Renderer2d* renderer) { - renderer->clearColor = Vector4(200, 229, 239, 255).toNormalizedColor(); - SnowflakeLoadParameters lp; - spr.load(lp, renderer); -} - -void WinterTheme::update(f32 dtSeconds) { - spr.update(dtSeconds); -} - -void WinterTheme::render(Renderer2d* renderer) { - spr.render(renderer); -} - -void WinterTheme::unload() { - spr.unload(); -} - -// -- Spring theme -void onBunnySuccess(emscripten_fetch_t *fetch) { - springTheme.state = BunnyAnimationState::Loaded; - printf("Finished downloading %llu bytes from URL %s.\n", fetch->numBytes, fetch->url); - const i32 len = fetch->numBytes; - springTheme.bunnyMesh = Mesh3d_fromObj(&renderer3d, fetch->data, len); - // The data is now available at fetch->data[0] through fetch->data[fetch->numBytes-1]; - emscripten_fetch_close(fetch); // Free data associated with the fetch. -} - -void onBunnyFail(emscripten_fetch_t *fetch) { - printf("Downloading %s failed, HTTP failure status code: %d.\n", fetch->url, fetch->status); - emscripten_fetch_close(fetch); // Also free data on failure. -} - -void SpringTheme::load(Renderer3D* renderer) { - springTheme.state = BunnyAnimationState::Loading; - renderer->clearColor = Vector4(160, 231, 160, 255.f).toNormalizedColor(); - - emscripten_fetch_attr_t attr; - emscripten_fetch_attr_init(&attr); - strcpy(attr.requestMethod, "GET"); - attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; - attr.onsuccess = onBunnySuccess; - attr.onerror = onBunnyFail; - emscripten_fetch(&attr, "themes/resources/bunny.obj"); -} - -inline Vector3 bunnyLerp(Vector3& start, Vector3& target, f32 t) { - t = 3 * t *t - 2 * t * t * t; - return start + ((target - start) * t); -} - -inline f32 verticalHopLerp(f32 start, f32 target, f32 t) { - f32 ogt = t; - t = 3 * t *t - 2 * t * t * t; - if (ogt >= 0.5f) t = 1.f - t; - return start + ((target - start) * t); -} - -inline f32 rotationLerp(f32 start, f32 target, f32 t) { - return start + ((target - start) * t); -} - -void SpringTheme::update(f32 dtSeconds) { - switch (state) { - case BunnyAnimationState::Loading: return; - case BunnyAnimationState::Loaded: - state = BunnyAnimationState::Idle; - stateTimer = 0.f; - bunnyHopAnimationTimer = 0.f; - break; - case BunnyAnimationState::Idle: { - bunnyHopAnimationTimer += dtSeconds; - const f32 HOP_FREQUENCY = 6.f; - - if (bunnyHopAnimationTimer > stateTimer) { - state = BunnyAnimationState::PreHop; - f32 xDir = 1; - f32 yDir = 1; - if (bunnyTarget.x > 0) xDir = -1; - if (bunnyTarget.z > 0) yDir = -1; - bunnyTarget = bunnyPosition + Vector3(randomFloatBetween(0, xDir * 25), 0, randomFloatBetween(0, yDir * 25)); - auto direction = (bunnyTarget - bunnyPosition); - auto distance = direction.length(); - direction = direction.normalize(); - numHops = ceil(distance / HOP_FREQUENCY); - hopCount = 0; - - targetRotation = PI - atan2(direction.y, direction.x); - stateTimer = ((bunnyTarget - bunnyPosition).length() / bunnySpeed) / numHops; - bunnyHopAnimationTimer = 0.f; - hopIncrement = (bunnyTarget - bunnyPosition) / numHops; - } - break; - } - case BunnyAnimationState::PreHop: { - const f32 ROTATION_TIME = 0.5f; - bunnyHopAnimationTimer += dtSeconds; - f32 current = bunnyRotation + (targetRotation - bunnyRotation) * (bunnyHopAnimationTimer / ROTATION_TIME); - bunnyMesh.model = Mat4x4().rotate(0, current, 0).translate(bunnyPosition); - - if (bunnyHopAnimationTimer > ROTATION_TIME) { - bunnyRotation = targetRotation; - bunnyHopAnimationTimer = 0; - state = BunnyAnimationState::Hopping; - } - break; - } - case BunnyAnimationState::Hopping: { - bunnyHopAnimationTimer += dtSeconds; - f32 t = bunnyHopAnimationTimer / stateTimer; - - Vector3 nextPosition = bunnyPosition + hopIncrement; - auto renderPos = bunnyLerp(bunnyPosition, nextPosition, t); - if ((renderPos - nextPosition).length() < 0.01f) { - hopCount += 1; - bunnyHopAnimationTimer = 0.f; - bunnyPosition = nextPosition; - } - - renderPos.y = verticalHopLerp(0.f, 4.f, t); - - const f32 RMAX = PI / 16.f; - f32 zRotation = 0; - f32 start = 0.f; - f32 end = PI / 8.f; - f32 startTime = 0.f; - f32 endTime = 0.f; - bool disableRot = false; - - if (t >= 0.9f) { - disableRot = true; - } - else if (t >= 0.7f) { - start = -RMAX; - end = 0.f; - startTime = 0.7f; - endTime = 0.9f; - } - else if (t >= 0.50f) { - start = 0.f; - end = -RMAX; - startTime = 0.50f; - endTime = 0.70f; - } - else if (t >= 0.40f) { - disableRot = true; - } - else if (t >= 0.20f) { - start = RMAX; - end = 0.f; - startTime = 0.20f; - endTime = 0.40f; - } - else { - start = 0.f; - end = RMAX; - startTime = 0.f; - endTime = 0.20f; - } - - - if (!disableRot) { - f32 totalTime = endTime - startTime; - zRotation = rotationLerp(start, end, (totalTime - (endTime - t)) / totalTime); - } - - bunnyMesh.model = Mat4x4().getZRotationMatrix(zRotation).rotate(0, bunnyRotation, 0).translate(renderPos); - if (hopCount == numHops) { - bunnyPosition = bunnyTarget; - bunnyHopAnimationTimer = 0.f; - state = BunnyAnimationState::Idle; - stateTimer = randomFloatBetween(0.5f, 1.f); - } - break; - } - } -} - -void SpringTheme::render(Renderer3D* renderer) { - renderer->render(); - if (state != BunnyAnimationState::Loading) { - bunnyMesh.render(renderer); - } -} - -void SpringTheme::unload() { - bunnyMesh.unload(); -} +} \ No newline at end of file diff --git a/themes/src/shader_fetcher.cpp b/themes/src/shader_fetcher.cpp new file mode 100644 index 0000000..19ef983 --- /dev/null +++ b/themes/src/shader_fetcher.cpp @@ -0,0 +1,69 @@ +#include "shader_fetcher.hpp" +#include "types.h" +#include +#include + +struct FetchtimeData { + ShaderFetchResult result; + ShaderFetchPaths paths_data; + void (*cb)(ShaderFetchResult*); +}; + +void on_failure(emscripten_fetch_t *fetch) { + FetchtimeData* ftd = (FetchtimeData*)fetch->userData; + printf("Downloading %s failed, HTTP failure status code: %d.\n", fetch->url, fetch->status); + emscripten_fetch_close(fetch); // Also free data on failure. + ftd->cb(nullptr); +} + +void on_fragment_shader(emscripten_fetch_t *fetch) { + FetchtimeData* ftd = (FetchtimeData*)fetch->userData; + printf("Finished downloading %llu bytes from URL %s.\n", fetch->numBytes, fetch->url); + const i32 len = fetch->numBytes; + char* data = (char*)fetch->data; + data[len - 1] = '\0'; + ftd->result.fragment = data; + emscripten_fetch_close(fetch); // Free data associated with the fetch. + + ftd->cb(&ftd->result); + delete ftd; +} + +void on_vertex_shader(emscripten_fetch_t *fetch) { + + FetchtimeData* ftd = (FetchtimeData*)fetch->userData; + printf("Finished downloading %llu bytes from URL %s.\n", fetch->numBytes, fetch->url); + const i32 len = fetch->numBytes; + char* data = (char*)fetch->data; + data[len - 1] = '\0'; + + ftd->result.vertex = data; + emscripten_fetch_close(fetch); // Free data associated with the fetch. + + // Fetch fragment shader next + emscripten_fetch_attr_t attr; + emscripten_fetch_attr_init(&attr); + strcpy(attr.requestMethod, "GET"); + attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; + attr.onsuccess = on_fragment_shader; + attr.onerror = on_failure; + auto* request = emscripten_fetch(&attr, ftd->paths_data.fragment); + request->userData = ftd; +} + +void fetch_shader(ShaderFetchPaths paths, void (*cb)(ShaderFetchResult*), void* user_data) { + FetchtimeData* ftd = new FetchtimeData(); + ftd->cb = cb; + ftd->paths_data = paths; + ftd->result.user_data = user_data; + + // Fetch vertex shader + emscripten_fetch_attr_t attr; + emscripten_fetch_attr_init(&attr); + strcpy(attr.requestMethod, "GET"); + attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; + attr.onsuccess = on_vertex_shader; + attr.onerror = on_failure; + auto* request = emscripten_fetch(&attr, paths.vertex); + request->userData = ftd; +} \ No newline at end of file diff --git a/themes/src/shader_fetcher.hpp b/themes/src/shader_fetcher.hpp new file mode 100644 index 0000000..aef25b4 --- /dev/null +++ b/themes/src/shader_fetcher.hpp @@ -0,0 +1,19 @@ +#ifndef SHADER_FETCHER_HPP +#define SHADER_FETCHER_HPP + +#include + +struct ShaderFetchPaths { + const char* vertex; + const char* fragment; +}; + +struct ShaderFetchResult { + std::string vertex; + std::string fragment; + void* user_data; +}; + +void fetch_shader(ShaderFetchPaths, void (*cb)(ShaderFetchResult*), void* user_data = nullptr); + +#endif \ No newline at end of file diff --git a/themes/src/shaders/renderer2d.frag b/themes/src/shaders/renderer2d.frag new file mode 100644 index 0000000..e69de29 diff --git a/themes/src/shaders/renderer2d.vert b/themes/src/shaders/renderer2d.vert new file mode 100644 index 0000000..e69de29 diff --git a/themes/src/shaders/renderer3d.frag b/themes/src/shaders/renderer3d.frag new file mode 100644 index 0000000..2f50347 --- /dev/null +++ b/themes/src/shaders/renderer3d.frag @@ -0,0 +1,7 @@ +varying lowp vec4 VertexColor; +varying lowp vec4 VertexNormal; + +void main() { + const lowp vec3 lightDirection = vec3(0.0, 1.0, 0.0); + gl_FragColor = vec4(VertexColor.xyz * dot(VertexNormal.xyz, lightDirection), 1); +} diff --git a/themes/src/shaders/renderer3d.vert b/themes/src/shaders/renderer3d.vert new file mode 100644 index 0000000..026285f --- /dev/null +++ b/themes/src/shaders/renderer3d.vert @@ -0,0 +1,15 @@ +attribute vec4 position; +attribute vec4 color; +attribute vec4 normal; +uniform mat4 projection; +uniform mat4 view; +uniform mat4 model; +varying lowp vec4 VertexColor; +varying lowp vec4 VertexNormal; + +void main() { + vec4 fragmentPosition = projection * view * model * position; + gl_Position = fragmentPosition; + VertexColor = color; + VertexNormal = normal; +} diff --git a/themes/src/spring/GrassRenderer.cpp b/themes/src/spring/GrassRenderer.cpp new file mode 100644 index 0000000..b69d111 --- /dev/null +++ b/themes/src/spring/GrassRenderer.cpp @@ -0,0 +1,29 @@ +#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 new file mode 100644 index 0000000..8c96724 --- /dev/null +++ b/themes/src/spring/GrassRenderer.hpp @@ -0,0 +1,33 @@ +#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 new file mode 100644 index 0000000..abe8c6e --- /dev/null +++ b/themes/src/spring/SpringTheme.cpp @@ -0,0 +1,198 @@ +#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); +} + +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 new file mode 100644 index 0000000..0866921 --- /dev/null +++ b/themes/src/spring/SpringTheme.hpp @@ -0,0 +1,41 @@ +#ifndef SPRING_THEME_HPP +#define SPRING_THEME_HPP + +#include "../mathlib.h" +#include "../types.h" +#include "../Renderer3d.h" + + +enum class SpringThemeState { + Loading = 0, + LoadedShader, + LoadedBunny, + PreHop, + Hopping, + Idle +}; + +struct 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 \ No newline at end of file diff --git a/themes/src/summer/SummerTheme.cpp b/themes/src/summer/SummerTheme.cpp new file mode 100644 index 0000000..406cd22 --- /dev/null +++ b/themes/src/summer/SummerTheme.cpp @@ -0,0 +1,67 @@ +#include "SummerTheme.h" +#include "../Renderer2d.h" +#include "../list.h" +#include "../mathlib.h" +#include + +void SummerTheme::load(Renderer2d* renderer) { + renderer->clearColor = Vector4(0, 181, 286, 255.f).toNormalizedColor(); + sun.sectors = 180; + sun.radius = renderer->context->width / 4.f; + sun.load(renderer); +} + +void SummerTheme::update(f32 dtSeconds) { + sun.update(dtSeconds); +} + +void SummerTheme::render(Renderer2d* renderer) { + sun.render(renderer); +} + +void SummerTheme::unload() { + sun.unload(); +} + +void Sun::load(Renderer2d* renderer) { + matte::List 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 new file mode 100644 index 0000000..4a9f76b --- /dev/null +++ b/themes/src/summer/SummerTheme.h @@ -0,0 +1,23 @@ +#pragma once +#include "../types.h" +#include "../Renderer2d.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(); +}; + +struct SummerTheme { + Sun sun; + void load(Renderer2d* renderer); + void update(f32 dtSeconds); + void render(Renderer2d* renderer); + void unload(); +}; diff --git a/themes/src/winter/Snowflake.cpp b/themes/src/winter/Snowflake.cpp new file mode 100644 index 0000000..57f1a8f --- /dev/null +++ b/themes/src/winter/Snowflake.cpp @@ -0,0 +1,189 @@ +#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 new file mode 100644 index 0000000..ad027f6 --- /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..88fb74b --- /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/WinterTheme.cpp b/themes/src/winter/WinterTheme.cpp new file mode 100644 index 0000000..2686988 --- /dev/null +++ b/themes/src/winter/WinterTheme.cpp @@ -0,0 +1,20 @@ +#include "WinterTheme.hpp" +#include "../Renderer2d.h" + +void WinterTheme::load(Renderer2d* renderer) { + renderer->clearColor = Vector4(200, 229, 239, 255).toNormalizedColor(); + SnowflakeLoadParameters lp; + spr.load(lp, renderer); +} + +void WinterTheme::update(f32 dtSeconds) { + spr.update(dtSeconds); +} + +void WinterTheme::render(Renderer2d* renderer) { + spr.render(renderer); +} + +void WinterTheme::unload() { + spr.unload(); +} diff --git a/themes/src/winter/WinterTheme.hpp b/themes/src/winter/WinterTheme.hpp new file mode 100644 index 0000000..5b8cc95 --- /dev/null +++ b/themes/src/winter/WinterTheme.hpp @@ -0,0 +1,18 @@ +#ifndef WINTER_THEME_HPP +#define WINTER_THEME_HPP + +#include "Snowflake.h" +#include "../types.h" + +struct Renderer2d; + +struct WinterTheme { + SnowflakeParticleRenderer spr; + + void load(Renderer2d* renderer); + void update(f32 dtSeconds); + void render(Renderer2d* renderer); + void unload(); +}; + +#endif \ No newline at end of file -- cgit v1.2.1