feat(nav): 17.1/17.3 - ludic.nav over Recast & Detour v1.6.0 (zlib), built here from the pinned tag on the Mac and the PC (the DLL imports KERNEL32 alone): a navmesh per kind of walker from triangles with an area byte each and cylinders nothing stands in, saved and loaded as bytes; the nearest point, a path as corners (partial when the end cannot be reached), whether a straight line stays walkable, a cost per kind of ground. Tests on a hand-built meadow: round a post, over a ford, stopped at a bank, a saved mesh answering alike - Mac and PC

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-27 15:29:11 +03:00
parent e397e4eee9
commit 9b8606483d
17 changed files with 587 additions and 0 deletions

View file

@ -0,0 +1,110 @@
# nav_test.ludic - ludic.nav on ground built by hand: a 40 m meadow of metre squares, a post in its
# middle, a river across it (area 0) with a ford or without one. A path goes round the post and over
# the ford, stops at the bank when there is none, and a saved mesh answers as the built one did.
import "ludic.nav"
import "ludic.base"
program NavTest {
numbers float
# the meadow, flat at y = 0; river 1 has a ford at z 30..34, river 2 none, river 0 is no river
function meadow(nav_st: mut NavState, kind: int, river: int, post: bool) -> bool {
let n = 40
let v = floats((n + 1) * (n + 1) * 3)
for j in 0 .. n + 1 {
for i in 0 .. n + 1 {
let o = (j * (n + 1) + i) * 3
v[o] = float(i)
v[o + 1] = 0.0
v[o + 2] = float(j)
}
}
let t = new []int
let area = buffer(n * n * 2)
for j in 0 .. n {
for i in 0 .. n {
let a = j * (n + 1) + i
push(t, a)
push(t, a + n + 1)
push(t, a + 1)
push(t, a + 1)
push(t, a + n + 1)
push(t, a + n + 2)
var k = 1
if river > 0 and i >= 18 and i < 22 and not (river == 1 and j >= 30 and j < 34) { k = 0 }
area[(j * n + i) * 2] = k
area[(j * n + i) * 2 + 1] = k
}
}
let cyl = floats(5)
cyl[0] = 20.0
cyl[1] = -1.0
cyl[2] = 20.0
cyl[3] = 2.0
cyl[4] = 4.0
var nc = 0
if post { nc = 1 }
return nav_build(nav_st, kind, v, (n + 1) * (n + 1), t, n * n * 2, area, cyl, nc, new NavConfig)
}
function last_x(nav_st: NavState) -> float { return nav_corner_x(nav_st, nav_corners(nav_st) - 1) }
function last_z(nav_st: NavState) -> float { return nav_corner_z(nav_st, nav_corners(nav_st) - 1) }
test "a path goes round a post, and the straight line through it is not walkable" (nav_st: mut NavState) {
expect(meadow(nav_st, NAV_PERSON, 0, true))
expect(nav_polygons(nav_st, NAV_PERSON) > 0)
let n = nav_path(nav_st, NAV_PERSON, 5.0, 0.0, 20.0, 35.0, 0.0, 20.0)
expect(n >= 3)
expect(not nav_partial(nav_st, NAV_PERSON))
expect(Math.abs(last_x(nav_st) - 35.0) < 0.1)
for i in 0 .. n {
let dx = nav_corner_x(nav_st, i) - 20.0
let dz = nav_corner_z(nav_st, i) - 20.0
expect(dx * dx + dz * dz > 2.0 * 2.0)
}
expect(not nav_straight(nav_st, NAV_PERSON, 5.0, 0.0, 20.0, 35.0, 20.0))
expect(nav_straight(nav_st, NAV_PERSON, 5.0, 0.0, 5.0, 35.0, 5.0))
}
test "a river is crossed at its ford" (nav_st: mut NavState) {
expect(meadow(nav_st, NAV_PERSON, 1, false))
let n = nav_path(nav_st, NAV_PERSON, 5.0, 0.0, 10.0, 35.0, 0.0, 10.0)
expect(n >= 3)
expect(not nav_partial(nav_st, NAV_PERSON))
var ford = false
for i in 0 .. n { if nav_corner_z(nav_st, i) > 29.0 { ford = true } }
expect(ford)
}
test "without a ford the path stops at the near bank and says so" (nav_st: mut NavState) {
expect(meadow(nav_st, NAV_PERSON, 2, false))
let n = nav_path(nav_st, NAV_PERSON, 5.0, 0.0, 10.0, 35.0, 0.0, 10.0)
expect(n >= 1)
expect(nav_partial(nav_st, NAV_PERSON))
expect(last_x(nav_st) < 18.0)
}
test "the nearest walkable point, and nothing off the mesh" (nav_st: mut NavState) {
expect(meadow(nav_st, NAV_PERSON, 0, false))
expect(nav_nearest(nav_st, NAV_PERSON, 10.0, 1.0, 10.0))
expect(Math.abs(nav_near_y(nav_st)) < 0.3)
expect(Math.abs(nav_near_x(nav_st) - 10.0) < 0.1)
expect(not nav_nearest(nav_st, NAV_PERSON, -50.0, 0.0, -50.0))
expect_eq(nav_path(nav_st, NAV_PERSON, -50.0, 0.0, -50.0, 10.0, 0.0, 10.0), -1)
expect_eq(nav_path(nav_st, NAV_LARGE, 5.0, 0.0, 5.0, 10.0, 0.0, 10.0), -1)
}
test "a mesh saved and loaded answers as the built one did" (nav_st: mut NavState) {
expect(meadow(nav_st, NAV_PERSON, 1, true))
let path = Os.temp_dir() + "/ludic_nav_test.navmesh"
expect(nav_save_file(nav_st, NAV_PERSON, path))
expect(nav_load_file(nav_st, NAV_SMALL, path))
let a = nav_path(nav_st, NAV_PERSON, 5.0, 0.0, 10.0, 35.0, 0.0, 10.0)
let ax = last_x(nav_st)
let az = last_z(nav_st)
let b = nav_path(nav_st, NAV_SMALL, 5.0, 0.0, 10.0, 35.0, 0.0, 10.0)
expect_eq(a, b)
expect_near(last_x(nav_st), ax, 0.0001)
expect_near(last_z(nav_st), az, 0.0001)
nav_reset(nav_st)
expect_eq(nav_polygons(nav_st, NAV_SMALL), 0)
}
}