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 1.68.0: walking map updated in place (0.3 ms instead of 10 ms)
- When fixed things change (a tree felled, a ladder put up) only the cells round them are worked out again; same result as a whole rebuild Includes 1.67.0 (if not committed separately): - Pour your drink on the fire (C): a small fire goes out, a big one hisses, vodka flares up Includes 1.66.0 (if not committed separately): - Careful people sort the woodpile by length (front even) and put the branches lying about onto a heap at the end of the woodpile Includes 1.65.0 (if not committed separately): - Smooth off the ladder (a step back, one foot after the other) and taking it down (the hand reaches for the rail at chest height) Includes 1.64.0 (if not committed separately): - Smooth ladder and climbs: getting on, over the edge at the top, feet from where they are; the easy 1 m climb no longer drops you off again Includes 1.63.0 (if not committed separately): - The hose melts in the fire (leaks, burns through); duct tape and the shears mend it Includes 1.62.0 (if not committed separately): - Smooth pull-ups: no hop at the end, feet and knees ease over the edge, elbows and hands taking hold blend in (audit walk: joint jumps 161 -> 58) Includes 1.61.0 (if not committed separately): - A step ladder in the tool room: stands on its own, at a wall with its back to it; climb onto the wall from its top Includes 1.60.0 (if not committed separately): - A folding ladder (two 2.5 m halves on a hinge, 5 m open) beside the telescopic one; one click at a wall unfolds it and leans it there Includes 1.59.0 (if not committed separately): - Stepping up onto blocks above knee high; set moves and jumps only for things standing on the ground in front of you Includes 1.58.0 (if not committed separately): - Walking over knee-high things is one smooth set move that always finishes; at a run: jump up onto blocks, catch climbable edges
entropie/Makefile | 2 +- entropie/STATE.md | 5 +- entropie/commit_message.txt | 10 ++-- entropie/src/world/nav.c | 109 +++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 113 insertions(+), 13 deletions(-) diff --git a/entropie/Makefile b/entropie/Makefile index 5193ed8..b3570f9 100644 --- a/entropie/Makefile +++ b/entropie/Makefile @@ -1,5 +1,5 @@ APP := entropie -VERSION := 1.67.0 +VERSION := 1.68.0 PREFIX ?= /usr BINDIR ?= $(PREFIX)/bin diff --git a/entropie/STATE.md b/entropie/STATE.md index 2f0ab4c..211b19d 100644 --- a/entropie/STATE.md +++ b/entropie/STATE.md @@ -56,7 +56,7 @@ Tools they shouldn't use (`ai_work.c`, Ai_shouldntUse): the chainsaw with tree s | Approach | come to you with an offer, a question, a suggestion; left without an answer they ask again, then give up (patience: 12 s x (1 - .35 energy) x (1 - .3 pride), 5..25 s: Jonas, Mila ~8 s, Ida, Ben ~15 s) and are annoyed (+0.4, fades in 3 min: frown, less willing, no approaching while > 0.3) | | Tidy up | tidy ones pick up trash on the way | -Getting about: straight at it, round things in the way (local look-ahead); leads into a corner: a way round from the walking map (`src/world/nav.c`, 0.4 m grid of fixed things, A*, rebuilt when fixed things change). They remember 4 things they couldn't get at. +Getting about: straight at it, round things in the way (local look-ahead); leads into a corner: a way round from the walking map (`src/world/nav.c`, 0.4 m grid of fixed things, A*). When fixed things change (cheap signature over all of them) only the cells round what moved, turned, came or went are done again (a table of each fixed thing as last built, by pointer; more than 32 changes at once: the whole map): a felled tree 0.3 ms instead of 10 ms, the same cells as a whole rebuild. They remember 4 things they couldn't get at. Watchdog (every behaviour except waiting ones: idle, help, follow, drink, breaks, talking, sitting): nothing happening for 25 s (150 s while working on something): let go of the target and look again; again: give the job up ("I'm stuck here"). Jobs with tools: fell, take branches off, cut a log, split, chop out a stump (axe, chainsaw, shears, hands). @@ -157,7 +157,6 @@ Most important first. **Scale** - Memory and speed for 750 people (the whole festival); drawing 300 figures not measured yet -- The walking map rebuild takes ~9 ms when fixed things change (a felled tree) --- -State of `entropie 1.67.0 (build e686fb3)` +State of `entropie 1.68.0 (build b9bdda1)` diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt index c685ebd..f274471 100644 --- a/entropie/commit_message.txt +++ b/entropie/commit_message.txt @@ -1,7 +1,11 @@ -entropie 1.67.0: pour your drink on the fire (C) +entropie 1.68.0: walking map updated in place (0.3 ms instead of 10 ms) -- Right at a burning fire with a drink in hand, C pours it on: a small - fire goes out, a big one hisses, vodka flares up; a hint shows there +- When fixed things change (a tree felled, a ladder put up) only the + cells round them are worked out again; same result as a whole rebuild + +Includes 1.67.0 (if not committed separately): +- Pour your drink on the fire (C): a small fire goes out, a big one + hisses, vodka flares up Includes 1.66.0 (if not committed separately): - Careful people sort the woodpile by length (front even) and put the diff --git a/entropie/src/world/nav.c b/entropie/src/world/nav.c index 634def3..2ec2654 100644 --- a/entropie/src/world/nav.c +++ b/entropie/src/world/nav.c @@ -46,7 +46,37 @@ static bool inCollider(const Object *o, int i, Vec3 p) { return true; } -static void blockCollider(const Object *o, int i, const Terrain *terrain) { +/* Cells an object's colliders may block (with the clearance), clipped to the map. */ +typedef struct { int x0, z0, x1, z1; } CellRect; + +static CellRect boundsOf(const Object *o) { + CellRect r = { NAV_N, NAV_N, -1, -1 }; + for (int i = 0; i < o->collider_count; i++) { + const Collider *c = &o->colliders[i]; + Vec3 center = Object_colliderCenter(o, i); + float ex = 0.0f, ez = 0.0f; + const float half[3] = { c->half_extents.x, c->half_extents.y, c->half_extents.z }; + for (int k = 0; k < 3; k++) { + ex += fabsf(c->axes[k].x) * half[k]; + ez += fabsf(c->axes[k].z) * half[k]; + } + int x0 = cellOf(center.x - ex - NAV_MARGIN), x1 = cellOf(center.x + ex + NAV_MARGIN); + int z0 = cellOf(center.z - ez - NAV_MARGIN), z1 = cellOf(center.z + ez + NAV_MARGIN); + if (x0 < r.x0) r.x0 = x0; + if (z0 < r.z0) r.z0 = z0; + if (x1 > r.x1) r.x1 = x1; + if (z1 > r.z1) r.z1 = z1; + } + if (r.x0 < 0) r.x0 = 0; + if (r.z0 < 0) r.z0 = 0; + if (r.x1 >= NAV_N) r.x1 = NAV_N - 1; + if (r.z1 >= NAV_N) r.z1 = NAV_N - 1; + return r; +} + +static bool overlaps(CellRect a, CellRect b) { return a.x0 <= b.x1 && b.x0 <= a.x1 && a.z0 <= b.z1 && b.z0 <= a.z1; } + +static void blockCollider(const Object *o, int i, const Terrain *terrain, CellRect clip) { const Collider *c = &o->colliders[i]; Vec3 center = Object_colliderCenter(o, i); float ex = 0.0f, ey = 0.0f, ez = 0.0f; @@ -58,8 +88,12 @@ static void blockCollider(const Object *o, int i, const Terrain *terrain) { } int x0 = cellOf(center.x - ex - NAV_MARGIN), x1 = cellOf(center.x + ex + NAV_MARGIN); int z0 = cellOf(center.z - ez - NAV_MARGIN), z1 = cellOf(center.z + ez + NAV_MARGIN); - for (int z = z0 < 0 ? 0 : z0; z <= z1 && z < NAV_N; z++) - for (int x = x0 < 0 ? 0 : x0; x <= x1 && x < NAV_N; x++) { + if (x0 < clip.x0) x0 = clip.x0; + if (z0 < clip.z0) z0 = clip.z0; + if (x1 > clip.x1) x1 = clip.x1; + if (z1 > clip.z1) z1 = clip.z1; + for (int z = z0; z <= z1; z++) + for (int x = x0; x <= x1; x++) { if (g_blocked[z * NAV_N + x]) continue; float px = centerOf(x), pz = centerOf(z); float ground = Terrain_height(terrain, px, pz); @@ -72,6 +106,34 @@ static void blockCollider(const Object *o, int i, const Terrain *terrain) { } } +/* What each fixed thing was at the last build (by pointer, open addressing): + when something changes only the cells round it are done again. */ +typedef struct { const Object *o; float x, z, yaw; int colliders; CellRect cells; unsigned seen; } NavEntry; +#define NAV_TABLE 16384 +#define NAV_DIRTY 64 /* more changes at once than this: the whole map again */ +static NavEntry *g_table, *g_table_new; +static unsigned g_build; + +static NavEntry *slotFor(NavEntry *table, const Object *o, bool *found) { + size_t h = ((size_t)o >> 4) * 2654435761u; + for (size_t k = 0; k < NAV_TABLE; k++) { + NavEntry *e = &table[(h + k) & (NAV_TABLE - 1)]; + if (e->o == o) { *found = true; return e; } + if (!e->o) { *found = false; return e; } + } + return NULL; +} + +/* Clears the cells in r and blocks them again from every fixed thing over them. */ +static void redo(Object *const *objects, int count, const Terrain *terrain, CellRect r) { + for (int z = r.z0; z <= r.z1; z++) memset(&g_blocked[z * NAV_N + r.x0], 0, (size_t)(r.x1 - r.x0 + 1)); + for (int i = 0; i < count; i++) { + const Object *o = objects[i]; + if (!o->is_static || o->collider_count == 0 || !overlaps(boundsOf(o), r)) continue; + for (int c = 0; c < o->collider_count; c++) blockCollider(o, c, terrain, r); + } +} + void Nav_update(Object *const *objects, int count, const Terrain *terrain) { /* What the fixed things are and where: rebuild when that changes */ double signature = 0.0; @@ -93,11 +155,46 @@ void Nav_update(Object *const *objects, int count, const Terrain *terrain) { g_heap = malloc((size_t)g_heap_cap * sizeof *g_heap); if (!g_blocked || !g_cost || !g_from || !g_seen || !g_done || !g_heap) return; } - memset(g_blocked, 0, (size_t)NAV_N * NAV_N); + if (!g_table) { + g_table = calloc(NAV_TABLE, sizeof *g_table); + g_table_new = calloc(NAV_TABLE, sizeof *g_table_new); + if (!g_table || !g_table_new) return; + } + /* What changed since the last build: the cells it was on and is on now */ + CellRect dirty[NAV_DIRTY]; + int n_dirty = 0; + bool whole = !g_ready; + g_build++; + memset(g_table_new, 0, NAV_TABLE * sizeof *g_table_new); for (int i = 0; i < count; i++) { const Object *o = objects[i]; - if (!o->is_static) continue; - for (int c = 0; c < o->collider_count; c++) blockCollider(o, c, terrain); + if (!o->is_static || o->collider_count == 0) continue; + bool found = false, was = false; + NavEntry *e = slotFor(g_table_new, o, &found); + if (!e) { whole = true; continue; } + NavEntry *old = slotFor(g_table, o, &was); + *e = (NavEntry){ o, o->position.x, o->position.z, o->rotation.y, o->collider_count, boundsOf(o), 0 }; + if (was && old->x == e->x && old->z == e->z && old->yaw == e->yaw && old->colliders == e->colliders) { old->seen = g_build; continue; } + if (was) old->seen = g_build; + if (n_dirty + 2 > NAV_DIRTY) { whole = true; continue; } + if (was) dirty[n_dirty++] = old->cells; + dirty[n_dirty++] = e->cells; + } + for (int k = 0; k < NAV_TABLE && !whole; k++) { /* (gone since: its cells) */ + const NavEntry *old = &g_table[k]; + if (!old->o || old->seen == g_build) continue; + if (n_dirty + 1 > NAV_DIRTY) whole = true; + else dirty[n_dirty++] = old->cells; + } + NavEntry *swap = g_table; + g_table = g_table_new; + g_table_new = swap; + if (whole) { + CellRect all = { 0, 0, NAV_N - 1, NAV_N - 1 }; + redo(objects, count, terrain, all); + } else { + for (int k = 0; k < n_dirty; k++) + if (dirty[k].x1 >= dirty[k].x0 && dirty[k].z1 >= dirty[k].z0) redo(objects, count, terrain, dirty[k]); } g_signature = signature; g_ready = true;