diff --git a/packages/ludic.nav/README.md b/packages/ludic.nav/README.md index a873c946..0eabe81e 100644 --- a/packages/ludic.nav/README.md +++ b/packages/ludic.nav/README.md @@ -78,6 +78,7 @@ the PC (Git Bash, the LLVM installer's clang). `native/LICENSE-recastnavigation` its filters. `nav_crowd_add(kind, x, y, z, radius, height, speed, filter, separation)` adds a walker (snapped to the mesh, -1 off it), `nav_crowd_target` and `nav_crowd_speed` say where and how fast, and `nav_crowd_step(dt)` steps every crowd. `nav_crowd_read` puts a walker into `nav_agent_x/y/z/vx/vz`, -and `nav_crowd_us()` is what the stepping has cost. The mechanic decides and the crowd moves, and +and `nav_crowd_us()` is what the stepping has cost. `nav_crowd_add_fixed` adds a walker the crowd avoids +but never steers (a player), which `nav_crowd_place` puts where it is, and how fast it goes, each frame. The mechanic decides and the crowd moves, and dropping a mesh drops its crowd first. On the test meadow, twenty walkers crossing head-on never come closer than their two radii, and a step costs about 12 us. diff --git a/packages/ludic.nav/crowd.ludic b/packages/ludic.nav/crowd.ludic index 519ead41..b3801389 100644 --- a/packages/ludic.nav/crowd.ludic +++ b/packages/ludic.nav/crowd.ludic @@ -27,6 +27,17 @@ export function nav_crowd_add(nav_st: NavState, kind: int, x: float, y: float, z if c == null { return -1 } return nvc_crowd_add(c, x, y, z, radius, height, speed, filter, separation) } +# a walker the crowd avoids but never moves (a player): put where it is each frame with nav_crowd_place +export function nav_crowd_add_fixed(nav_st: NavState, kind: int, x: float, y: float, z: float, radius: float, height: float) -> int { + let c = nv_crowd(nav_st, kind) + if c == null { return -1 } + return nvc_crowd_add_fixed(c, x, y, z, radius, height) +} +# walker i put at (x, y, z) going (vx, vz); false off the mesh +export function nav_crowd_place(nav_st: NavState, kind: int, i: int, x: float, y: float, z: float, vx: float, vz: float) -> bool { + let c = nv_crowd(nav_st, kind) + return c != null and i >= 0 and nvc_crowd_place(c, i, x, y, z, vx, vz) == 1 +} export function nav_crowd_remove(nav_st: NavState, kind: int, i: int) -> void { let c = nv_crowd(nav_st, kind) if c != null and i >= 0 { nvc_crowd_remove(c, i) } diff --git a/packages/ludic.nav/lib/macos-arm64/libludicnav.dylib b/packages/ludic.nav/lib/macos-arm64/libludicnav.dylib index 474332e5..9bfb38c7 100755 --- a/packages/ludic.nav/lib/macos-arm64/libludicnav.dylib +++ b/packages/ludic.nav/lib/macos-arm64/libludicnav.dylib @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:82c2b394bc9a6640055be28ce5172057b665759828e499d057df0cffe94aa05a -size 198320 +oid sha256:422b71e66182f0c12a32fb4660c536b46d0665bfdd2fa3e6f751d16c14761b55 +size 198464 diff --git a/packages/ludic.nav/lib/windows-x64/ludicnav.dll b/packages/ludic.nav/lib/windows-x64/ludicnav.dll index 253cf5f0..678bc410 100644 --- a/packages/ludic.nav/lib/windows-x64/ludicnav.dll +++ b/packages/ludic.nav/lib/windows-x64/ludicnav.dll @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1a0eea4d94669a548e4874c07bc23caa321bf891f7a724331163e64f10006099 -size 374784 +oid sha256:14a3b0a5f6e6a6af79f8a6c205baeee57fc47d42148dec4190a8b3fdff633223 +size 375296 diff --git a/packages/ludic.nav/lib/windows-x64/ludicnav.lib b/packages/ludic.nav/lib/windows-x64/ludicnav.lib index 32d4c3af..e6dca282 100644 --- a/packages/ludic.nav/lib/windows-x64/ludicnav.lib +++ b/packages/ludic.nav/lib/windows-x64/ludicnav.lib @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:754f4ea0ce90f7b512d9e6e12f59c5babaf1d071dfd8a30faff93bba8af52cee -size 5804 +oid sha256:382674cb7e3141c20b852cd25b43c6348de433fad36141b97335c0c44de9b5c7 +size 6228 diff --git a/packages/ludic.nav/native.ludic b/packages/ludic.nav/native.ludic index 0f1db0f2..b7f59072 100644 --- a/packages/ludic.nav/native.ludic +++ b/packages/ludic.nav/native.ludic @@ -18,6 +18,8 @@ extern function nvc_stage(size: int) -> pointer = "nav_stage" extern function nvc_crowd_new(h: pointer, max: int, max_radius: float) -> pointer = "nav_crowd_new" extern function nvc_crowd_free(c: pointer) -> void = "nav_crowd_free" extern function nvc_crowd_add(c: pointer, x: float, y: float, z: float, radius: float, height: float, speed: float, filter: int, separation: float) -> int = "nav_crowd_add" +extern function nvc_crowd_add_fixed(c: pointer, x: float, y: float, z: float, radius: float, height: float) -> int = "nav_crowd_add_fixed" +extern function nvc_crowd_place(c: pointer, i: int, x: float, y: float, z: float, vx: float, vz: float) -> int = "nav_crowd_place" extern function nvc_crowd_remove(c: pointer, i: int) -> void = "nav_crowd_remove" extern function nvc_crowd_target(c: pointer, i: int, x: float, y: float, z: float) -> int = "nav_crowd_target" extern function nvc_crowd_speed(c: pointer, i: int, speed: float) -> void = "nav_crowd_speed" diff --git a/packages/ludic.nav/native/shim/nav_crowd.inl b/packages/ludic.nav/native/shim/nav_crowd.inl index 178df676..62a44c6d 100644 --- a/packages/ludic.nav/native/shim/nav_crowd.inl +++ b/packages/ludic.nav/native/shim/nav_crowd.inl @@ -46,6 +46,43 @@ NAV_SHIM int nav_crowd_add(void *p, float x, float y, float z, float radius, flo if (!ref) return -1; return c->crowd->addAgent(near, &ap); } +// a walker the crowd avoids but does not steer (a player): placed by nav_crowd_place each frame +NAV_SHIM int nav_crowd_add_fixed(void *p, float x, float y, float z, float radius, float height) { + Crowd *c = static_cast(p); + dtCrowdAgentParams ap; + memset(&ap, 0, sizeof(ap)); + ap.radius = radius; + ap.height = height; + ap.maxSpeed = 20.0f; + ap.maxAcceleration = 1000.0f; + ap.collisionQueryRange = radius * 12.0f; + ap.pathOptimizationRange = radius * 30.0f; + ap.separationWeight = 0.0f; + ap.updateFlags = 0; + float pos[3] = {x, y, z}, near[3]; + dtPolyRef ref = 0; + c->nav->query->findNearestPoly(pos, c->nav->ext, c->crowd->getFilter(0), &ref, near); + if (!ref) return -1; + return c->crowd->addAgent(near, &ap); +} +// walker i put at (x, y, z) going (vx, vz), snapped to the mesh; 0 when it is off it (left where it was) +NAV_SHIM int nav_crowd_place(void *p, int i, float x, float y, float z, float vx, float vz) { + Crowd *c = static_cast(p); + dtCrowdAgent *a = c->crowd->getEditableAgent(i); + if (!a || !a->active) return 0; + float pos[3] = {x, y, z}, near[3]; + dtPolyRef ref = 0; + c->nav->query->findNearestPoly(pos, c->nav->ext, c->crowd->getFilter(0), &ref, near); + if (!ref) return 0; + a->corridor.reset(ref, near); + dtVcopy(a->npos, near); + float v[3] = {vx, 0.0f, vz}; + dtVcopy(a->vel, v); + dtVcopy(a->dvel, v); + dtVcopy(a->nvel, v); + a->desiredSpeed = dtVlen(v); + return 1; +} NAV_SHIM void nav_crowd_remove(void *p, int i) { static_cast(p)->crowd->removeAgent(i); } // send walker i toward (x, y, z), snapped to the mesh; 0 when the point is off it diff --git a/packages/ludic.nav/tests/crowd_test.ludic b/packages/ludic.nav/tests/crowd_test.ludic index 183e87cd..8f3ca4f3 100644 --- a/packages/ludic.nav/tests/crowd_test.ludic +++ b/packages/ludic.nav/tests/crowd_test.ludic @@ -81,4 +81,39 @@ program CrowdTest { expect(not nav_crowd_read(nav_st, NAV_PERSON, a)) expect_eq(nav_crowd_add(nav_st, NAV_PERSON, 5.0, 0.0, 5.0, 0.35, 1.8, 1.4, 0, 2.0), -1) } + + test "a player standing in the way is walked round and never moved" (nav_st: mut NavState) { + expect(nav_build(nav_st, NAV_PERSON, ground(40, 0), new NavConfig)) + expect(nav_crowd_start(nav_st, NAV_PERSON, 16, 0.6)) + let me = nav_crowd_add_fixed(nav_st, NAV_PERSON, 20.0, 0.0, 20.0, 0.4, 1.8) + expect(me >= 0) + let ids = new []int + for i in 0 .. 6 { + let id = nav_crowd_add(nav_st, NAV_PERSON, 4.0, 0.0, 19.0 + float(i % 3), 0.35, 1.8, 1.4, 0, 2.0) + expect(nav_crowd_target(nav_st, NAV_PERSON, id, 36.0, 0.0, 19.0 + float(i % 3))) + push(ids, id) + } + var worst = 1000.0 + for k in 0 .. 1800 { + expect(nav_crowd_place(nav_st, NAV_PERSON, me, 20.0, 0.0, 20.0, 0.0, 0.0)) + nav_crowd_step(nav_st, 1.0 / 60.0) + for i in 0 .. len(ids) { + nav_crowd_read(nav_st, NAV_PERSON, ids[i]) + let dx = nav_agent_x(nav_st) - 20.0 + let dz = nav_agent_z(nav_st) - 20.0 + worst = Math.min(worst, Math.sqrt(dx * dx + dz * dz)) + } + } + nav_crowd_place(nav_st, NAV_PERSON, me, 20.0, 0.0, 20.0, 0.0, 0.0) + nav_crowd_read(nav_st, NAV_PERSON, me) + expect_near(nav_agent_x(nav_st), 20.0, 0.01) + var past = 0 + for i in 0 .. len(ids) { + nav_crowd_read(nav_st, NAV_PERSON, ids[i]) + if nav_agent_x(nav_st) > 34.0 { past += 1 } + } + print(`crowd: a walker came within {worst} m of the player (radii 0.75); {past} of 6 got past`) + expect(worst > 0.65) + expect_eq(past, 6) + } }