feat(character): 16.8 - on land a physics walker moves the body (CharacterGround.walk); the package's own gravity, ballistic air and slope-probe refusal are gone
The package keeps the rules and hands them over each step: speed and heading, whether a jump leaves, CHAR_STEP and the boots' slope limit. The walker (Jolt CharacterVirtual) does gravity, landing, steps, slopes and following the ground down. Too steep is ground too steep facing the way the body wanted to go; stepping off an edge is not. ludic.physics: phys_walker_move (follow the body if something else moved it, then step), the slope limit, the jump always honoured. The character's tests run on a real Jolt walker over the same fake world: 15 pass, a knee-high boulder now a step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
d2cd775309
commit
e909ec122c
11 changed files with 169 additions and 89 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.
|
|
@ -33,7 +33,7 @@ extern function jph_push(w: pointer, x: float, z: float, r: float, y0: float, y1
|
|||
extern function jph_step_top(w: pointer, x: float, z: float, r: float, feet: float, reach: float, mask: int) -> float = "jph_step_top"
|
||||
extern function jph_char_new(w: pointer, r: float, h: float, x: float, y: float, z: float, max_slope: float, mass: float) -> pointer = "jph_char_new"
|
||||
extern function jph_char_free(k: pointer) -> void = "jph_char_free"
|
||||
extern function jph_char_limits(k: pointer, step_up: float, stick: float) -> void = "jph_char_limits"
|
||||
extern function jph_char_limits(k: pointer, step_up: float, stick: float, max_slope: float) -> void = "jph_char_limits"
|
||||
extern function jph_char_step(w: pointer, k: pointer, dt: float, vx: float, vz: float, jump: float) -> int = "jph_char_step"
|
||||
extern function jph_char_read(k: pointer, out: pointer) -> void = "jph_char_read"
|
||||
extern function jph_char_place(w: pointer, k: pointer, x: float, y: float, z: float) -> void = "jph_char_place"
|
||||
|
|
|
|||
|
|
@ -34,11 +34,31 @@ JPH_SHIM void jph_char_free(void *k) {
|
|||
delete wk;
|
||||
}
|
||||
|
||||
// how high a step it takes without a jump, and how far it follows the ground down
|
||||
JPH_SHIM void jph_char_limits(void *k, float step_up, float stick) {
|
||||
// how high a step it takes without a jump, how far it follows the ground down, and the steepest
|
||||
// slope in degrees that is still ground
|
||||
JPH_SHIM void jph_char_limits(void *k, float step_up, float stick, float max_slope) {
|
||||
Walker *wk = static_cast<Walker *>(k);
|
||||
wk->step_up = step_up;
|
||||
wk->stick = stick;
|
||||
wk->c->SetMaxSlopeAngle(DegreesToRadians(max_slope));
|
||||
}
|
||||
|
||||
// what the step comes to: TOO STEEP (1) only when GROUND too steep for it faces against the way it
|
||||
// wanted to go (a cliff, a rise the boots cannot take) - a still thing's edge is a step or a wall;
|
||||
// steep ground merely under the feet, as when stepping off an edge, is UNSUPPORTED (2)
|
||||
static int jph_char_says(World *ww, CharacterVirtual *c, float vx, float vz) {
|
||||
int state = int(c->GetGroundState());
|
||||
if (state == int(CharacterBase::EGroundState::InAir)) return state;
|
||||
Vec3 want(vx, 0.0f, vz);
|
||||
for (const CharacterContact &ct : c->GetActiveContacts()) {
|
||||
if (!ct.mHadCollision || ct.mWasDiscarded || ct.mBodyB.IsInvalid()) continue;
|
||||
if (ww->sys.GetBodyInterface().GetObjectLayer(ct.mBodyB) != L_GROUND) continue;
|
||||
Vec3 n = ct.mSurfaceNormal;
|
||||
bool slope = n.GetY() > 0.05f && c->IsSlopeTooSteep(n);
|
||||
if (slope && Vec3(n.GetX(), 0.0f, n.GetZ()).Dot(want) < -1.0e-4f) return int(CharacterBase::EGroundState::OnSteepGround);
|
||||
}
|
||||
if (state == int(CharacterBase::EGroundState::OnSteepGround)) return int(CharacterBase::EGroundState::NotSupported);
|
||||
return state;
|
||||
}
|
||||
|
||||
// one step of dt: (vx, vz) the speed it wants across; jump > 0 leaves the ground at that speed up
|
||||
|
|
@ -50,18 +70,17 @@ JPH_SHIM int jph_char_step(void *w, void *k, float dt, float vx, float vz, float
|
|||
bool ground = c->GetGroundState() == CharacterBase::EGroundState::OnGround;
|
||||
float vy = c->GetLinearVelocity().GetY() + g.GetY() * dt;
|
||||
Vec3 v(vx, vy, vz);
|
||||
if (ground) {
|
||||
v = Vec3(vx, 0.0f, vz) + c->GetGroundVelocity();
|
||||
if (jump > 0.0f) v.SetY(jump);
|
||||
}
|
||||
if (ground) v = Vec3(vx, 0.0f, vz) + c->GetGroundVelocity();
|
||||
// whether a jump may leave is the game's rule; here it only leaves
|
||||
if (jump > 0.0f) v.SetY(jump);
|
||||
c->SetLinearVelocity(v);
|
||||
CharacterVirtual::ExtendedUpdateSettings u;
|
||||
u.mWalkStairsStepUp = Vec3(0.0f, wk->step_up, 0.0f);
|
||||
u.mStickToFloorStepDown = Vec3(0.0f, -wk->stick, 0.0f);
|
||||
u.mStickToFloorStepDown = Vec3(0.0f, jump > 0.0f ? 0.0f : -wk->stick, 0.0f);
|
||||
IgnoreSingleBodyFilter self(c->GetInnerBodyID());
|
||||
c->ExtendedUpdate(dt, g, u, ww->sys.GetDefaultBroadPhaseLayerFilter(L_MOVING), ww->sys.GetDefaultLayerFilter(L_MOVING),
|
||||
self, {}, ww->temp);
|
||||
return int(c->GetGroundState());
|
||||
return jph_char_says(ww, c, vx, vz);
|
||||
}
|
||||
|
||||
// position, velocity, the ground state (0 on ground, 1 too steep, 2 touching but unsupported,
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ program WalkerTest {
|
|||
phys_static_add(physics_st, wall, 3.0, 0.0, 10.0, 0.0)
|
||||
phys_settle(physics_st)
|
||||
let a = phys_walker_add(physics_st, 0.35, 1.8, 0.0, 0.0, 0.0, 50.0, 70.0)
|
||||
phys_walker_limits(physics_st, a, 0.55, 0.5)
|
||||
phys_walker_limits(physics_st, a, 0.55, 0.5, 50.0)
|
||||
walk(physics_st, a, 1.5, 2.0, 0.0)
|
||||
let pa = phys_walker_pose(physics_st, a)
|
||||
expect(pa.x > 2.5)
|
||||
|
|
|
|||
|
|
@ -31,10 +31,11 @@ export function phys_walker_add(physics_st: mut PhysicsState, r: float, h: float
|
|||
return len(physics_st.ph_walkers) - 1
|
||||
}
|
||||
|
||||
# how high a step it takes without a jump, and how far it follows the ground down
|
||||
export function phys_walker_limits(physics_st: PhysicsState, id: int, step_up: float, stick: float) -> void {
|
||||
# how high a step it takes without a jump, how far it follows the ground down, and the steepest
|
||||
# slope in degrees that is still ground
|
||||
export function phys_walker_limits(physics_st: PhysicsState, id: int, step_up: float, stick: float, max_slope: float) -> void {
|
||||
let k = ph_walker(physics_st, id)
|
||||
if k != null { jph_char_limits(k, step_up, stick) }
|
||||
if k != null { jph_char_limits(k, step_up, stick, max_slope) }
|
||||
}
|
||||
|
||||
export function phys_walker_remove(physics_st: mut PhysicsState, id: int) -> void {
|
||||
|
|
@ -72,3 +73,16 @@ export function phys_walker_place(physics_st: mut PhysicsState, id: int, x: floa
|
|||
let k = ph_walker(physics_st, id)
|
||||
if k != null and physics_st.ph_world != null { jph_char_place(physics_st.ph_world, k, x, y, z) }
|
||||
}
|
||||
|
||||
# a body the game keeps elsewhere, moved by its walker: put where the body is if something else
|
||||
# moved it (a teleport, a ride, a swim), then one step under the given limits; the ground after it
|
||||
export function phys_walker_move(physics_st: mut PhysicsState, id: int, x: float, y: float, z: float, vx: float, vz: float, jump: float, step: float, max_slope: float, dt: float) -> int {
|
||||
let p = phys_walker_pose(physics_st, id)
|
||||
if p == null { return PHYS_IN_AIR }
|
||||
let dx = p.x - x
|
||||
let dy = p.y - y
|
||||
let dz = p.z - z
|
||||
if dx * dx + dy * dy + dz * dz > 0.0025 { phys_walker_place(physics_st, id, x, y, z) }
|
||||
phys_walker_limits(physics_st, id, step, 0.5, max_slope)
|
||||
return phys_walker_step(physics_st, id, dt, vx, vz, jump)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue