summaryrefslogtreecommitdiff
path: root/themes/src/summer/water_model.cpp
diff options
context:
space:
mode:
authorMatt Kosarek <matt.kosarek@canonical.com>2026-07-08 09:07:55 -0400
committerMatt Kosarek <matt.kosarek@canonical.com>2026-07-08 09:07:55 -0400
commitc8938fe29132a11126013ed1b010c3092f5645e4 (patch)
treef6a6cd0603f25d51c08e302cc4342c28f9e10806 /themes/src/summer/water_model.cpp
parent418396ee5100eda8f6bc036ea35dbbe44db2f91a (diff)
feature: improved summer scene
Diffstat (limited to 'themes/src/summer/water_model.cpp')
-rw-r--r--themes/src/summer/water_model.cpp127
1 files changed, 127 insertions, 0 deletions
diff --git a/themes/src/summer/water_model.cpp b/themes/src/summer/water_model.cpp
new file mode 100644
index 0000000..3ad02bb
--- /dev/null
+++ b/themes/src/summer/water_model.cpp
@@ -0,0 +1,127 @@
+#include "water_model.h"
+#include "../webgl_context.h"
+#include "../list.h"
+#include "../shaders/water_vert.h"
+#include "../shaders/water_frag.h"
+
+WaterModel::WaterModel(Vector3 inPosition, Vector3 inSize)
+ : position(inPosition), size(inSize) {}
+
+WaterModel::~WaterModel() { unload(); }
+
+void WaterModel::setSun(Vector3 direction, Vector4 color) {
+ sunDir = direction.normalize();
+ sunColor = color.toVector3();
+}
+
+void WaterModel::load(WebglContext* inContext) {
+ context = inContext;
+ shader = loadShader(shader_water_vert, shader_water_frag);
+ useShader(shader);
+
+ attributes.position = getShaderAttribute(shader, "position");
+ uniforms.projection = getShaderUniform(shader, "projection");
+ uniforms.view = getShaderUniform(shader, "view");
+ uniforms.model = getShaderUniform(shader, "model");
+ uniforms.time = getShaderUniform(shader, "time");
+ uniforms.amplitude = getShaderUniform(shader, "amplitude");
+ uniforms.cameraPos = getShaderUniform(shader, "cameraPos");
+ uniforms.sunDir = getShaderUniform(shader, "sunDir");
+ uniforms.sunColor = getShaderUniform(shader, "sunColor");
+
+ // Camera: forward almost horizontal with a slight downward pitch so the
+ // horizon sits near the vertical center and the plane fills the lower half.
+ cameraPos = Vector3(0.f, 5.f, 20.f);
+ projection = Mat4x4().getPerspectiveProjection(
+ 0.1f, 1000.f, 0.9f,
+ static_cast<f32>(context->width) / static_cast<f32>(context->height));
+ view = Mat4x4().getLookAt(cameraPos, Vector3(0.f, 4.f, -10.f), Vector3(0.f, 1.f, 0.f));
+ model = Mat4x4().translate(position);
+
+ // Sensible defaults; the theme overrides these with the actual sun via setSun().
+ sunDir = Vector3(0.f, 0.4f, -1.f).normalize();
+ sunColor = Vector3(1.0f, 0.95f, 0.75f);
+
+ // Build the grid in local XZ, centered on the origin, extents from size.
+ matte::List<Vector2> verts;
+ matte::List<u32> idx;
+ verts.allocate((RES + 1) * (RES + 1));
+ idx.allocate(RES * RES * 6);
+
+ for (i32 z = 0; z <= RES; z++) {
+ for (i32 x = 0; x <= RES; x++) {
+ f32 fx = ((static_cast<f32>(x) / RES) - 0.5f) * size.x;
+ f32 fz = ((static_cast<f32>(z) / RES) - 0.5f) * size.z;
+ verts.add(Vector2(fx, fz));
+ }
+ }
+
+ const i32 stride = RES + 1;
+ for (i32 z = 0; z < RES; z++) {
+ for (i32 x = 0; x < RES; x++) {
+ u32 tl = z * stride + x;
+ u32 tr = tl + 1;
+ u32 bl = (z + 1) * stride + x;
+ u32 br = bl + 1;
+ idx.add(tl); idx.add(bl); idx.add(tr);
+ idx.add(tr); idx.add(bl); idx.add(br);
+ }
+ }
+ numIndices = static_cast<i32>(idx.numElements);
+
+ glGenVertexArrays(1, &vao);
+ glBindVertexArray(vao);
+
+ glGenBuffers(1, &vbo);
+ glBindBuffer(GL_ARRAY_BUFFER, vbo);
+ glBufferData(GL_ARRAY_BUFFER, verts.numElements * sizeof(Vector2), &verts.data[0], GL_STATIC_DRAW);
+
+ glGenBuffers(1, &ebo);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx.numElements * sizeof(u32), &idx.data[0], GL_STATIC_DRAW);
+
+ glEnableVertexAttribArray(attributes.position);
+ glVertexAttribPointer(attributes.position, 2, GL_FLOAT, GL_FALSE, sizeof(Vector2), (GLvoid*)0);
+
+ glBindVertexArray(0);
+ verts.deallocate();
+ idx.deallocate();
+}
+
+void WaterModel::update(f32 dtSeconds) {
+ elapsedTime += dtSeconds;
+}
+
+void WaterModel::render() {
+ useShader(shader);
+
+ // Opaque, depth-tested. Wipe the depth buffer first: the 2D sun was drawn at
+ // z=0 (nearest) with depth writes on, so without this the LEQUAL test would
+ // reject every perspective water fragment. Clearing depth (not color) lets the
+ // opaque water paint over the lower half of the sun while still self-occluding.
+ glDisable(GL_BLEND);
+ glEnable(GL_DEPTH_TEST);
+ glDepthFunc(GL_LEQUAL);
+ glDepthMask(GL_TRUE);
+ glClear(GL_DEPTH_BUFFER_BIT);
+
+ setShaderMat4(uniforms.projection, projection);
+ setShaderMat4(uniforms.view, view);
+ setShaderMat4(uniforms.model, model);
+ setShaderFloat(uniforms.time, elapsedTime);
+ setShaderFloat(uniforms.amplitude, size.y);
+ glUniform3f(uniforms.cameraPos, cameraPos.x, cameraPos.y, cameraPos.z);
+ glUniform3f(uniforms.sunDir, sunDir.x, sunDir.y, sunDir.z);
+ glUniform3f(uniforms.sunColor, sunColor.x, sunColor.y, sunColor.z);
+
+ glBindVertexArray(vao);
+ glDrawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_INT, 0);
+ glBindVertexArray(0);
+}
+
+void WaterModel::unload() {
+ if (vao) { glDeleteVertexArrays(1, &vao); vao = 0; }
+ if (vbo) { glDeleteBuffers(1, &vbo); vbo = 0; }
+ if (ebo) { glDeleteBuffers(1, &ebo); ebo = 0; }
+ if (shader) { glDeleteProgram(shader); shader = 0; }
+}