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/forestry.c raw
#include "world/forestry.h"
#include "world/physics.h"
#include "world/injury.h"
#include "world/site.h"
#define STUMP_MIN 0.3f /* stumps are at least this wide */
#define ROUND_PIECE 0.6f /* a chopped-out stump gives a chunk of wood this long at most */
#define CROWN_STEM 1.5f /* the trunk goes on this far into the crown */
#define BRANCH_SIZE { 0.0f, 0.9f, 0.0f }
#define BRANCH_WEIGHT 1.5f
#define BUSH_TWIGS 4 /* twigs a bush loses at most to people landing in it */
#define HEAVY 150.0f /* a falling tree comes to rest on anything this heavy */
#define TREE_SHOVE 3.0f /* m/s given to light things a falling tree hits */
#define CUT_GAP 0.01f
/* Trunk direction from the base towards the top (world). */
static Vec3 trunkAxis(const Object *tree) {
return vec3_normalize(mat4_mul_dir(mat4_rotate_yxz(tree->rotation), vec3(0.0f, 1.0f, 0.0f)));
}
/* Lets go of an object everybody was holding (it changes shape). */
static void releaseAll(World *world, Object *o) {
for (int i = 0; i < world->count; i++)
if (world->objects[i]->hands.grabbed == o) Hands_release(world->objects[i]);
o->lifted = false;
}
/* ---------- Jobs ---------- */
static void fellTowards(World *world, Object *tree, Vec3 away, float cut_height);
#define FINE_APPLE 300 /* EUR: one of Kurti's apple trees felled (and Kurti cross) */
#define FINE_PROTECTED 1000 /* EUR: a tree that stays (the town's tree protection) */
#define BIG_LEAN 1.4f /* the big old tree felled without a rope: it goes the way it leans */
static void fell(World *world, Object *tree, const Object *by, float cut_height) {
if (tree->type != OBJ_TREE || tree->toppled) return;
Vec3 away = vec3_sub(tree->position, by->position);
away.y = 0.0f;
if (vec3_length(away) < 1e-3f) away = Object_facing(by);
if (tree->variant == 2) away = vec3_add(vec3_normalize(away), vec3_scale(Site_bigTreeLean(), BIG_LEAN)); /* (no rope on it) */
else if (tree->variant == 1) { world->tree_fine += FINE_APPLE; world->tree_fine_kind = 1; }
else if (tree->keep) { world->tree_fine += FINE_PROTECTED; world->tree_fine_kind = 2; }
fellTowards(world, tree, away, cut_height);
}
void Forestry_fell(World *world, Object *tree, Vec3 dir, float cut_height) {
if (tree->type != OBJ_TREE || tree->toppled) return;
dir.y = 0.0f;
fellTowards(world, tree, vec3_length(dir) > 1e-3f ? dir : vec3(1.0f, 0.0f, 0.0f), cut_height);
}
static void fellTowards(World *world, Object *tree, Vec3 away, float cut_height) {
/* The trunk comes apart where it was cut: the part below stays as the stump */
Vec3 base = tree->position;
Object_cutTrunk(tree, cut_height);
float stump_height = tree->position.y - base.y;
Object_topple(tree, away);
float diameter = fmaxf(2.0f * tree->colliders[0].half_extents.x, STUMP_MIN); /* (as wide as the trunk: it looks the same as before) */
{ /* it comes down beside the stump, on the ground */
Vec3 dir = tree->fall_dir;
tree->fall_slide = diameter * 0.5f + tree->colliders[0].half_extents.x + 0.05f;
Vec3 butt = vec3_add(base, vec3_scale(dir, tree->fall_slide));
tree->fall_ground_y = Terrain_height(&world->terrain, butt.x, butt.z);
}
Object *stump = Object_create(OBJ_STUMP, PALETTE_WARM, vec3_add(base, vec3(0.0f, stump_height * 0.5f, 0.0f)),
vec3(diameter, stump_height, 0.0f), 0.0f);
if (stump && World_add(world, stump)) tree->stump = stump;
}
/* Small deterministic dice from a position (no shared state needed). */
static float dice(Vec3 p, int k) {
unsigned h = (unsigned)(int)(p.x * 197.0f) * 73856093u ^ (unsigned)(int)(p.z * 211.0f) * 19349663u ^ (unsigned)k * 83492791u;
h ^= h >> 13; h *= 0x5bd1e995u; h ^= h >> 15;
return (float)(h >> 8) / 16777216.0f;
}
static float terrainAt(const void *ctx, float x, float z) {
return Terrain_height(&((const World *)ctx)->terrain, x, z);
}
/* The stump chopped out: roots left in the dug-up ground, and a chunk of wood (for the fire). */
static void grubStump(World *world, Object *stump) {
if (stump->type != OBJ_STUMP || stump->broken) return;
Vec3 at = stump->position;
at.y = Terrain_height(&world->terrain, at.x, at.z);
ShapeGround ground = { .height = terrainAt, .ctx = world, .origin = at };
unsigned seed = (unsigned)(int)(at.x * 613.0f) ^ ((unsigned)(int)(at.z * 419.0f) << 9);
Mesh roots = Shapes_roots(stump->size.x, seed, ground);
if (roots.vao) {
Object *decal = Object_createDecal(roots, at);
if (!decal || !World_add(world, decal)) Object_destroy(decal);
}
float d = stump->size.x * 0.8f;
Vec3 aside = vec3((dice(at, 3) - 0.5f) * 2.0f, 0.0f, (dice(at, 4) - 0.5f) * 2.0f); /* rolled aside, standing */
aside = vec3_scale(vec3_length(aside) > 0.05f ? vec3_normalize(aside) : vec3(1.0f, 0.0f, 0.0f), stump->size.x + 0.35f);
Object *chunk = Object_create(OBJ_LOG, PALETTE_WARM, vec3_add(at, vec3(aside.x, d * 0.5f + 0.1f, aside.z)),
vec3(d, fminf(stump->size.y + 0.15f, ROUND_PIECE), 0.0f), 0.0f);
if (chunk && !World_add(world, chunk)) Object_destroy(chunk);
for (int i = 0; i < world->count; i++)
if (world->objects[i]->stump == stump) world->objects[i]->stump = NULL;
Object_useUp(stump);
}
/* A branch breaks off and drops to the ground at (x, z). */
static void dropBranch(World *world, Object *tree, Vec3 at, int k) {
if (tree->branches <= 1) return; /* (the last ones stay on: that's still a crown to work on) */
at.y = Terrain_height(&world->terrain, at.x, at.z) + 0.2f;
const Vec3 size = BRANCH_SIZE;
Object *branch = Object_create(OBJ_BRANCH, PALETTE_WARM, at, size, BRANCH_WEIGHT);
if (!branch) return;
Object_setRotation(branch, vec3(PI_F / 2.0f, 6.28f * dice(at, k), 0.0f));
if (!World_add(world, branch)) { Object_destroy(branch); return; }
Object_setBranches(tree, tree->branches - 1);
}
/* A tree crashing down: a few branches snap off around its crown; the tree
it fell into (if it did) loses some too. */
static void crash(World *world, Object *tree, Object *into) {
Vec3 axis = trunkAxis(tree), side = vec3_normalize(vec3_cross(axis, vec3(0.0f, 1.0f, 0.0f)));
float crown = fmaxf(tree->size.x, 1.0f);
int n = (into ? 2 : 1) + (int)(dice(tree->position, 1) * 2.0f);
for (int k = 0; k < n; k++) {
float along = tree->size.y + crown * (0.2f + 0.7f * dice(tree->position, 10 + k));
float out = (dice(tree->position, 20 + k) - 0.5f) * crown * 1.2f;
dropBranch(world, tree, vec3_add(tree->position, vec3_add(vec3_scale(axis, along), vec3_scale(side, out))), 30 + k);
}
if (into && into->type == OBJ_TREE && !into->toppled) {
int m = 1 + (int)(dice(into->position, 2) * 2.0f);
for (int k = 0; k < m; k++) {
float a = 6.28f * dice(into->position, 40 + k), r = fmaxf(into->size.x, 1.0f) * (0.2f + 0.4f * dice(into->position, 50 + k));
dropBranch(world, into, vec3_add(into->position, vec3(cosf(a) * r, 0.0f, sinf(a) * r)), 60 + k);
}
}
}
int Forestry_intoBush(World *world, Object *who, Object *bush) {
Vec3 to = vec3_sub(bush->position, who->position);
to.y = 0.0f;
Vec3 dir = vec3_length(to) > 1e-3f ? vec3_normalize(to) : Object_facing(who);
Body_trip(&who->body, 0, dir, 0.85f); /* (badly: a fall) */
World_noise(world, NOISE_RUSTLE, bush->position, 1.0f);
if (bush->stored >= BUSH_TWIGS) return 0; /* (nothing much left to break) */
int n = 1 + (int)(dice(who->position, bush->stored) * 2.0f), made = 0;
for (int k = 0; k < n && bush->stored < BUSH_TWIGS; k++) {
float a = 6.28f * dice(bush->position, 70 + bush->stored), r = bush->size.x * 0.45f;
Vec3 at = vec3_add(bush->position, vec3(cosf(a) * r, 0.0f, sinf(a) * r));
at.y = Terrain_height(&world->terrain, at.x, at.z) + 0.1f;
Object *twig = Object_create(OBJ_BRANCH, PALETTE_WARM, at, vec3(0.03f, 0.45f + 0.3f * dice(at, 3), 0.0f), 0.2f);
if (!twig) break;
Object_setRotation(twig, vec3(PI_F / 2.0f, a, 0.0f));
if (!World_add(world, twig)) { Object_destroy(twig); break; }
bush->stored++;
made++;
}
return made;
}
/* One branch comes off where the stroke landed; with the last one the trunk is a log. */
static void takeBranch(World *world, Object *tree, Vec3 point) {
if (tree->type != OBJ_TREE || tree->branches <= 0) return;
Vec3 at = point;
at.y = Terrain_height(&world->terrain, at.x, at.z) + 0.2f;
const Vec3 size = BRANCH_SIZE;
Object *branch = Object_create(OBJ_BRANCH, PALETTE_WARM, at, size, BRANCH_WEIGHT);
if (branch) Object_setRotation(branch, vec3(PI_F / 2.0f, 0.9f * (float)tree->branches, 0.0f));
World_add(world, branch);
Object_setBranches(tree, tree->branches - 1);
if (tree->branches > 0) return;
/* What is left is the trunk */
const float radius = tree->colliders[0].half_extents.x;
const float length = tree->size.y + CROWN_STEM;
Vec3 axis = trunkAxis(tree);
releaseAll(world, tree);
Object_makeLog(tree, vec3_add(tree->position, vec3_scale(axis, length * 0.5f)), axis, length, 2.0f * radius);
}
static void cut(World *world, Object *log, float at) {
Vec3 a, b;
Tools_logEnds(log, &a, &b);
Vec3 axis = vec3_sub(b, a);
float length = vec3_length(axis);
axis = vec3_scale(axis, 1.0f / fmaxf(length, 1e-4f));
float diameter = 2.0f * log->colliders[0].half_extents.x;
float rest = length - at;
releaseAll(world, log);
Vec3 first = vec3_add(a, vec3_scale(axis, at * 0.5f - CUT_GAP));
Vec3 second = vec3_add(a, vec3_scale(axis, at + rest * 0.5f + CUT_GAP));
Object_makeLog(log, first, axis, at, diameter);
Object *piece = Object_create(OBJ_LOG, PALETTE_WARM, vec3_zero(), vec3(diameter, rest, diameter), 0.0f);
if (!piece) return;
Object_makeLog(piece, second, axis, rest, diameter);
World_add(world, piece);
}
/* Split along its length: a short log into two halves, a half into two
quarters. The pieces fall apart to both sides, flat side down. */
static void split(World *world, Object *piece, const Object *by) {
bool from_log = piece->type == OBJ_LOG;
if (!from_log && (piece->type != OBJ_FIREWOOD || piece->size.z >= 4.0f)) return;
Vec3 a, b;
Tools_logEnds(piece, &a, &b);
Vec3 axis = vec3_sub(b, a);
float length = vec3_length(axis);
axis.y = 0.0f;
if (vec3_length(axis) < 1e-3f) axis = vec3_cross(Object_facing(by), vec3(0.0f, 1.0f, 0.0f));
axis = vec3_normalize(axis);
const float diameter = from_log ? 2.0f * piece->colliders[0].half_extents.x : piece->size.x;
const float r = diameter * 0.5f;
const int parts = from_log ? 2 : 4;
const Vec3 up = vec3(0.0f, 1.0f, 0.0f), side = vec3_normalize(vec3_cross(axis, up));
const float apart = (parts == 2 ? r : r * 0.5f) + 0.04f;
Vec3 center = vec3_scale(vec3_add(a, b), 0.5f);
center.y = Object_bottom(piece) + r * 0.5f + 0.005f;
releaseAll(world, piece);
Object *other = Object_create(OBJ_FIREWOOD, PALETTE_WARM, vec3_zero(), vec3(diameter, length, (float)parts), 0.0f);
/* Quarters: the left one turned end for end, so both split faces look at each other */
Object_makeFirewood(piece, vec3_sub(center, vec3_scale(side, apart)),
parts == 4 ? vec3_scale(axis, -1.0f) : axis, up, length, diameter, parts);
if (!other) return;
Object_makeFirewood(other, vec3_add(center, vec3_scale(side, apart)), axis, up, length, diameter, parts);
World_add(world, other);
}
void Forestry_finishJobs(World *world, Object *by) {
JobDone d;
while (Tools_takeDone(by, &d)) {
switch (d.job) {
case JOB_FELL: fell(world, d.target, by, d.at); break;
case JOB_DELIMB: takeBranch(world, d.target, d.point); break;
case JOB_CUT: cut(world, d.target, d.at); break;
case JOB_SPLIT: split(world, d.target, by); break;
case JOB_STUMP: grubStump(world, d.target); break;
case JOB_NONE: break;
}
}
}
/* ---------- Falling trees ---------- */
/* What the tree hits on its way down. Returns true if something stops it. */
#define BREAK_TREE 0.35f /* a falling tree knocks somebody down: it breaks something this often ... */
#define BREAK_BIG 0.9f /* ... the big old one: nearly always */
static bool hit(World *world, Object *tree, Object *o) {
Vec3 dir = tree->fall_dir;
if (o->is_character) { /* knocked down - and maybe worse */
bool big = tree->variant == 2;
if (o->body.pose == POSE_UP || (big && !o->injured)) {
if (o->body.pose == POSE_UP) o->velocity = vec3_add(o->velocity, vec3_scale(dir, 2.0f));
Body_fall(&o->body, dir);
if (o->type != OBJ_DOG) Injury_break(world, o, Injury_roll(world, big ? BREAK_BIG : BREAK_TREE));
}
return false;
}
if (Object_isBottle(o)) {
Object_topple(o, dir);
o->velocity = vec3_add(o->velocity, vec3_scale(dir, TREE_SHOVE));
return false;
}
if (!o->is_static && o->weight < HEAVY) { /* shoved aside and down */
o->velocity = vec3_add(o->velocity, vec3_add(vec3_scale(dir, TREE_SHOVE), vec3(0.0f, -1.0f, 0.0f)));
return false;
}
return true; /* walls, other trees, heavy things */
}
static bool touches(const Object *tree, const Object *o) {
for (int c = 0; c < tree->collider_count; c++)
for (int k = 0; k < o->collider_count; k++)
if (Physics_overlap(tree, c, o, k)) return true;
return false;
}
/* A fallen tree dragged along: its branches catch on what stands in the way
and hold it back; pulled on (or going fast) one snaps off. */
#define SNAG_HOLD 1.0f /* s pulling against a snag before a branch gives */
#define SNAG_SPEED 0.9f /* m/s: into it this fast, it snaps at once */
static bool draggedBy(const World *world, const Object *tree) {
for (int i = 0; i < world->count; i++)
if (world->objects[i]->hands.grabbed == tree) return true;
return false;
}
static void snagBranches(World *world, Object *tree, float dt) {
if (tree->is_static || tree->branches <= 1 || tree->collider_count < 2 || !draggedBy(world, tree)) { tree->snag = 0.0f; return; }
Object *caught = NULL;
for (int i = 0; i < world->count && !caught; i++) {
Object *o = world->objects[i];
if (o == tree || !o->is_static || o == tree->stump || o->collider_count == 0 || o->type == OBJ_DECAL || o->type == OBJ_STRUCTURE) continue;
if (fabsf(o->position.x - tree->position.x) > 15.0f || fabsf(o->position.z - tree->position.z) > 15.0f) continue;
for (int c = 0; c < o->collider_count && !caught; c++)
if (Physics_overlap(tree, 1, o, c)) caught = o;
}
if (!caught) { tree->snag = fmaxf(0.0f, tree->snag - 0.5f * dt); return; } /* (caught and freed again by the push back: it adds up) */
float speed = sqrtf(tree->velocity.x * tree->velocity.x + tree->velocity.z * tree->velocity.z);
tree->snag += dt;
if (speed < SNAG_SPEED) tree->velocity = vec3(0.0f, tree->velocity.y, 0.0f); /* (held back) */
if (tree->snag >= SNAG_HOLD || speed >= SNAG_SPEED) {
tree->snag = 0.0f;
Vec3 at = Object_colliderCenter(tree, 1);
dropBranch(world, tree, vec3_scale(vec3_add(at, Object_colliderCenter(caught, 0)), 0.5f), 80 + tree->branches);
Chips_spray(&world->chips, vec3_add(at, vec3(0.0f, 0.4f, 0.0f)), vec3(0.0f, 1.0f, 0.0f), 8, false);
}
}
void Forestry_update(World *world, float dt) {
for (int i = 0; i < world->count; i++) {
Object *t = world->objects[i];
if (t->type == OBJ_TREE && t->toppled && t->topple >= 1.0f) snagBranches(world, t, dt);
}
for (int i = 0; i < world->count; i++) {
Object *tree = world->objects[i];
if (tree->type != OBJ_TREE || !tree->toppled || tree->topple >= 1.0f) continue;
Vec3 rotation = tree->rotation, position = tree->position;
Object_advanceTopple(tree, dt); /* (cut above the ground: it slides off the stump and comes down, see fell) */
if (tree->topple >= 1.0f) World_noise(world, NOISE_CRASH, Object_colliderCenter(tree, 0), 1.0f); /* (down: the crash) */
Object *stopped = NULL;
for (int k = 0; k < world->count; k++) {
Object *o = world->objects[k];
if (o == tree || o == tree->stump || o->is_held || o->broken || o->collider_count == 0) continue;
if (touches(tree, o) && hit(world, tree, o)) stopped = o;
}
if (stopped) {
/* Comes to rest where it was, leaning on what it hit */
tree->rotation = rotation;
tree->position = position;
tree->topple = 1.0f;
Object_syncColliders(tree);
}
if (tree->topple >= 1.0f) {
crash(world, tree, stopped && stopped->type == OBJ_TREE ? stopped : NULL);
if (!stopped) { /* lying on the ground: it can be dragged about now (heavy) */
tree->is_static = false;
tree->velocity = vec3_zero();
tree->weight = Object_treeWeight(tree);
}
}
}
}