feat(physics): phys_step_top - the highest top a foot could step up to, walls left out (what CharacterGround.top_at asks)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
204ec93c5a
commit
1c814464c5
5 changed files with 66 additions and 2 deletions
BIN
packages/ludic.physics/lib/macos-arm64/libludicjolt.dylib
(Stored with Git LFS)
BIN
packages/ludic.physics/lib/macos-arm64/libludicjolt.dylib
(Stored with Git LFS)
Binary file not shown.
|
|
@ -30,3 +30,4 @@ extern function jph_ray(w: pointer, ox: float, oy: float, oz: float, dx: float,
|
|||
extern function jph_top_at(w: pointer, x: float, z: float, r: float, from_y: float, depth: float, mask: int) -> float = "jph_top_at"
|
||||
extern function jph_overlap(w: pointer, x: float, y: float, z: float, r: float, mask: int, ids: pointer, cap: int) -> int = "jph_overlap"
|
||||
extern function jph_push(w: pointer, x: float, z: float, r: float, y0: float, y1: float, mask: int, out: pointer) -> int = "jph_push"
|
||||
extern function jph_step_top(w: pointer, x: float, z: float, r: float, feet: float, reach: float, mask: int) -> float = "jph_step_top"
|
||||
|
|
|
|||
|
|
@ -79,3 +79,40 @@ JPH_SHIM int jph_push(void *w, float x, float z, float r, float y0, float y1, in
|
|||
out[1] = pz;
|
||||
return int(hits.mHits.size());
|
||||
}
|
||||
|
||||
// the highest top under an upright body r wide at (x, z) that a foot at `feet` could stand on or
|
||||
// step up to: a thing whose top is above feet + reach is a wall and is left out, whatever else is
|
||||
// there. Each candidate's surface is found by letting a ball r across down onto it alone.
|
||||
// -FLT_MAX when there is nothing.
|
||||
JPH_SHIM float jph_step_top(void *w, float x, float z, float r, float feet, float reach, int mask) {
|
||||
World *ww = static_cast<World *>(w);
|
||||
float limit = feet + reach;
|
||||
float y0 = feet - 1.0f, y1 = limit + 0.05f;
|
||||
float half = (y1 - y0) * 0.5f;
|
||||
CylinderShape column(half, r, 0.0f);
|
||||
column.SetEmbedded();
|
||||
AllHitCollisionCollector<CollideShapeCollector> hits;
|
||||
MaskFilter f(mask);
|
||||
ww->sys.GetNarrowPhaseQuery().CollideShape(&column, Vec3::sOne(), RMat44::sTranslation(RVec3(x, y0 + half, z)),
|
||||
CollideShapeSettings(), RVec3::sZero(), hits, {}, f);
|
||||
hits.Sort();
|
||||
float best = -FLT_MAX;
|
||||
SphereShape ball(r);
|
||||
ball.SetEmbedded();
|
||||
float from = limit + r + 0.01f;
|
||||
RShapeCast cast = RShapeCast::sFromWorldTransform(&ball, Vec3::sOne(), RMat44::sTranslation(RVec3(x, from, z)),
|
||||
Vec3(0.0f, y0 - from, 0.0f));
|
||||
for (const CollideShapeResult &h : hits.mHits) {
|
||||
BodyLockRead lock(ww->sys.GetBodyLockInterface(), h.mBodyID2);
|
||||
if (!lock.Succeeded()) continue;
|
||||
const Body &b = lock.GetBody();
|
||||
float top = float(b.GetWorldSpaceBounds().mMax.GetY());
|
||||
if (top > limit + 1.0e-4f) continue;
|
||||
ClosestHitCollisionCollector<CastShapeCollector> c;
|
||||
b.GetTransformedShape().CastShape(cast, ShapeCastSettings(), RVec3::sZero(), c);
|
||||
float y = c.HadHit() ? float(c.mHit.mContactPointOn2.GetY()) : top;
|
||||
if (y > top) y = top;
|
||||
if (y > best) best = y;
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,16 @@ export function phys_top_at(physics_st: PhysicsState, x: float, z: float, r: flo
|
|||
return y
|
||||
}
|
||||
|
||||
# the highest top under an upright body r wide that a foot at `feet` could stand on or step up
|
||||
# to, or PHYS_NONE: anything whose top is above feet + reach is a wall and left out - what
|
||||
# CharacterGround.top_at asks when a boulder may be climbed and a cliff may not
|
||||
export function phys_step_top(physics_st: PhysicsState, x: float, z: float, r: float, feet: float, reach: float, mask: int) -> float {
|
||||
if physics_st.ph_world == null { return PHYS_NONE }
|
||||
let y = jph_step_top(physics_st.ph_world, x, z, r, feet, reach, mask)
|
||||
if y < PHYS_NONE { return PHYS_NONE }
|
||||
return y
|
||||
}
|
||||
|
||||
# the bodies a ball at (x, y, z) touches, in the order Jolt sorts them
|
||||
export function phys_overlap(physics_st: mut PhysicsState, x: float, y: float, z: float, r: float, mask: int) -> []int {
|
||||
let out = new []int
|
||||
|
|
|
|||
|
|
@ -94,4 +94,20 @@ program PhysicsTest {
|
|||
expect_eq(clear.n, 0)
|
||||
phys_close(physics_st)
|
||||
}
|
||||
|
||||
test "a step's top counts, a wall's does not, and the step beside a wall still does" (physics_st: mut PhysicsState) {
|
||||
ground(physics_st)
|
||||
let step = phys_offset(physics_st, phys_cylinder(physics_st, 0.15, 0.8), 0.0, 0.15, 0.0, 0.0)
|
||||
let wall = phys_offset(physics_st, phys_cylinder(physics_st, 1.5, 0.8), 0.0, 1.5, 0.0, 0.0)
|
||||
phys_static_add(physics_st, step, 0.0, 0.0, 0.0, 0.0)
|
||||
phys_static_add(physics_st, wall, 1.5, 0.0, 0.0, 0.0)
|
||||
phys_static_add(physics_st, wall, 5.0, 0.0, 0.0, 0.0)
|
||||
phys_settle(physics_st)
|
||||
expect(Math.abs(phys_step_top(physics_st, 0.0, 0.0, 0.35, 0.0, 0.55, PHYS_M_STATIC) - 0.3) < 0.02)
|
||||
expect(Math.abs(phys_step_top(physics_st, 0.6, 0.0, 0.35, 0.0, 0.55, PHYS_M_STATIC) - 0.3) < 0.02)
|
||||
expect(phys_step_top(physics_st, 5.0, 0.0, 0.35, 0.0, 0.55, PHYS_M_STATIC) == PHYS_NONE)
|
||||
expect(phys_step_top(physics_st, -5.0, 0.0, 0.35, 0.0, 0.55, PHYS_M_STATIC) == PHYS_NONE)
|
||||
expect(Math.abs(phys_step_top(physics_st, 5.0, 0.0, 0.35, 2.9, 0.55, PHYS_M_STATIC) - 3.0) < 0.02)
|
||||
phys_close(physics_st)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue