summaryrefslogtreecommitdiff
path: root/themes/src/summer/water_model.cpp
blob: 3ad02bbed890756f235978177e17c4b6c5b229af (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
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; }
}