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.96.0: people find the basement, legs under the table
- The walking map has two levels: people find their way down the stairs to the basement and back up - Narrow doorways and the landing at the top of the basement stairs no longer stop people; the floor over the stairs doesn't count as a wall on the way down - Wandering somewhere the long way round no longer gives up halfway - Sitting at a table, people's legs go under it (they lay on the table top)
entropie/Makefile | 2 +- entropie/README.md | 3 +- entropie/STATE.md | 10 ++- entropie/commit_message.txt | 21 ++--- entropie/src/people/ai.c | 11 ++- entropie/src/people/body_pose.c | 2 + entropie/src/world/nav.c | 174 +++++++++++++++++++++++++--------------- 7 files changed, 138 insertions(+), 85 deletions(-) diff --git a/entropie/Makefile b/entropie/Makefile index 2633686..c46d9a3 100644 --- a/entropie/Makefile +++ b/entropie/Makefile @@ -1,5 +1,5 @@ APP := entropie -VERSION := 1.95.0 +VERSION := 1.96.0 PREFIX ?= /usr BINDIR ?= $(PREFIX)/bin diff --git a/entropie/README.md b/entropie/README.md index 5d18828..e30e9e0 100644 --- a/entropie/README.md +++ b/entropie/README.md @@ -104,7 +104,8 @@ and satellite pictures (about 340 x 340 m). - People come over to a burning fire, sit down round it and look into the flames - Now and then they sit down at a table (the meeting table, the kitchens) for a while and have a word; food they pick up they mostly eat there, a beer - sometimes too. They find their way in: up the steps, through the doors + sometimes too. They find their way in: up the steps, through the doors, + down the stairs to the basement - C: sit down yourself (and rest faster): on a bench or chair (right on the seat; a chair is pulled out first, shuffled in to the table once you sit, and pushed back in after you leave) if one is next diff --git a/entropie/STATE.md b/entropie/STATE.md index d29d011..9f96501 100644 --- a/entropie/STATE.md +++ b/entropie/STATE.md @@ -129,6 +129,11 @@ Round 1.95: - Sitting: no half-over-something falls (`world.c` halfOver clears `crossing` while sitting; feet under the table started a step over). - Felling cut (`tools.c`): looking lower than hip height, right where you look; between that and AIM_UP_FROM 0.3 m over the eyes, hip height; higher, the cut goes up as much (clamped to the tool's range: axe 0.45-1.35 m). +Round 1.96: +- Walking map in two levels (`nav.c`): level 0 where one stands (the ground or the top floor / step), level 1 under it where there's BELOW_ROOM 2 m or more (a basement: the pit floor, its stairs' lower steps). Nodes are level x N2 + cell; A* steps to whichever level of the next cell is within NAV_RISE (`stepTo`): the stairs join them. Start and goal take the level nearest their height (`levelFor`); waypoints carry the floor height. Diagonals only blocked between two blocked corners (a turned stair runs diagonally). NAV_MARGIN 0.25 -> 0.18 (an 0.8 m landing or doorway keeps a row of free cells). +- Steering (`ai.c` ai_blockedBy): a building's walls kept WALL_RADIUS 0.24 m off (BODY_RADIUS 0.35 left no way through a 0.9 m doorway); a building part over the head (bottom 0.3 m+ over the feet) more than 0.6 m off doesn't block (a floor over a stair going down). Wandering counts progress along the way round (`stillToGo`), not straight to the goal. +- Sitting at a table (`body_pose.c` keepBodyOut): the legs aren't lifted onto anything higher than the hips (they lay on the table top). + Unfinished building (Shell_build, at SHELL_AT in the frame's frame): brick shell 6.4 x 4.7, walls 2.9, open front (2 m) to the frame (now only the front half: pillars at z -13.8 / -9.2), window holes, bare floor, tiled roof with slabs. Barn: brick courses on BUILDING_BRICK walls (`addBrickCourses`: mortar a shade lighter, so the ink doesn't trace it), ivy (`Shapes_ivy`, OBJ_STRUCTUREs) on both long sides; the woodpile (OBJ_WOODPILE "Woodpile", WOODPILE_A/B) left of the toilet, everybody's woodpile from the start (`scene.c` Scene_finish). Fireplace tripod: wooden poles, feet TRIPOD_OUT 0.9 m outside the stones, TRIPOD_PAST 0.4 m past the top, a lashing; each pole an OBJ_WALL along it ("Tripod pole"). Gates (FURN_GATE, green boards): in your garages' doorways (GATES: hinge, end), `gate_open` / `gate_t`, `hinge`, `gate_shut` / `gate_out`; Object_swingGate (1.2 s), driven by showProps; a click within 1.2 m opens / shuts both leaves. Props that show (1.89, `world.c` showProps every frame): the tin's mesh is built again when its butts change (`Shapes_ashtray`: up to 12 show, the ash rises, a heap from 14; `shown` = what's built); the coat rack (FURN_COATRACK, in your kitchen by the doors, garage coords 20.6 / 2.4) shows the jackets hanging on it (`Shapes_coatRack`, up to 6, a bitmask of whose in `shown`). `jacket_hung` (characters, true at the start): cold (chill 0.6+) and the jacket with them: on it goes (as before); hanging: NPCs `ai.want_jacket` -> `ai.c` fetchJacket (idle / wander: goTo the rack, on with it, "Ah, better."; gives up after 90 s or warm again); you are told once ("your jacket hangs on the rack in your garage") and click the rack (`game_actions.c` coatRack: on, or back onto it). Warm again: it comes off and stays with them. @@ -260,9 +265,6 @@ Most important first. **Bugs** - Joint jumps left in the audit: jog+stop 171, sprint 297 (worst 26 cm) - running into things: a hurried catch step swings through in a few frames (step 0.07 -> 0.34, the ankle up 20 cm). Capping the swing speed helped sprint (278) but made jumps, drunk walk and the NPCs worse - tried and reverted in 1.83; ladder 45: toes on the sloping roof (a landing step's toes snap onto the slope, 16 cm; blending them in made all walking worse), toes turning onto the rungs getting on from the top (14 cm) -**From the 15th of October (to do)** -- NPC pathfinding: buildings, steps and squeezes fixed in 1.95; the basement (under a floor) isn't on the walking map yet - **Body and moving** - Obstacle approach, still to do: NPCs stepping up onto higher things (now only what they'd stride over anyway); thin round logs (under 0.2 m) are still stepped over; a step in between on top of something unstable (if clumsy); NPCs: pull up on their own for their own reasons (now only following a leader), jumping onto things - Climbing rework: hands first (one reaches first, shoulder ahead), a knee up, the other foot next to it; or sit on it and lift the legs; or belly first and roll; by height, surface and person @@ -284,4 +286,4 @@ Most important first. - Drawing many figures on a real graphics card: not measured (software GL: 300 people cost 3x the rest of the scene) --- -State of `entropie 1.95.0 (build f8a9b47)` +State of `entropie 1.96.0 (build d8ab965)` diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt index 76f77ad..75f0ca3 100644 --- a/entropie/commit_message.txt +++ b/entropie/commit_message.txt @@ -1,14 +1,9 @@ -entropie 1.95.0: people sit at the tables, find their way into buildings +entropie 1.96.0: people find the basement, legs under the table -- People now and then sit down at a table (the meeting table, the kitchen - tables) for a while and have a word with whoever sits there; food they - pick up they mostly eat there, a beer sometimes too -- They find their way into the buildings: the walking map knows floors and - steps (before, every floor counted as a wall), and they walk up steps - instead of round them -- Nobody stands stuck under a small tree's crown or squeezed against a box - any more: they step out the way that's free -- Sitting at a table no longer ends in a fall (the feet under the table - counted as stepping over something) -- Felling: look down at the trunk to cut right there, look up the trunk - above your head to cut higher (up to the chest); straight ahead stays hip high +- The walking map has two levels: people find their way down the stairs to + the basement and back up +- Narrow doorways and the landing at the top of the basement stairs no + longer stop people; the floor over the stairs doesn't count as a wall + on the way down +- Wandering somewhere the long way round no longer gives up halfway +- Sitting at a table, people's legs go under it (they lay on the table top) diff --git a/entropie/src/people/ai.c b/entropie/src/people/ai.c index 481ede4..fc34ef8 100644 --- a/entropie/src/people/ai.c +++ b/entropie/src/people/ai.c @@ -110,6 +110,7 @@ static bool longToStepOnto(const Object *self, const Object *o) { return longest >= ONTO_LONG && top - self->position.y <= ONTO_HIGH; } +#define WALL_RADIUS 0.24f /* m kept from a building's walls (a doorway 0.9 m wide leaves 0.4 m to aim through) */ #define STAIR_FIRST 0.3f /* m: a step up right here ... */ #define STAIR_SLOPE 0.65f /* ... and this much more per m further on: stairs, not a wall */ @@ -151,14 +152,18 @@ bool ai_blockedBy(const Object *self, const Object *ignore, const Object *ignore float top = Object_colliderCenter(o, c).y + fabsf(box->axes[0].y) * box->half_extents.x + fabsf(box->axes[1].y) * box->half_extents.y + fabsf(box->axes[2].y) * box->half_extents.z; if (top < self->position.y + 0.25f) continue; + /* Over our heads here and a way off (a floor above a stair going down): there we'll be lower */ + float bottom = 2.0f * Object_colliderCenter(o, c).y - top; + if (o->type == OBJ_WALL && bottom > self->position.y + 0.3f && flatDistance(self->position, p) > 0.6f) continue; /* A building's floor or steps: walked up (a stair rises about STAIR_SLOPE) */ if (o->type == OBJ_WALL && o->variant != 3 && box->axes[1].y > 0.99f && box->half_extents.x >= 0.1f && box->half_extents.z >= 0.1f && top <= self->position.y + STAIR_FIRST + STAIR_SLOPE * flatDistance(self->position, p)) continue; - /* Knee, waist and head height above the feet */ + /* Knee, waist and head height above the feet (a building's walls: closer, or no doorway is wide enough) */ + float clear = o->type == OBJ_WALL ? WALL_RADIUS : BODY_RADIUS; for (int k = 0; k < 3; k++) { Vec3 q = vec3(p.x, self->position.y + 0.4f + 0.6f * (float)k, p.z); - if (insideCollider(o, c, q, BODY_RADIUS)) return true; + if (insideCollider(o, c, q, clear)) return true; } } } @@ -707,7 +712,7 @@ static void updateIdle(Object *self, Object *const *objects, int count, float dt static void updateWander(Object *self, Object *const *objects, int count, float dt) { Ai *ai = &self->ai; float dist = flatDistance(ai->goal, self->position); - if (dist < ARRIVE_DISTANCE * 2.0f || isStuck(ai, dist, dt)) { + if (dist < ARRIVE_DISTANCE * 2.0f || isStuck(ai, stillToGo(self, ai->goal), dt)) { /* (along the way round, if any) */ rest(ai, PAUSE_MIN, PAUSE_MAX); stand(self); return; diff --git a/entropie/src/people/body_pose.c b/entropie/src/people/body_pose.c index 0c7661e..5dd5725 100644 --- a/entropie/src/people/body_pose.c +++ b/entropie/src/people/body_pose.c @@ -1396,6 +1396,7 @@ static void keepBodyOut(Body *b, Vec3 position, Vec3 facing, float s, float dt, Vec3 *j = out->joints; float lift[JOINT_COUNT] = { 0.0f }; bool any = false; + const float hips_y = Figure_toWorld(position, facing, j[JOINT_PELVIS]).y; /* (sitting: a table top up there goes over the legs) */ for (unsigned n = 0; n < sizeof BONES / sizeof BONES[0]; n++) { int a = BONES[n][0], c = BONES[n][1]; bool trunk = a <= JOINT_HEAD && c <= JOINT_HEAD; @@ -1407,6 +1408,7 @@ static void keepBodyOut(Body *b, Vec3 position, Vec3 facing, float s, float dt, float from = w.y + 0.55f * s; /* (what a body could come to lie on: about knee high) */ float top = b->ground(b->clutter_ctx, w.x, w.z, from); if (top >= from - 0.01f || top + r <= w.y) continue; /* (clear, or inside something tall: left alone) */ + if (legs_only && top > hips_y - 0.05f * s) continue; /* (under a table: the legs go under it, not up on it) */ float need = top + r - w.y; /* a joint is lifted by what's under it, a bone's middle lifts both ends */ if (t < 0.99f) lift[a] = fmaxf(lift[a], need * (1.0f - t) + (k ? need * t : 0.0f)); diff --git a/entropie/src/world/nav.c b/entropie/src/world/nav.c index 85ba462..b1e89ac 100644 --- a/entropie/src/world/nav.c +++ b/entropie/src/world/nav.c @@ -1,9 +1,11 @@ /* - * Walking map: a grid over the site, a cell blocked where something fixed - * stands between knee and head height over where one stands there (the - * ground, or a building's floor or step). Ways are found with A* (8 + * Walking map: a grid over the site in two levels - where one stands (the + * ground, or a building's floor or step) and, under a floor, the level below + * it (a basement). A cell of a level is blocked where something fixed stands + * between knee and head height over it. Ways are found with A* (8 * directions, no cutting corners, no more up or down from cell to cell than a - * step or two) and then straightened: only the turns are kept. + * step or two, from one level to the other where they meet: stairs) and then + * straightened: only the turns are kept. */ #include "world/nav.h" @@ -12,16 +14,19 @@ #include <string.h> #define NAV_N ((int)(TERRAIN_SIZE / NAV_CELL)) -#define NAV_MARGIN 0.25f /* clearance from walls (sideways) */ +#define NAV_MARGIN 0.18f /* clearance from walls (sideways): an 0.8 m landing or doorway still has a row of free cells */ #define STEP_OVER 0.25f /* lower than this above the ground: stepped over */ #define HEADROOM 1.8f /* higher than this: walked under */ #define MOST_STEPS 90000 /* cells looked at for one way, at most */ #define FREE_SEARCH 6 /* cells round a blocked start or end to find a free one */ #define WALK_ON_MOST 1.2f /* building floors and steps (OBJ_WALL) up to this over the natural ground are walked on ... */ #define NAV_RISE 0.38f /* ... from one cell to the next at most this much up or down (a stair: 1-2 steps) */ +#define BELOW_ROOM 2.0f /* a level this far under the top one (or more): a second one there (a basement) */ +#define NO_FLOOR -1e9f +#define N2 ((size_t)NAV_N * NAV_N) /* cells in one level; node = level * N2 + cell */ -static unsigned char *g_blocked; -static float *g_floor; /* where one stands in each cell: the ground, or a floor / step of a building */ +static unsigned char *g_blocked; /* [2 * N2] */ +static float *g_floor; /* [2 * N2] where one stands: the ground, a floor or step of a building; NO_FLOOR: no lower level */ static bool g_ready; static double g_signature = -1.0; @@ -37,9 +42,9 @@ static int g_heap_n, g_heap_cap; static int cellOf(float v) { return (int)floorf((v + TERRAIN_SIZE * 0.5f) / NAV_CELL); } static float centerOf(int i) { return ((float)i + 0.5f) * NAV_CELL - TERRAIN_SIZE * 0.5f; } static bool inside(int x, int z) { return x >= 0 && z >= 0 && x < NAV_N && z < NAV_N; } -static bool open(int x, int z) { return inside(x, z) && !g_blocked[z * NAV_N + x]; } -/* From one cell to the next: no more up or down than a step or two. */ -static bool canStep(int a, int b, bool diagonal) { return fabsf(g_floor[a] - g_floor[b]) <= (diagonal ? NAV_RISE * 1.42f : NAV_RISE); } +static int node(int level, int x, int z) { return (int)((size_t)level * N2) + z * NAV_N + x; } +static bool openAt(int level, int x, int z) { return inside(x, z) && !g_blocked[node(level, x, z)]; } +static bool open(int x, int z) { return openAt(0, x, z) || openAt(1, x, z); } /* Point in the collider, with clearance sideways only (not up and down). */ static bool inCollider(const Object *o, int i, Vec3 p) { @@ -93,7 +98,7 @@ static bool walkOn(const Object *o, int i, const Terrain *terrain, float *top) { return *top <= Terrain_naturalHeight(terrain, center.x, center.z) + WALK_ON_MOST; } -static void raiseFloor(const Object *o, int i, const Terrain *terrain, CellRect clip) { +static void raiseFloor(const Object *o, int i, const Terrain *terrain, CellRect clip, int level) { float top; if (!walkOn(o, i, terrain, &top)) return; const Collider *c = &o->colliders[i]; @@ -109,7 +114,8 @@ static void raiseFloor(const Object *o, int i, const Terrain *terrain, CellRect for (int x = x0; x <= x1; x++) { Vec3 rel = vec3_sub(vec3(centerOf(x), center.y, centerOf(z)), center); if (fabsf(vec3_dot(rel, c->axes[0])) > c->half_extents.x || fabsf(vec3_dot(rel, c->axes[2])) > c->half_extents.z) continue; - float *f = &g_floor[z * NAV_N + x]; + float *f = &g_floor[node(level, x, z)]; + if (level == 1 && top > g_floor[node(0, x, z)] - BELOW_ROOM) continue; /* (the level below: under the top one) */ if (top > *f) *f = top; } } @@ -130,18 +136,20 @@ static void blockCollider(const Object *o, int i, CellRect clip) { 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 = g_floor[z * NAV_N + x]; - if (center.y + ey < ground + STEP_OVER || center.y - ey > ground + HEADROOM) continue; - for (int k = 0; k < 3; k++) - if (inCollider(o, i, vec3(px, ground + 0.4f + 0.6f * (float)k, pz))) { - g_blocked[z * NAV_N + x] = 1; - break; - } - } + for (int level = 0; level < 2; level++) + for (int z = z0; z <= z1; z++) + for (int x = x0; x <= x1; x++) { + int n = node(level, x, z); + if (g_blocked[n]) continue; + float px = centerOf(x), pz = centerOf(z); + float ground = g_floor[n]; + if (center.y + ey < ground + STEP_OVER || center.y - ey > ground + HEADROOM) continue; + for (int k = 0; k < 3; k++) + if (inCollider(o, i, vec3(px, ground + 0.4f + 0.6f * (float)k, pz))) { + g_blocked[n] = 1; + break; + } + } } /* What each fixed thing was at the last build (by pointer, open addressing): @@ -165,14 +173,29 @@ static NavEntry *slotFor(NavEntry *table, const Object *o, bool *found) { /* 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 x = r.x0; x <= r.x1; x++) g_floor[z * NAV_N + x] = Terrain_height(terrain, centerOf(x), centerOf(z)); + memset(&g_blocked[node(0, r.x0, z)], 0, (size_t)(r.x1 - r.x0 + 1)); + memset(&g_blocked[node(1, r.x0, z)], 0, (size_t)(r.x1 - r.x0 + 1)); + for (int x = r.x0; x <= r.x1; x++) g_floor[node(0, x, z)] = Terrain_height(terrain, centerOf(x), centerOf(z)); } for (int i = 0; i < count; i++) { /* floors and steps first: what's in the way is from there up */ const Object *o = objects[i]; if (!o->is_static || o->type != OBJ_WALL || !overlaps(boundsOf(o), r)) continue; - for (int c = 0; c < o->collider_count; c++) raiseFloor(o, c, terrain, r); + for (int c = 0; c < o->collider_count; c++) raiseFloor(o, c, terrain, r, 0); } + /* The level below (a basement under a floor): the ground there, or a step, well under the top one */ + for (int z = r.z0; z <= r.z1; z++) + for (int x = r.x0; x <= r.x1; x++) { + float ground = Terrain_height(terrain, centerOf(x), centerOf(z)); + g_floor[node(1, x, z)] = ground <= g_floor[node(0, x, z)] - BELOW_ROOM ? ground : NO_FLOOR; + } + for (int i = 0; i < count; i++) { + const Object *o = objects[i]; + if (!o->is_static || o->type != OBJ_WALL || !overlaps(boundsOf(o), r)) continue; + for (int c = 0; c < o->collider_count; c++) raiseFloor(o, c, terrain, r, 1); + } + for (int z = r.z0; z <= r.z1; z++) + for (int x = r.x0; x <= r.x1; x++) + if (g_floor[node(1, x, z)] == NO_FLOOR) g_blocked[node(1, x, z)] = 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; @@ -191,7 +214,7 @@ void Nav_update(Object *const *objects, int count, const Terrain *terrain) { } if (g_ready && signature == g_signature) return; if (!g_blocked) { - size_t n = (size_t)NAV_N * NAV_N; + size_t n = 2 * N2; g_blocked = calloc(n, 1); g_floor = calloc(n, sizeof *g_floor); g_cost = malloc(n * sizeof *g_cost); @@ -251,9 +274,16 @@ bool Nav_free(Vec3 p) { return !g_ready || open(cellOf(p.x), cellOf(p.z)); } -/* Nearest open cell to (x, z) within a few cells; false if none. */ -static bool nearestOpen(int *x, int *z) { - if (open(*x, *z)) return true; +/* The level at (x, z) nearest the height y (the lower one only if it's there and nearer). */ +static int levelFor(int x, int z, float y) { + if (!inside(x, z)) return 0; + float f0 = g_floor[node(0, x, z)], f1 = g_floor[node(1, x, z)]; + return f1 != NO_FLOOR && fabsf(y - f1) < fabsf(y - f0) ? 1 : 0; +} + +/* Nearest open cell of that level to (x, z) within a few cells; false if none. */ +static bool nearestOpen(int level, int *x, int *z) { + if (openAt(level, *x, *z)) return true; for (int r = 1; r <= FREE_SEARCH; r++) { int best = -1; float best_d = 1e9f; @@ -261,7 +291,7 @@ static bool nearestOpen(int *x, int *z) { for (int dx = -r; dx <= r; dx++) { if (abs(dx) != r && abs(dz) != r) continue; float d = (float)(dx * dx + dz * dz); - if (open(*x + dx, *z + dz) && d < best_d) { + if (openAt(level, *x + dx, *z + dz) && d < best_d) { best_d = d; best = (*z + dz) * NAV_N + (*x + dx); } @@ -307,34 +337,52 @@ static float guess(int x, int z, int gx, int gz) { return (dx + dz) + (1.41421356f - 2.0f) * fminf(dx, dz); } -/* Straight line between two cells without touching a blocked one. */ -static bool clearLine(int ax, int az, int bx, int bz) { +/* The open node at (x, z) a step from height `from` (either level), or -1. */ +static int stepTo(int x, int z, float from, bool diagonal) { + for (int level = 0; level < 2; level++) { + if (!openAt(level, x, z)) continue; + int n = node(level, x, z); + if (fabsf(g_floor[n] - from) <= (diagonal ? NAV_RISE * 1.42f : NAV_RISE)) return n; + } + return -1; +} + +/* Straight line between two nodes over open cells, a step at a time up or down. */ +static bool clearLine(int a, int b) { + int ac = (int)((size_t)a % N2), bc = (int)((size_t)b % N2); + int ax = ac % NAV_N, az = ac / NAV_N, bx = bc % NAV_N, bz = bc / NAV_N; float dx = (float)(bx - ax), dz = (float)(bz - az); - int steps = (int)(fmaxf(fabsf(dx), fabsf(dz)) * 3.0f) + 1, last = az * NAV_N + ax; + int steps = (int)(fmaxf(fabsf(dx), fabsf(dz)) * 3.0f) + 1, last = ac; + float floor_y = g_floor[a]; for (int s = 1; s <= steps; s++) { float t = (float)s / (float)steps; float fx = (float)ax + 0.5f + dx * t, fz = (float)az + 0.5f + dz * t; - if (!open((int)floorf(fx), (int)floorf(fz))) return false; - int here = (int)floorf(fz) * NAV_N + (int)floorf(fx); - if (here != last && !canStep(last, here, true)) return false; /* (not off the edge of a floor) */ - last = here; + int cx = (int)floorf(fx), cz = (int)floorf(fz), here = cz * NAV_N + cx; + if (here != last) { + int n = stepTo(cx, cz, floor_y, true); /* (not off the edge of a floor) */ + if (n < 0) return false; + floor_y = g_floor[n]; + last = here; + } /* (a little to each side too: no grazing corners) */ - if (!open((int)floorf(fx + 0.3f), (int)floorf(fz + 0.3f)) || !open((int)floorf(fx - 0.3f), (int)floorf(fz - 0.3f))) + if (stepTo((int)floorf(fx + 0.3f), (int)floorf(fz + 0.3f), floor_y, true) < 0 || + stepTo((int)floorf(fx - 0.3f), (int)floorf(fz - 0.3f), floor_y, true) < 0) return false; } - return true; + return fabsf(floor_y - g_floor[b]) <= NAV_RISE * 1.42f; } int Nav_findPath(Vec3 from, Vec3 to, Vec3 *out, int max) { if (!g_ready || max < 1) return 0; int sx = cellOf(from.x), sz = cellOf(from.z), gx = cellOf(to.x), gz = cellOf(to.z); - bool goal_open = open(gx, gz); - if (!nearestOpen(&sx, &sz) || !nearestOpen(&gx, &gz)) return 0; - int start = sz * NAV_N + sx, goal = gz * NAV_N + gx; + int s_level = levelFor(sx, sz, from.y), g_level = levelFor(gx, gz, to.y); + bool goal_open = openAt(g_level, gx, gz); + if (!nearestOpen(s_level, &sx, &sz) || !nearestOpen(g_level, &gx, &gz)) return 0; + int start = node(s_level, sx, sz), goal = node(g_level, gx, gz); if (++g_stamp == 0) { /* wrapped: clear the stamps once */ - memset(g_seen, 0, (size_t)NAV_N * NAV_N * sizeof *g_seen); - memset(g_done, 0, (size_t)NAV_N * NAV_N * sizeof *g_done); + memset(g_seen, 0, 2 * N2 * sizeof *g_seen); + memset(g_done, 0, 2 * N2 * sizeof *g_done); g_stamp = 1; } g_heap_n = 0; @@ -346,29 +394,29 @@ int Nav_findPath(Vec3 from, Vec3 to, Vec3 *out, int max) { bool found = false; for (int steps = 0; g_heap_n > 0 && steps < MOST_STEPS; steps++) { HeapItem it = heapPop(); - int cell = it.cell; - if (g_done[cell] == g_stamp) continue; - g_done[cell] = g_stamp; - if (cell == goal) { found = true; break; } - int x = cell % NAV_N, z = cell / NAV_N; + int cur = it.cell; + if (g_done[cur] == g_stamp) continue; + g_done[cur] = g_stamp; + if (cur == goal) { found = true; break; } + int cell = (int)((size_t)cur % N2), x = cell % NAV_N, z = cell / NAV_N; + float here = g_floor[cur]; for (int k = 0; k < 8; k++) { int nx = x + DX[k], nz = z + DZ[k]; - if (!open(nx, nz)) continue; - if (k >= 4 && (!open(x + DX[k], z) || !open(x, z + DZ[k]))) continue; /* no cutting corners */ - int n = nz * NAV_N + nx; - if (!canStep(cell, n, k >= 4)) continue; /* (up or down a floor's edge: no) */ + int n = stepTo(nx, nz, here, k >= 4); /* (up or down a floor's edge: no) */ + if (n < 0) continue; + if (k >= 4 && stepTo(x + DX[k], z, here, false) < 0 && stepTo(x, z + DZ[k], here, false) < 0) continue; /* no squeezing between two corners (past one: the margin's room enough - a turned stair runs diagonally) */ if (g_done[n] == g_stamp) continue; - float cost = g_cost[cell] + (k < 4 ? 1.0f : 1.41421356f); + float cost = g_cost[cur] + (k < 4 ? 1.0f : 1.41421356f); if (g_seen[n] == g_stamp && cost >= g_cost[n]) continue; g_seen[n] = g_stamp; g_cost[n] = cost; - g_from[n] = cell; + g_from[n] = cur; heapPush(cost + guess(nx, nz, gx, gz), n); } } if (!found) return 0; - /* The cells back from the goal, then only the turns that matter */ + /* The nodes back from the goal, then only the turns that matter */ int length = 0; for (int c = goal; c >= 0; c = g_from[c]) length++; int *chain = malloc((size_t)length * sizeof *chain); @@ -378,13 +426,13 @@ int Nav_findPath(Vec3 from, Vec3 to, Vec3 *out, int max) { int n = 0, at = 0; while (at < length - 1 && n < max) { int next = at + 1; /* as far along as can be seen straight */ - while (next + 1 < length && clearLine(chain[at] % NAV_N, chain[at] / NAV_N, chain[next + 1] % NAV_N, chain[next + 1] / NAV_N)) - next++; + while (next + 1 < length && clearLine(chain[at], chain[next + 1])) next++; at = next; - out[n++] = vec3(centerOf(chain[at] % NAV_N), 0.0f, centerOf(chain[at] / NAV_N)); + int cell = (int)((size_t)chain[at] % N2); + out[n++] = vec3(centerOf(cell % NAV_N), g_floor[chain[at]], centerOf(cell / NAV_N)); } free(chain); - if (n == 0) out[n++] = vec3(centerOf(gx), 0.0f, centerOf(gz)); - if (goal_open && at == length - 1) out[n - 1] = vec3(to.x, 0.0f, to.z); /* right to it */ + if (n == 0) out[n++] = vec3(centerOf(gx), g_floor[goal], centerOf(gz)); + if (goal_open && at == length - 1) out[n - 1] = vec3(to.x, g_floor[goal], to.z); /* right to it */ return n; }