Nimbin[12]?Games / Entropie / entropie/src/world/meadow.c

Entropie git · main

Entropie festival game; first steps

sdl c game entropie cmake · first commit 2026-10-10 · last commit 2026-11-02 (18 hours ago) · synced 10 hours ago

C 88.5% Markdown 9.1% GLSL 2.3%
git clone https://git.christianimmanuel.de/games/Entropie.gitwget https://git.christianimmanuel.de/games/Entropie/archive/Entropie.tar.gz
entropie/src/world/meadow.c 5.6 KB · 111 lines raw
#include "world/meadow.h"
#include "world/field.h"
#include "world/site.h"

#include <string.h>

#define TUFT_SPACING  0.42f  /* meters between tufts at full growth */
#define BLADES        4
#define BLADE_WIDTH   0.07f
#define DRAW_RANGE    40.0f  /* squares this close get drawn */
#define DROP_RANGE    100.0f /* ... this far away get dropped */
#define BUILDS_PER_FRAME 3

static float hash(int x, int z, int salt) {
    unsigned h = (unsigned)x * 374761393u + (unsigned)z * 668265263u + (unsigned)salt * 2246822519u;
    h = (h ^ (h >> 13)) * 1274126177u;
    h ^= h >> 16;
    return (float)(h & 0xffffu) / 65535.0f;
}

/* Something built (or lying) on the ground here? */
static bool covered(const World *world, float x, float z, float ground) {
    return World_groundHeight(world, x, z, ground + 4.0f) > ground + 0.05f;
}

static Mesh buildChunk(const World *world, int cx, int cz) {
    MeshBuilder b;
    MeshBuilder_init(&b);
    const float x0 = -TERRAIN_SIZE * 0.5f + (float)cx * MEADOW_CHUNK, z0 = -TERRAIN_SIZE * 0.5f + (float)cz * MEADOW_CHUNK;
    const int n = (int)(MEADOW_CHUNK / TUFT_SPACING);
    for (int iz = 0; iz < n; iz++) {
        for (int ix = 0; ix < n; ix++) {
            int gx = cx * n + ix, gz = cz * n + iz;
            float x = x0 + ((float)ix + hash(gx, gz, 1)) * TUFT_SPACING;
            float z = z0 + ((float)iz + hash(gx, gz, 2)) * TUFT_SPACING;
            if (fabsf(x) > TERRAIN_SIZE * 0.5f - 0.5f || fabsf(z) > TERRAIN_SIZE * 0.5f - 0.5f) continue;
            bool field = Field_inside(x, z); /* (the Kartoffelacker: knee high till it's mown) */
            float grow = field ? fmaxf(Site_grass(x, z), 0.9f) : Site_grass(x, z);
            if (hash(gx, gz, 3) > grow) continue;
            float ground = Terrain_baseHeight(&world->terrain, x, z);
            if (covered(world, x, z, ground)) continue;
            Vec3 color = Site_groundColor(x, z, Terrain_grassColor(x, z));
            float tall = (0.10f + 0.18f * hash(gx, gz, 4)) * (0.6f + 0.4f * grow);
            if (field) tall = lerpf(0.38f + 0.3f * hash(gx, gz, 4), 0.05f + 0.04f * hash(gx, gz, 4), Field_mownAt(world, x, z));
            for (int k = 0; k < BLADES; k++) {
                float a = 6.2832f * hash(gx, gz, 10 + k), lean = 0.06f + 0.1f * hash(gx, gz, 20 + k);
                float bx = x + (hash(gx, gz, 30 + k) - 0.5f) * 0.3f, bz = z + (hash(gx, gz, 40 + k) - 0.5f) * 0.3f;
                float h = tall * (0.7f + 0.5f * hash(gx, gz, 50 + k));
                Vec3 side = vec3(cosf(a) * BLADE_WIDTH * 0.5f, 0.0f, sinf(a) * BLADE_WIDTH * 0.5f);
                Vec3 out = vec3(-sinf(a) * lean, 0.0f, cosf(a) * lean);
                float gy = Terrain_baseHeight(&world->terrain, bx, bz) - 0.02f;
                Vec3 shade = vec3_scale(color, 0.85f + 0.25f * hash(gx, gz, 60 + k));
                Vec3 tip_color = vec3_add(vec3_scale(shade, 1.15f), vec3(0.05f, 0.05f, 0.0f));
                /* normal.y carries the height above the ground (the shader bends and flattens by it) */
                GLuint i0 = MeshBuilder_vertex(&b, vec3(bx - side.x, gy, bz - side.z), vec3(0.0f, 0.0f, 0.0f), shade);
                GLuint i1 = MeshBuilder_vertex(&b, vec3(bx + side.x, gy, bz + side.z), vec3(0.0f, 0.0f, 0.0f), shade);
                GLuint i2 = MeshBuilder_vertex(&b, vec3(bx + out.x, gy + h, bz + out.z), vec3(0.0f, h, 0.0f), tip_color);
                MeshBuilder_triangle(&b, i0, i1, i2);
            }
        }
    }
    if (b.index_count == 0) { /* (no grass here: tar, a yard) */
        MeshBuilder_free(&b);
        return (Mesh){ 0 };
    }
    return Mesh_fromBuilder(&b);
}

