#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); } } } }