Entropie git · main
Entropie festival game; first steps
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.gzentropie/src/world/meadow.c 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));
}