feat(physics): phys_resolve (two passes, the place kept) - a contact more up than across is ground under the feet, not a wall
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
1c814464c5
commit
28c54e4b39
6 changed files with 58 additions and 5 deletions
|
|
@ -39,6 +39,8 @@ import "ludic.physics"
|
||||||
| `phys_ray(st, o, d, mask) -> PhysHit` | the first thing along a ray |
|
| `phys_ray(st, o, d, mask) -> PhysHit` | the first thing along a ray |
|
||||||
| `phys_top_at(st, x, z, r, from_y, depth, mask) -> float` | what a foot would land on (`CharacterGround.top_at`), `PHYS_NONE` if nothing |
|
| `phys_top_at(st, x, z, r, from_y, depth, mask) -> float` | what a foot would land on (`CharacterGround.top_at`), `PHYS_NONE` if nothing |
|
||||||
| `phys_push(st, x, z, r, y0, y1, mask) -> PhysPush` | the move that stands an upright body clear (`CharacterGround.push`) |
|
| `phys_push(st, x, z, r, y0, y1, mask) -> PhysPush` | the move that stands an upright body clear (`CharacterGround.push`) |
|
||||||
|
| `phys_resolve(st, x, z, r, y0, y1, mask) -> bool`, `phys_resolved_x` / `_z` | pushed clear in two passes, the place kept (`CharacterGround.push`, `pushed_x` / `_z`); a contact more up than across is ground under the feet, not a wall |
|
||||||
|
| `phys_step_top(st, x, z, r, feet, reach, mask) -> float` | the highest top a foot could step up to, anything taller than `feet + reach` left out as a wall |
|
||||||
| `phys_overlap(st, x, y, z, r, mask) -> []int` | the bodies a ball touches |
|
| `phys_overlap(st, x, y, z, r, mask) -> []int` | the bodies a ball touches |
|
||||||
| `phys_float(st, id, buoyancy, linear_drag, angular_drag)`, `phys_sink`, `phys_floating` | a body buoyed each step against `PhysWater` and carried by its current |
|
| `phys_float(st, id, buoyancy, linear_drag, angular_drag)`, `phys_sink`, `phys_floating` | a body buoyed each step against `PhysWater` and carried by its current |
|
||||||
| `phys_facts(st)` | `PhysFact`: `PHYS_HIT` (two bodies met at `ph_hard` m/s or more), `PHYS_SPLASH` (a floating body reached water) |
|
| `phys_facts(st)` | `PhysFact`: `PHYS_HIT` (two bodies met at `ph_hard` m/s or more), `PHYS_SPLASH` (a floating body reached water) |
|
||||||
|
|
|
||||||
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.
|
|
@ -53,7 +53,7 @@ JPH_SHIM int jph_overlap(void *w, float x, float y, float z, float r, int mask,
|
||||||
}
|
}
|
||||||
|
|
||||||
// an upright body r wide, from y0 to y1, at (x, z): how far to move it in x and z to stand clear
|
// an upright body r wide, from y0 to y1, at (x, z): how far to move it in x and z to stand clear
|
||||||
// of what it overlaps in mask, into out[2]; returns how many things it overlapped
|
// of what it overlaps in mask, into out[2]; returns how many things were in the way
|
||||||
JPH_SHIM int jph_push(void *w, float x, float z, float r, float y0, float y1, int mask, float *out) {
|
JPH_SHIM int jph_push(void *w, float x, float z, float r, float y0, float y1, int mask, float *out) {
|
||||||
World *ww = static_cast<World *>(w);
|
World *ww = static_cast<World *>(w);
|
||||||
float half = (y1 - y0) * 0.5f;
|
float half = (y1 - y0) * 0.5f;
|
||||||
|
|
@ -66,18 +66,23 @@ JPH_SHIM int jph_push(void *w, float x, float z, float r, float y0, float y1, in
|
||||||
ww->sys.GetNarrowPhaseQuery().CollideShape(&body, Vec3::sOne(), RMat44::sTranslation(RVec3(x, y0 + half, z)),
|
ww->sys.GetNarrowPhaseQuery().CollideShape(&body, Vec3::sOne(), RMat44::sTranslation(RVec3(x, y0 + half, z)),
|
||||||
cs, RVec3::sZero(), hits, {}, f);
|
cs, RVec3::sZero(), hits, {}, f);
|
||||||
hits.Sort();
|
hits.Sort();
|
||||||
|
// a contact more up than across is what the feet stand on (a rock's top, a dome under them),
|
||||||
|
// not a wall, and a touch with nothing in it is no contact
|
||||||
float px = 0.0f, pz = 0.0f;
|
float px = 0.0f, pz = 0.0f;
|
||||||
|
int n = 0;
|
||||||
for (const CollideShapeResult &h : hits.mHits) {
|
for (const CollideShapeResult &h : hits.mHits) {
|
||||||
Vec3 a = h.mPenetrationAxis;
|
if (h.mPenetrationDepth <= 1.0e-4f) continue;
|
||||||
|
Vec3 a = h.mPenetrationAxis.NormalizedOr(Vec3::sAxisY());
|
||||||
Vec3 flat(a.GetX(), 0.0f, a.GetZ());
|
Vec3 flat(a.GetX(), 0.0f, a.GetZ());
|
||||||
if (flat.LengthSq() < 1.0e-8f) continue;
|
if (flat.Length() < 0.5f) continue;
|
||||||
flat = flat.Normalized() * h.mPenetrationDepth;
|
flat = flat.Normalized() * h.mPenetrationDepth;
|
||||||
px -= flat.GetX();
|
px -= flat.GetX();
|
||||||
pz -= flat.GetZ();
|
pz -= flat.GetZ();
|
||||||
|
n++;
|
||||||
}
|
}
|
||||||
out[0] = px;
|
out[0] = px;
|
||||||
out[1] = pz;
|
out[1] = pz;
|
||||||
return int(hits.mHits.size());
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
// the highest top under an upright body r wide at (x, z) that a foot at `feet` could stand on or
|
// the highest top under an upright body r wide at (x, z) that a foot at `feet` could stand on or
|
||||||
|
|
|
||||||
|
|
@ -72,3 +72,25 @@ export function phys_push(physics_st: mut PhysicsState, x: float, z: float, r: f
|
||||||
p.z = physics_st.ph_vals[1]
|
p.z = physics_st.ph_vals[1]
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# a body r wide from y0 to y1 at (x, z) moved clear of what it stands in, two passes over (a push out
|
||||||
|
# of one thing can put it into another); true when it moved, the place in phys_resolved_x / _z -
|
||||||
|
# what CharacterGround.push and pushed_x / _z ask
|
||||||
|
export function phys_resolve(physics_st: mut PhysicsState, x: float, z: float, r: float, y0: float, y1: float, mask: int) -> bool {
|
||||||
|
var px = x
|
||||||
|
var pz = z
|
||||||
|
var moved = false
|
||||||
|
for pass in 0 .. 2 {
|
||||||
|
let p = phys_push(physics_st, px, pz, r, y0, y1, mask)
|
||||||
|
if p.n > 0 {
|
||||||
|
px += p.x
|
||||||
|
pz += p.z
|
||||||
|
moved = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
physics_st.ph_rx = px
|
||||||
|
physics_st.ph_rz = pz
|
||||||
|
return moved
|
||||||
|
}
|
||||||
|
export function phys_resolved_x(physics_st: PhysicsState) -> float { return physics_st.ph_rx }
|
||||||
|
export function phys_resolved_z(physics_st: PhysicsState) -> float { return physics_st.ph_rz }
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,8 @@ export state PhysicsState {
|
||||||
ph_hard: float = 2.0 # metres a second: a contact under this is not a fact
|
ph_hard: float = 2.0 # metres a second: a contact under this is not a fact
|
||||||
ph_floats: []int = null # buoyant bodies' ids
|
ph_floats: []int = null # buoyant bodies' ids
|
||||||
ph_float_k: []float = null # and theirs: buoyancy, linear drag, angular drag, wet
|
ph_float_k: []float = null # and theirs: buoyancy, linear drag, angular drag, wet
|
||||||
|
ph_rx: float = 0.0 # where the last phys_resolve put a body
|
||||||
|
ph_rz: float = 0.0
|
||||||
ph_ids: []int = null
|
ph_ids: []int = null
|
||||||
ph_vals: []float = null
|
ph_vals: []float = null
|
||||||
ph_facts: Queue<PhysFact> = null
|
ph_facts: Queue<PhysFact> = null
|
||||||
|
|
|
||||||
|
|
@ -110,4 +110,26 @@ program PhysicsTest {
|
||||||
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)
|
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)
|
phys_close(physics_st)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "a body is resolved clear of a post, and standing on a rock is not being in it" (physics_st: mut PhysicsState) {
|
||||||
|
ground(physics_st)
|
||||||
|
let post = phys_offset(physics_st, phys_cylinder(physics_st, 1.0, 0.3), 0.0, 1.0, 0.0, 0.0)
|
||||||
|
phys_static_add(physics_st, post, 0.0, 0.0, 0.0, 0.0)
|
||||||
|
let rock = phys_offset(physics_st, phys_cylinder(physics_st, 0.2, 1.0), 0.0, 0.2, 0.0, 0.0)
|
||||||
|
phys_static_add(physics_st, rock, 5.0, 0.0, 0.0, 0.0)
|
||||||
|
phys_static_add(physics_st, phys_dome(physics_st, 1.5, 0.6), -5.0, 0.0, 0.0, 0.0)
|
||||||
|
phys_settle(physics_st)
|
||||||
|
expect(phys_resolve(physics_st, 0.4, 0.1, 0.35, 0.0, 1.8, PHYS_M_STATIC))
|
||||||
|
let dx = phys_resolved_x(physics_st)
|
||||||
|
let dz = phys_resolved_z(physics_st)
|
||||||
|
expect(Math.sqrt(dx * dx + dz * dz) > 0.64)
|
||||||
|
expect(not phys_resolve(physics_st, 5.2, 0.0, 0.35, 0.4, 2.2, PHYS_M_STATIC))
|
||||||
|
expect_eq(phys_resolved_x(physics_st), 5.2)
|
||||||
|
expect(phys_resolve(physics_st, 6.3, 0.0, 0.35, 0.0, 1.8, PHYS_M_STATIC))
|
||||||
|
expect(phys_resolved_x(physics_st) > 6.34)
|
||||||
|
let top = phys_step_top(physics_st, -5.3, 0.0, 0.35, 0.0, 0.7, PHYS_M_STATIC)
|
||||||
|
expect(top > 0.4)
|
||||||
|
expect(not phys_resolve(physics_st, -5.3, 0.0, 0.35, top, top + 1.8, PHYS_M_STATIC))
|
||||||
|
phys_close(physics_st)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue