#include "world/meadow.h" #include "world/field.h" #include "world/site.h" #include #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)); }