floor test inclusion rule from the code (edge planes with a small tolerance, highest plane wins): explains kuzco floating next to the crate in jungle day 2 (needle triangle tip), tools/floortest.py re-implements the engine test
This commit is contained in:
1 parent
c39d58bdac
commit
9e8e7f9a10
5 files changed
+183
-1
No files matched your search
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Exact re-implementation of the engine's map floor test (FUN_004036d0, floor branch) on raw collision records.
|
||||
|
||||
floortest.py LEVEL.wad X Y Z world position in cells (game space, z = -A.z); prints every polygon of the
|
||||
cell's tiles that is a floor candidate, whether it passes, and the winner
|
||||
|
||||
Integer arithmetic as in groove.exe (20.12 fixed point, sine table read from re/groove.exe, C division).
|
||||
See notes/geometry.md "Floor test: inclusion rule and the floating-next-to-the-log case".
|
||||
"""
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, HERE)
|
||||
import geomlib as G # noqa: E402
|
||||
|
||||
_EXE = os.path.join(os.path.dirname(HERE), 're', 'groove.exe')
|
||||
SIN = struct.unpack_from('<4096i', open(_EXE, 'rb').read(), 0x574318 - 0x400000) # DAT_00574318
|
||||
PROBE = 0x400 # DAT_0057dd98 (0.25) unless strat flag 0x200000
|
||||
|
||||
|
||||
def _c8(b):
|
||||
return b - 256 if b > 127 else b
|
||||
|
||||
|
||||
def _cdiv(a, b):
|
||||
q = abs(a) // abs(b)
|
||||
return q if (a >= 0) == (b > 0) else -q
|
||||
|
||||
|
||||
def to_local(px, pz, tile_b):
|
||||
"""World (fixed) -> tile-local x/z exactly like 0x4037b3..0x4037f1 (rotation by -ry)."""
|
||||
lx, lz = px - tile_b[3], pz - tile_b[5]
|
||||
u = (-tile_b[1]) & 0xffffffff
|
||||
if u:
|
||||
s, c = SIN[u & 0xfff], SIN[(u + 0x400) & 0xfff]
|
||||
lx, lz = (s * lz + c * lx) >> 12, (c * lz - s * lx) >> 12
|
||||
return lx, lz
|
||||
|
||||
|
||||
def test_poly(r, lx, ly, lz):
|
||||
"""One 32-byte record. ly = probe height (feet - tile y + 0.25). Returns None (skipped) or
|
||||
(dist, plane_y_local, passed, edge_values, tolerance); dist = probe height above the plane."""
|
||||
ny32 = _c8(r[3]) * 32
|
||||
if ny32 <= 0: # only up-facing planes (0x200000 strats also take down-facing ones)
|
||||
return None
|
||||
d0 = struct.unpack_from('<h', r, 6)[0]
|
||||
i6 = _cdiv(d0 * 4096 - _c8(r[4]) * 32 * lz - _c8(r[2]) * 32 * lx - ny32 * ly, ny32)
|
||||
dist = -i6
|
||||
y = ly + i6 # the probe dropped vertically onto the plane
|
||||
tol = -0x40 - ((0x1000 - ny32) >> 6)
|
||||
vals = []
|
||||
for k in range(4 if r[0] & 1 else 3):
|
||||
nx, ny, nz, _, d = struct.unpack_from('<bbbBh', r, 8 + 6 * k)
|
||||
vals.append(((ny * 32 * y + nz * 32 * lz + nx * 32 * lx) >> 12) - d)
|
||||
return dist, y, all(v >= tol for v in vals), vals, tol
|
||||
|
||||
|
||||
def floor(wad, x, y, z, wfpc_8000000=False):
|
||||
ch, wf, trak, mp = G.load_level(wad)
|
||||
px, py, pz = (int(round(v * 4096)) for v in (x, y, z))
|
||||
skip_mat = 0x12 if wfpc_8000000 else 0x11
|
||||
best = None
|
||||
rows = []
|
||||
for t in range(mp['N']):
|
||||
b = mp['B'][t]
|
||||
if (b[3] >> 12, b[5] >> 12) != (px >> 12, pz >> 12):
|
||||
continue
|
||||
lx, lz = to_local(px, pz, b)
|
||||
ly = py - b[4] + PROBE
|
||||
recs = [r for g in trak[mp['midx'][t]].coll for r in g]
|
||||
for i, r in enumerate(recs):
|
||||
if r[1] == skip_mat:
|
||||
continue
|
||||
res = test_poly(r, lx, ly, lz)
|
||||
if res is None or res[0] < -0x80:
|
||||
continue # plane more than 1/32 above the probe: ceiling branch, not a floor
|
||||
dist, yl, ok, vals, tol = res
|
||||
rows.append((t, i, r[1], dist / 4096, (b[4] + yl) / 4096, ok, vals, tol))
|
||||
if ok and (best is None or dist < best[0]):
|
||||
best = (dist, t, i, (b[4] + yl) / 4096)
|
||||
return rows, best
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) != 5:
|
||||
sys.exit(__doc__)
|
||||
rows, best = floor(sys.argv[1], *map(float, sys.argv[2:5]))
|
||||
for t, i, mat, dist, h, ok, vals, tol in rows:
|
||||
print(f'tile {t} poly {i:2d} mat {mat:3d} probe-plane {dist:+.4f} plane y {h:.4f} '
|
||||
f'{"PASS" if ok else "fail"} edges {vals} (tolerance {tol})')
|
||||
print('floor:', f'tile {best[1]} poly {best[2]} height {best[3]:.4f}' if best else 'none in this cell')
|
||||
Reference in new issue
Block a user