/* Does this square reach into the Kartoffelacker? */
static bool overField(int cx, int cz) {
    const float x0 = -TERRAIN_SIZE * 0.5f + (float)cx * MEADOW_CHUNK, z0 = -TERRAIN_SIZE * 0.5f + (float)cz * MEADOW_CHUNK;
    return x0 < FIELD_X0 + FIELD_NX * FIELD_CELL && x0 + MEADOW_CHUNK > FIELD_X0 && z0 < FIELD_Z0 + FIELD_NZ * FIELD_CELL && z0 + MEADOW_CHUNK > FIELD_Z0;
}

#define MOW_REBUILD 20 /* frames between building the field's squares again while it's being mown */

void Meadow_draw(Meadow *m, const World *world, Renderer *r, Vec3 eye) {
    int builds = 0;
    if (world->mow_rev != m->mow_rev && ++m->mow_wait >= MOW_REBUILD) { /* (mown a bit more: its grass again) */
        m->mow_rev = world->mow_rev;
        m->mow_wait = 0;
        for (int i = 0; i < MEADOW_SIDE * MEADOW_SIDE; i++)
            if (m->built[i] && overField(i % MEADOW_SIDE, i / MEADOW_SIDE)) { Mesh_destroy(&m->chunks[i]); m->built[i] = false; }
    }
    for (int cz = 0; cz < MEADOW_SIDE; cz++) {
        for (int cx = 0; cx < MEADOW_SIDE; cx++) {
            int i = cz * MEADOW_SIDE + cx;
            float mx = -TERRAIN_SIZE * 0.5f + ((float)cx + 0.5f) * MEADOW_CHUNK;
            float mz = -TERRAIN_SIZE * 0.5f + ((float)cz + 0.5f) * MEADOW_CHUNK;
            float d = sqrtf((mx - eye.x) * (mx - eye.x) + (mz - eye.z) * (mz - eye.z));
            if (d > DROP_RANGE) {
                if (m->built[i]) { Mesh_destroy(&m->chunks[i]); m->built[i] = false; }
                continue;
            }
            if (d > DRAW_RANGE + MEADOW_CHUNK * 0.71f) continue;
            if (!m->built[i]) {
                if (builds >= BUILDS_PER_FRAME) continue;
                m->chunks[i] = buildChunk(world, cx, cz);
                m->built[i] = true;
                builds++;
            }
            if (m->chunks[i].vao) Renderer_drawGrass(r, &m->chunks[i], TERRAIN_SIZE, TERRAIN_GRASS_DEPTH);
        }
    }
}

void Meadow_destroy(Meadow *m) {
    for (int i = 0; i < MEADOW_SIDE * MEADOW_SIDE; i++)
        if (m->built[i]) Mesh_destroy(&m->chunks[i]);
    memset(m, 0, sizeof(*m));
}