summaryrefslogtreecommitdiff
path: root/themes/src/spring
diff options
context:
space:
mode:
Diffstat (limited to 'themes/src/spring')
-rw-r--r--themes/src/spring/grass_renderer.cpp145
-rw-r--r--themes/src/spring/grass_renderer.hpp58
-rw-r--r--themes/src/spring/spring_theme.cpp41
-rw-r--r--themes/src/spring/spring_theme.hpp64
4 files changed, 246 insertions, 62 deletions
diff --git a/themes/src/spring/grass_renderer.cpp b/themes/src/spring/grass_renderer.cpp
index 685f733..e4a210c 100644
--- a/themes/src/spring/grass_renderer.cpp
+++ b/themes/src/spring/grass_renderer.cpp
@@ -1,29 +1,138 @@
#include "grass_renderer.hpp"
#include "../renderer_3d.h"
+#include "../shader.h"
+#include "../shaders/grass_frag.h"
+#include "../shaders/grass_vert.h"
+#include "mathlib.h"
+#include <cmath>
+#include <cstddef>
-void GrassRenderer::load(GrassRendererLoadData params, Renderer3d* renderer) {
- 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::load(GrassRendererLoadData params, Renderer3d *renderer) {
+ bladeHeight = params.grassHeight;
+
+ // Place blades randomly within a circle. Using r = R*sqrt(u) with a
+ // uniform u in [0,1] gives uniform areal density (no center clustering).
+ const f32 radius = fminf(params.area.x, params.area.y) * 0.5f;
+ for (i32 i = 0; i < NUM_GRASS_BLADES; i++) {
+ f32 r = radius * sqrtf(randomFloatBetween(0.f, 1.f));
+ f32 theta = randomFloatBetween(0.f, 2.f * PI);
+ f32 x = params.origin.x + r * cosf(theta);
+ f32 z = params.origin.y + r * sinf(theta);
+ grassBlades[i].position = Vector3(x, 0, z);
+ grassBlades[i].top_offset =
+ Vector2(randomFloatBetween(0.f, 2.f * PI), // sway phase
+ randomFloatBetween(0.5f, 1.5f) // height scale
+ );
+ }
+
+ // Compile grass shader
+ shader = loadShader(shader_grass_vert, shader_grass_frag);
+ useShader(shader);
+
+ // Attribute locations
+ attributes.position = getShaderAttribute(shader, "position");
+ attributes.instancePos = getShaderAttribute(shader, "instancePos");
+ attributes.instancePhase = getShaderAttribute(shader, "instancePhase");
+ attributes.instanceHeight = getShaderAttribute(shader, "instanceHeight");
+
+ // Uniform locations
+ uniforms.projection = getShaderUniform(shader, "projection");
+ uniforms.view = getShaderUniform(shader, "view");
+ uniforms.time = getShaderUniform(shader, "time");
+ uniforms.bladeWidth = getShaderUniform(shader, "bladeWidth");
+ uniforms.bladeHeight = getShaderUniform(shader, "bladeHeight");
+ uniforms.swayAmount = getShaderUniform(shader, "swayAmount");
+
+ // Base quad: two triangles forming a unit quad
+ // x in [-0.5, 0.5], y in [0, 1]
+ Vector2 quadVertices[] = {Vector2(-0.5f, 0.0f), Vector2(0.5f, 0.0f),
+ Vector2(0.5f, 1.0f), Vector2(-0.5f, 0.0f),
+ Vector2(0.5f, 1.0f), Vector2(-0.5f, 1.0f)};
+
+ glGenVertexArrays(1, &vao);
+ glBindVertexArray(vao);
+
+ // Static quad VBO
+ glGenBuffers(1, &quadVbo);
+ glBindBuffer(GL_ARRAY_BUFFER, quadVbo);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), quadVertices,
+ GL_STATIC_DRAW);
+ glEnableVertexAttribArray(attributes.position);
+ glVertexAttribPointer(attributes.position, 2, GL_FLOAT, GL_FALSE,
+ sizeof(Vector2), (GLvoid *)0);
-void GrassRenderer::update(f32 seconds) {
-
+ // Dynamic instance VBO
+ glGenBuffers(1, &instanceVbo);
+ glBindBuffer(GL_ARRAY_BUFFER, instanceVbo);
+ glBufferData(GL_ARRAY_BUFFER, NUM_GRASS_BLADES * sizeof(GrassInstanceData),
+ NULL, GL_DYNAMIC_DRAW);
+
+ // instancePos: vec3 (x, y, z) at offset 0
+ glEnableVertexAttribArray(attributes.instancePos);
+ glVertexAttribPointer(attributes.instancePos, 3, GL_FLOAT, GL_FALSE,
+ sizeof(GrassInstanceData),
+ (GLvoid *)offsetof(GrassInstanceData, x));
+ glVertexAttribDivisor(attributes.instancePos, 1);
+
+ // instancePhase: float at offset 12 (after 3 floats)
+ glEnableVertexAttribArray(attributes.instancePhase);
+ glVertexAttribPointer(attributes.instancePhase, 1, GL_FLOAT, GL_FALSE,
+ sizeof(GrassInstanceData),
+ (GLvoid *)offsetof(GrassInstanceData, phaseOffset));
+ glVertexAttribDivisor(attributes.instancePhase, 1);
+
+ // instanceHeight: float at offset 16 (after phaseOffset)
+ glEnableVertexAttribArray(attributes.instanceHeight);
+ glVertexAttribPointer(attributes.instanceHeight, 1, GL_FLOAT, GL_FALSE,
+ sizeof(GrassInstanceData),
+ (GLvoid *)offsetof(GrassInstanceData, heightScale));
+ glVertexAttribDivisor(attributes.instanceHeight, 1);
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindVertexArray(0);
}
-void GrassRenderer::render(Renderer3d* renderer) {
+void GrassRenderer::update(f32 dtSeconds) { time += dtSeconds; }
+void GrassRenderer::render(Renderer3d *renderer) {
+ useShader(shader);
+ setShaderMat4(uniforms.projection, renderer->projection);
+ setShaderMat4(uniforms.view, renderer->view);
+ setShaderFloat(uniforms.time, time);
+ setShaderFloat(uniforms.bladeWidth, bladeWidth);
+ setShaderFloat(uniforms.bladeHeight, bladeHeight);
+ setShaderFloat(uniforms.swayAmount, swayAmount);
+
+ // Build and upload instance data
+ GrassInstanceData instanceData[NUM_GRASS_BLADES];
+ for (i32 i = 0; i < NUM_GRASS_BLADES; i++) {
+ instanceData[i].x = grassBlades[i].position.x;
+ instanceData[i].y = grassBlades[i].position.y;
+ instanceData[i].z = grassBlades[i].position.z;
+ instanceData[i].phaseOffset = grassBlades[i].top_offset.x;
+ instanceData[i].heightScale = grassBlades[i].top_offset.y;
+ }
+
+ glBindBuffer(GL_ARRAY_BUFFER, instanceVbo);
+ glBufferSubData(GL_ARRAY_BUFFER, 0,
+ NUM_GRASS_BLADES * sizeof(GrassInstanceData), instanceData);
+
+ glBindVertexArray(vao);
+ glDrawArraysInstanced(GL_TRIANGLES, 0, 6, NUM_GRASS_BLADES);
+ glBindVertexArray(0);
}
void GrassRenderer::unload() {
-
+ if (vao)
+ glDeleteVertexArrays(1, &vao);
+ if (quadVbo)
+ glDeleteBuffers(1, &quadVbo);
+ if (instanceVbo)
+ glDeleteBuffers(1, &instanceVbo);
+ if (shader)
+ glDeleteProgram(shader);
+ vao = 0;
+ quadVbo = 0;
+ instanceVbo = 0;
+ shader = 0;
}
diff --git a/themes/src/spring/grass_renderer.hpp b/themes/src/spring/grass_renderer.hpp
index 88879f3..14ef067 100644
--- a/themes/src/spring/grass_renderer.hpp
+++ b/themes/src/spring/grass_renderer.hpp
@@ -5,29 +5,59 @@
#include "mathlib.h"
#include "types.h"
-const i32 GRASS_BLADES_PER_ROW = 24;
-const i32 GRASS_BLADES_PER_COL = 24;
+const i32 GRASS_BLADES_PER_ROW = 48;
+const i32 GRASS_BLADES_PER_COL = 48;
const i32 NUM_GRASS_BLADES = GRASS_BLADES_PER_ROW * GRASS_BLADES_PER_COL;
struct GrassRendererLoadData {
- Vector2 origin = Vector2(0, 0);
- Vector2 area = Vector2(480, 480);
- f32 grassHeight = 12.f;
+ Vector2 origin = Vector2(0, 0);
+ Vector2 area = Vector2(480, 480);
+ f32 grassHeight = 4.f;
};
struct GrassUpdateData {
- Vector3 position;
- Vector2 top_offset;
+ Vector3 position;
+ Vector2 top_offset; // top_offset.x stores per-blade sway phase offset
+};
+
+struct GrassInstanceData {
+ float x, y, z;
+ float phaseOffset;
+ float heightScale;
};
struct GrassRenderer {
-
- GrassUpdateData grassBlades[NUM_GRASS_BLADES];
-
- void load(GrassRendererLoadData params, Renderer3d* renderer);
- void update(f32 dtSeconds);
- void render(Renderer3d* renderer);
- void unload();
+ GrassUpdateData grassBlades[NUM_GRASS_BLADES];
+
+ u32 vao = 0;
+ u32 quadVbo = 0;
+ u32 instanceVbo = 0;
+ u32 shader = 0;
+ f32 time = 0.f;
+ f32 bladeWidth = 1.5f;
+ f32 bladeHeight = 6.f;
+ f32 swayAmount = 0.3f;
+
+ struct {
+ i32 position;
+ i32 instancePos;
+ i32 instancePhase;
+ i32 instanceHeight;
+ } attributes;
+
+ struct {
+ i32 projection;
+ i32 view;
+ i32 time;
+ i32 bladeWidth;
+ i32 bladeHeight;
+ i32 swayAmount;
+ } uniforms;
+
+ void load(GrassRendererLoadData params, Renderer3d *renderer);
+ void update(f32 dtSeconds);
+ void render(Renderer3d *renderer);
+ void unload();
};
#endif
diff --git a/themes/src/spring/spring_theme.cpp b/themes/src/spring/spring_theme.cpp
index 8b09366..4b62795 100644
--- a/themes/src/spring/spring_theme.cpp
+++ b/themes/src/spring/spring_theme.cpp
@@ -1,6 +1,8 @@
#include "spring_theme.hpp"
#include "../renderer_3d.h"
+#include "../shader.h"
#include "../shader_fetcher.hpp"
+#include "../shapes_2d.h"
#include <cstdio>
#include <emscripten/fetch.h>
@@ -49,7 +51,15 @@ SpringTheme::~SpringTheme() { unload(); }
void SpringTheme::load(WebglContext *context) {
state = SpringThemeState::Loading;
renderer.context = context;
- renderer.clearColor = Vector4(160, 231, 160, 255.f).toNormalizedColor();
+ renderer.clearColor = Vector4(174, 216, 230, 255.f).toNormalizedColor();
+
+ renderer2d.load(context);
+ background = new RectangularGradient(
+ renderer2d, Vector4(174, 216, 230, 255).toNormalizedColor(),
+ Vector4(144, 238, 144, 255).toNormalizedColor(), renderer2d.get_width(),
+ renderer2d.get_height(), {0, 0});
+
+ grassRenderer.load({Vector2(0, -20), Vector2(96, 96), 3.f}, &renderer);
fetch_shader({"themes/src/_shaders/renderer3d.vert",
"themes/src/_shaders/renderer3d.frag"},
@@ -74,6 +84,9 @@ inline f32 rotationLerp(f32 start, f32 target, f32 t) {
}
void SpringTheme::update(f32 dtSeconds) {
+ if (state != SpringThemeState::Loading) {
+ grassRenderer.update(dtSeconds);
+ }
switch (state) {
case SpringThemeState::Loading:
return;
@@ -98,6 +111,15 @@ void SpringTheme::update(f32 dtSeconds) {
yDir = -1;
bunnyTarget = bunnyPosition + Vector3(randomFloatBetween(0, xDir * 25), 0,
randomFloatBetween(0, yDir * 25));
+ // Clamp bunnyTarget to within the grass circle (origin=(0,-20), radius=48)
+ const Vector3 grassCenter(0, 0, -20);
+ const f32 grassRadius = 48.f;
+ Vector3 toTarget = bunnyTarget - grassCenter;
+ toTarget.y = 0;
+ if (toTarget.length() > grassRadius) {
+ toTarget = toTarget.normalize() * grassRadius;
+ bunnyTarget = Vector3(grassCenter.x + toTarget.x, 0, grassCenter.z + toTarget.z);
+ }
auto direction = (bunnyTarget - bunnyPosition);
auto distance = direction.length();
direction = direction.normalize();
@@ -197,12 +219,29 @@ void SpringTheme::update(f32 dtSeconds) {
void SpringTheme::render() {
renderer.render();
+
+ // Draw the 2D gradient background without writing to the depth buffer so
+ // the 3D content rendered afterwards is unobstructed.
+ glDepthMask(GL_FALSE);
+ useShader(renderer2d.shader);
+ setShaderMat4(renderer2d.uniforms.projection, renderer2d.projection);
+ background->render();
+ glDepthMask(GL_TRUE);
+
if (state != SpringThemeState::Loading) {
+ grassRenderer.render(&renderer);
+ // Restore the 3D renderer's shader after the grass shader took over
+ useShader(renderer.shader);
+ setShaderMat4(renderer.uniforms.projection, renderer.projection);
+ setShaderMat4(renderer.uniforms.view, renderer.view);
bunnyMesh.render(&renderer);
}
}
void SpringTheme::unload() {
renderer.unload();
+ renderer2d.unload();
+ delete background;
bunnyMesh.unload();
+ grassRenderer.unload();
}
diff --git a/themes/src/spring/spring_theme.hpp b/themes/src/spring/spring_theme.hpp
index 6079958..4ee5684 100644
--- a/themes/src/spring/spring_theme.hpp
+++ b/themes/src/spring/spring_theme.hpp
@@ -2,44 +2,50 @@
#define SPRING_THEME_HPP
#include "../mathlib.h"
-#include "../types.h"
+#include "../renderer_2d.h"
#include "../renderer_3d.h"
#include "../theme.h"
+#include "../types.h"
+#include "grass_renderer.hpp"
+class RectangularGradient;
enum class SpringThemeState {
- Loading = 0,
- LoadedShader,
- LoadedBunny,
- PreHop,
- Hopping,
- Idle
+ Loading = 0,
+ LoadedShader,
+ LoadedBunny,
+ PreHop,
+ Hopping,
+ Idle
};
class SpringTheme : public Theme {
public:
- SpringTheme(WebglContext*);
- ~SpringTheme();
- Renderer3d renderer;
- SpringThemeState state;
- f32 bunnySpeed = 5.f;
- Vector3 bunnyPosition = Vector3(0, 0, 0);
- Vector3 bunnyTarget = Vector3(0, 0, 0);
- Vector3 hopIncrement = Vector3(0, 0, 0);
-
- f32 numHops = 0;
- f32 hopCount = 0;
- f32 bunnyHopAnimationTimer = 0.f;
- f32 stateTimer = 0.f;
- f32 bunnyRotation = 0.f;
- f32 targetRotation = 0.f;
-
- Mesh3d bunnyMesh;
-
- void load(WebglContext*);
- void update(f32 dtSeconds);
- void render();
- void unload();
+ SpringTheme(WebglContext *);
+ ~SpringTheme();
+ Renderer3d renderer;
+ SpringThemeState state;
+ f32 bunnySpeed = 5.f;
+ Vector3 bunnyPosition = Vector3(0, 0, 0);
+ Vector3 bunnyTarget = Vector3(0, 0, 0);
+ Vector3 hopIncrement = Vector3(0, 0, 0);
+
+ f32 numHops = 0;
+ f32 hopCount = 0;
+ f32 bunnyHopAnimationTimer = 0.f;
+ f32 stateTimer = 0.f;
+ f32 bunnyRotation = 0.f;
+ f32 targetRotation = 0.f;
+
+ Mesh3d bunnyMesh;
+ GrassRenderer grassRenderer;
+ Renderer2d renderer2d;
+ RectangularGradient *background;
+
+ void load(WebglContext *);
+ void update(f32 dtSeconds);
+ void render();
+ void unload();
};
#endif