From c7a18fd4f0336624defd3138d7402331654c7937 Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Sun, 27 Sep 2026 00:24:47 +0300 Subject: [PATCH] feat(physics): a walker (Jolt CharacterVirtual with an inner body) and the verbs a boat and a guest's copy need - force, torque, angular impulse, spin, set_pose Five new tests: stand and walk at the asked speed, a low stone is a step and a tall one a wall, a jump up and back, a crate shoved aside, a hull turned by a torque and carried by a force. Co-Authored-By: Claude Opus 5.5 --- packages/ludic.physics/README.md | 2 + packages/ludic.physics/bodies.ludic | 19 ++++ packages/ludic.physics/index.ludic | 1 + .../lib/macos-arm64/libludicjolt.dylib | 4 +- packages/ludic.physics/native.ludic | 11 ++ .../ludic.physics/native/shim/jph_bodies.inl | 23 ++++ .../native/shim/jph_character.inl | 87 +++++++++++++++ .../ludic.physics/native/shim/jph_shim.cpp | 3 + packages/ludic.physics/shapes.ludic | 6 + packages/ludic.physics/state.ludic | 2 + .../ludic.physics/tests/walker_test.ludic | 104 ++++++++++++++++++ packages/ludic.physics/walker.ludic | 74 +++++++++++++ 12 files changed, 334 insertions(+), 2 deletions(-) create mode 100644 packages/ludic.physics/native/shim/jph_character.inl create mode 100644 packages/ludic.physics/tests/walker_test.ludic create mode 100644 packages/ludic.physics/walker.ludic diff --git a/packages/ludic.physics/README.md b/packages/ludic.physics/README.md index 754fca53..167eee0c 100644 --- a/packages/ludic.physics/README.md +++ b/packages/ludic.physics/README.md @@ -42,6 +42,8 @@ import "ludic.physics" | `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 thing wholly under the feet or over the head is not in the way | | `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_walker_add(st, r, h, x, y, z, max_slope, mass)`, `phys_walker_step(st, id, dt, vx, vz, jump) -> ground`, `phys_walker_pose -> PhysWalk`, `phys_walker_place`, `phys_walker_limits(step_up, stick)`, `phys_walker_remove` | a walking body (Jolt's CharacterVirtual): gravity, landing, steps, slopes, following the ground down; it shoves dynamic things and rays find its `body` | +| `phys_force`, `phys_torque`, `phys_angular_impulse`, `phys_spin_y`, `phys_set_pose(st, id, pose)` | an oar's stroke and a turn; a guest's copy put where the host said | | `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_tick`, `phys_advance(st, dt) -> steps`, `phys_step`, `phys_system()` | the system, in `PH_SIMULATE` | diff --git a/packages/ludic.physics/bodies.ludic b/packages/ludic.physics/bodies.ludic index 6942331e..7a4a278c 100644 --- a/packages/ludic.physics/bodies.ludic +++ b/packages/ludic.physics/bodies.ludic @@ -68,3 +68,22 @@ export function phys_awake(physics_st: PhysicsState, id: int) -> bool { return p export function phys_set_kinematic_target(physics_st: mut PhysicsState, id: int, x: float, y: float, z: float, yaw: float) -> void { if physics_st.ph_world != null { jph_body_target(physics_st.ph_world, id, x, y, z, yaw, physics_st.ph_step) } } + +# a push kept up over the next step (an oar), a twist, and a twist at once +export function phys_force(physics_st: mut PhysicsState, id: int, x: float, y: float, z: float) -> void { if physics_st.ph_world != null { jph_body_force(physics_st.ph_world, id, x, y, z) } } +export function phys_torque(physics_st: mut PhysicsState, id: int, x: float, y: float, z: float) -> void { if physics_st.ph_world != null { jph_body_torque(physics_st.ph_world, id, x, y, z) } } +export function phys_angular_impulse(physics_st: mut PhysicsState, id: int, x: float, y: float, z: float) -> void { if physics_st.ph_world != null { jph_body_angular_impulse(physics_st.ph_world, id, x, y, z) } } + +# how fast it turns about y, radians a second +export function phys_spin_y(physics_st: mut PhysicsState, id: int) -> float { + if physics_st.ph_world == null { return 0.0 } + ph_buffers(physics_st) + jph_body_spin(physics_st.ph_world, id, physics_st.ph_vals) + return physics_st.ph_vals[1] +} + +# a body put exactly where a pose says, moving as it says: a guest's copy of the host's body +export function phys_set_pose(physics_st: mut PhysicsState, id: int, p: PhysPose) -> void { + if physics_st.ph_world == null or p == null { return } + jph_body_set_pose(physics_st.ph_world, id, p.x, p.y, p.z, p.qx, p.qy, p.qz, p.qw, p.vx, p.vy, p.vz) +} diff --git a/packages/ludic.physics/index.ludic b/packages/ludic.physics/index.ludic index 8f5aada1..72c200c5 100644 --- a/packages/ludic.physics/index.ludic +++ b/packages/ludic.physics/index.ludic @@ -9,4 +9,5 @@ import "shapes.ludic" import "bodies.ludic" import "queries.ludic" import "water.ludic" +import "walker.ludic" import "system.ludic" diff --git a/packages/ludic.physics/lib/macos-arm64/libludicjolt.dylib b/packages/ludic.physics/lib/macos-arm64/libludicjolt.dylib index 3a12b6ae..498a3a02 100755 --- a/packages/ludic.physics/lib/macos-arm64/libludicjolt.dylib +++ b/packages/ludic.physics/lib/macos-arm64/libludicjolt.dylib @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fadb571f9e934ff8a9ba3407b6396d55cf1389f14e78af0a25dbc14f464c3289 -size 1099936 +oid sha256:9a5cfb124c82f5d7073eac72b11d8f882fcac10865be65dda91af0ec491a1164 +size 1160640 diff --git a/packages/ludic.physics/native.ludic b/packages/ludic.physics/native.ludic index abef00ef..5e6da921 100644 --- a/packages/ludic.physics/native.ludic +++ b/packages/ludic.physics/native.ludic @@ -31,3 +31,14 @@ extern function jph_top_at(w: pointer, x: float, z: float, r: float, from_y: flo 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" +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_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" +extern function jph_body_force(w: pointer, id: int, x: float, y: float, z: float) -> void = "jph_body_force" +extern function jph_body_torque(w: pointer, id: int, x: float, y: float, z: float) -> void = "jph_body_torque" +extern function jph_body_angular_impulse(w: pointer, id: int, x: float, y: float, z: float) -> void = "jph_body_angular_impulse" +extern function jph_body_spin(w: pointer, id: int, out: pointer) -> void = "jph_body_spin" +extern function jph_body_set_pose(w: pointer, id: int, x: float, y: float, z: float, qx: float, qy: float, qz: float, qw: float, vx: float, vy: float, vz: float) -> void = "jph_body_set_pose" diff --git a/packages/ludic.physics/native/shim/jph_bodies.inl b/packages/ludic.physics/native/shim/jph_bodies.inl index 2ed464b9..0409dc54 100644 --- a/packages/ludic.physics/native/shim/jph_bodies.inl +++ b/packages/ludic.physics/native/shim/jph_bodies.inl @@ -62,3 +62,26 @@ JPH_SHIM int jph_body_buoyancy(void *w, int id, float sy, float nx, float ny, fl Vec3(cx, 0.0f, cz), ww->sys.GetGravity(), dt); return in ? 1 : 0; } + +// a push kept up over the next step (an oar's stroke, a paddle), and a twist +JPH_SHIM void jph_body_force(void *w, int id, float x, float y, float z) { + static_cast(w)->sys.GetBodyInterface().AddForce(bid(id), Vec3(x, y, z)); +} +JPH_SHIM void jph_body_torque(void *w, int id, float x, float y, float z) { + static_cast(w)->sys.GetBodyInterface().AddTorque(bid(id), Vec3(x, y, z)); +} +JPH_SHIM void jph_body_angular_impulse(void *w, int id, float x, float y, float z) { + static_cast(w)->sys.GetBodyInterface().AddAngularImpulse(bid(id), Vec3(x, y, z)); +} +// how fast it turns (x, y, z), into out[3] +JPH_SHIM void jph_body_spin(void *w, int id, float *out) { + Vec3 a = static_cast(w)->sys.GetBodyInterface().GetAngularVelocity(bid(id)); + out[0] = a.GetX(); out[1] = a.GetY(); out[2] = a.GetZ(); +} +// where a body is going, set whole: a guest's copy of the host's body, moved to what the host said +JPH_SHIM void jph_body_set_pose(void *w, int id, float x, float y, float z, float qx, float qy, float qz, float qw, + float vx, float vy, float vz) { + BodyInterface &bi = static_cast(w)->sys.GetBodyInterface(); + bi.SetPositionAndRotation(bid(id), RVec3(x, y, z), Quat(qx, qy, qz, qw).Normalized(), EActivation::Activate); + bi.SetLinearVelocity(bid(id), Vec3(vx, vy, vz)); +} diff --git a/packages/ludic.physics/native/shim/jph_character.inl b/packages/ludic.physics/native/shim/jph_character.inl new file mode 100644 index 00000000..1655b979 --- /dev/null +++ b/packages/ludic.physics/native/shim/jph_character.inl @@ -0,0 +1,87 @@ +// jph_character.inl - a walking body: Jolt's CharacterVirtual, stepped by the game, with an inner +// rigid body on the moving layer so dynamic things are shoved and a ray can find it. Its feet are +// its origin. The game says how fast it wants to go across and whether it jumps; Jolt does the +// rest - gravity, landing, stairs, slopes, sticking to the ground going down. + +struct Walker { + CharacterVirtual *c; + float step_up; + float stick; +}; + +// r wide, h tall, feet at (x, y, z); a slope steeper than max_slope degrees is not ground +JPH_SHIM void *jph_char_new(void *w, float r, float h, float x, float y, float z, float max_slope, float mass) { + World *ww = static_cast(w); + float half = (h - 2.0f * r) * 0.5f; + if (half < 0.01f) half = 0.01f; + RefConst cap = CapsuleShapeSettings(half, r).Create().Get(); + RefConst sh = RotatedTranslatedShapeSettings(Vec3(0.0f, half + r, 0.0f), Quat::sIdentity(), cap).Create().Get(); + CharacterVirtualSettings st; + st.mShape = sh; + st.mInnerBodyShape = sh; + st.mInnerBodyLayer = L_MOVING; + st.mMaxSlopeAngle = DegreesToRadians(max_slope); + st.mMass = mass; + st.mMaxStrength = mass * 9.81f * 2.0f; + st.mSupportingVolume = Plane(Vec3::sAxisY(), -r); + Walker *wk = new Walker{ new CharacterVirtual(&st, RVec3(x, y, z), Quat::sIdentity(), 0, &ww->sys), 0.55f, 0.5f }; + return wk; +} + +JPH_SHIM void jph_char_free(void *k) { + Walker *wk = static_cast(k); + delete wk->c; + 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) { + Walker *wk = static_cast(k); + wk->step_up = step_up; + wk->stick = stick; +} + +// one step of dt: (vx, vz) the speed it wants across; jump > 0 leaves the ground at that speed up +JPH_SHIM int jph_char_step(void *w, void *k, float dt, float vx, float vz, float jump) { + World *ww = static_cast(w); + Walker *wk = static_cast(k); + CharacterVirtual *c = wk->c; + Vec3 g = ww->sys.GetGravity(); + 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); + } + 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); + 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()); +} + +// position, velocity, the ground state (0 on ground, 1 too steep, 2 touching but unsupported, +// 3 in the air), the ground normal's y, and the inner body's id - into out[9] +JPH_SHIM void jph_char_read(void *k, float *out) { + CharacterVirtual *c = static_cast(k)->c; + RVec3 p = c->GetPosition(); + Vec3 v = c->GetLinearVelocity(); + float vals[9] = { float(p.GetX()), float(p.GetY()), float(p.GetZ()), v.GetX(), v.GetY(), v.GetZ(), + float(int(c->GetGroundState())), c->GetGroundNormal().GetY(), + float(int(c->GetInnerBodyID().GetIndexAndSequenceNumber())) }; + for (int i = 0; i < 9; i++) out[i] = vals[i]; +} + +// put it somewhere, standing still (a teleport, a map swap, getting off a boat) +JPH_SHIM void jph_char_place(void *w, void *k, float x, float y, float z) { + World *ww = static_cast(w); + CharacterVirtual *c = static_cast(k)->c; + c->SetPosition(RVec3(x, y, z)); + c->SetLinearVelocity(Vec3::sZero()); + IgnoreSingleBodyFilter self(c->GetInnerBodyID()); + c->RefreshContacts(ww->sys.GetDefaultBroadPhaseLayerFilter(L_MOVING), ww->sys.GetDefaultLayerFilter(L_MOVING), self, {}, ww->temp); +} diff --git a/packages/ludic.physics/native/shim/jph_shim.cpp b/packages/ludic.physics/native/shim/jph_shim.cpp index 31897e90..0bf01460 100644 --- a/packages/ludic.physics/native/shim/jph_shim.cpp +++ b/packages/ludic.physics/native/shim/jph_shim.cpp @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include #include #include @@ -126,3 +128,4 @@ JPH_SHIM int jph_init(void) { #include "jph_shapes.inl" #include "jph_bodies.inl" #include "jph_queries.inl" +#include "jph_character.inl" diff --git a/packages/ludic.physics/shapes.ludic b/packages/ludic.physics/shapes.ludic index 3d4cf6b4..fc03dda8 100644 --- a/packages/ludic.physics/shapes.ludic +++ b/packages/ludic.physics/shapes.ludic @@ -15,6 +15,12 @@ export function phys_is_open(physics_st: PhysicsState) -> bool { return physics_ # everything let go: the world, every body in it and every shape (a world swap, a quit) export function phys_close(physics_st: mut PhysicsState) -> void { + if physics_st.ph_walkers != null { + for i in 0 .. len(physics_st.ph_walkers) { + if physics_st.ph_walkers[i] != null { jph_char_free(physics_st.ph_walkers[i]) } + } + } + physics_st.ph_walkers = new []pointer if physics_st.ph_world != null { jph_world_free(physics_st.ph_world) } physics_st.ph_world = null if physics_st.ph_shapes != null { diff --git a/packages/ludic.physics/state.ludic b/packages/ludic.physics/state.ludic index 6d291dd1..9ec38552 100644 --- a/packages/ludic.physics/state.ludic +++ b/packages/ludic.physics/state.ludic @@ -47,6 +47,7 @@ export state PhysicsState { 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_walkers: []pointer = null # a walker id is its place here; null once removed ph_ids: []int = null ph_vals: []float = null ph_facts: Queue = null @@ -64,6 +65,7 @@ function ph_buffers(physics_st: mut PhysicsState) -> void { if physics_st.ph_shapes == null { physics_st.ph_shapes = new []pointer } if physics_st.ph_floats == null { physics_st.ph_floats = new []int } if physics_st.ph_float_k == null { physics_st.ph_float_k = new []float } + if physics_st.ph_walkers == null { physics_st.ph_walkers = new []pointer } } # a step's length, its collision steps, and how hard a contact must be to be a fact diff --git a/packages/ludic.physics/tests/walker_test.ludic b/packages/ludic.physics/tests/walker_test.ludic new file mode 100644 index 00000000..77cd5e27 --- /dev/null +++ b/packages/ludic.physics/tests/walker_test.ludic @@ -0,0 +1,104 @@ +# walker_test.ludic - a walker on the real Jolt: it stands, walks, steps onto a low stone, is held +# by a tall one, jumps and lands, shoves a crate aside; and a boat-shaped body turns under a torque +import "ludic.physics" +import "ludic.base" +program WalkerTest { + numbers float + + function flat(physics_st: mut PhysicsState) -> void { + expect(phys_open(physics_st, 1024, 1)) + let h = floats(64 * 64) + for i in 0 .. 64 * 64 { h[i] = 0.0 } + phys_ground_add(physics_st, phys_heightfield(physics_st, h, 64, -32.0, -32.0, 1.0)) + } + + function walk(physics_st: mut PhysicsState, id: int, seconds: float, vx: float, vz: float) -> void { + for i in 0 .. int(seconds * 60.0) { phys_walker_step(physics_st, id, 1.0 / 60.0, vx, vz, 0.0) } + } + + test "a walker falls to the ground, stands, and walks at the speed it asks" (physics_st: mut PhysicsState) { + flat(physics_st) + let me = phys_walker_add(physics_st, 0.35, 1.8, 0.0, 1.0, 0.0, 50.0, 70.0) + walk(physics_st, me, 1.0, 0.0, 0.0) + let p = phys_walker_pose(physics_st, me) + expect_eq(p.ground, PHYS_ON_GROUND) + expect(Math.abs(p.y) < 0.05) + walk(physics_st, me, 2.0, 3.0, 0.0) + let q = phys_walker_pose(physics_st, me) + expect(Math.abs(q.x - 6.0) < 0.2) + expect(q.body >= 0) + phys_close(physics_st) + } + + test "a low stone is a step, a tall one is a wall" (physics_st: mut PhysicsState) { + flat(physics_st) + let stone = phys_offset(physics_st, phys_box(physics_st, 1.0, 0.15, 3.0), 0.0, 0.15, 0.0, 0.0) + phys_static_add(physics_st, stone, 3.0, 0.0, 0.0, 0.0) + let wall = phys_offset(physics_st, phys_box(physics_st, 1.0, 1.5, 3.0), 0.0, 1.5, 0.0, 0.0) + 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) + walk(physics_st, a, 1.5, 2.0, 0.0) + let pa = phys_walker_pose(physics_st, a) + expect(pa.x > 2.5) + expect(Math.abs(pa.y - 0.3) < 0.05) + let b = phys_walker_add(physics_st, 0.35, 1.8, 0.0, 0.0, 10.0, 50.0, 70.0) + walk(physics_st, b, 2.0, 2.0, 0.0) + let pb = phys_walker_pose(physics_st, b) + expect(pb.x < 1.7) + expect(Math.abs(pb.y) < 0.05) + phys_close(physics_st) + } + + test "a jump leaves the ground and comes back down" (physics_st: mut PhysicsState) { + flat(physics_st) + let me = phys_walker_add(physics_st, 0.35, 1.8, 0.0, 0.0, 0.0, 50.0, 70.0) + walk(physics_st, me, 0.5, 0.0, 0.0) + phys_walker_step(physics_st, me, 1.0 / 60.0, 0.0, 0.0, 5.0) + walk(physics_st, me, 0.25, 0.0, 0.0) + let up = phys_walker_pose(physics_st, me) + expect_eq(up.ground, PHYS_IN_AIR) + expect(up.y > 0.5) + walk(physics_st, me, 1.5, 0.0, 0.0) + let down = phys_walker_pose(physics_st, me) + expect_eq(down.ground, PHYS_ON_GROUND) + expect(Math.abs(down.y) < 0.05) + phys_close(physics_st) + } + + test "walking into a crate shoves it, and a placed walker stands where it was put" (physics_st: mut PhysicsState) { + flat(physics_st) + let crate = phys_body_add(physics_st, phys_box(physics_st, 0.25, 0.25, 0.25), 2.0, 0.25, 0.0, 0.0, 4.0) + let me = phys_walker_add(physics_st, 0.35, 1.8, 0.0, 0.0, 0.0, 50.0, 70.0) + for i in 0 .. 150 { + phys_walker_step(physics_st, me, 1.0 / 60.0, 2.0, 0.0, 0.0) + phys_step(physics_st) + } + expect(phys_pose(physics_st, crate).x > 2.5) + phys_walker_place(physics_st, me, -10.0, 0.0, -10.0) + let p = phys_walker_pose(physics_st, me) + expect(Math.abs(p.x + 10.0) < 0.01) + phys_walker_remove(physics_st, me) + expect(phys_walker_pose(physics_st, me) == null) + phys_close(physics_st) + } + + test "a torque turns a body and a force carries it" (physics_st: mut PhysicsState) { + expect(phys_open(physics_st, 64, 1)) + phys_gravity(physics_st, 0.0) + let hull = phys_body_add(physics_st, phys_box(physics_st, 0.6, 0.2, 1.8), 0.0, 0.0, 0.0, 0.0, 120.0) + for i in 0 .. 60 { + phys_torque(physics_st, hull, 0.0, 200.0, 0.0) + phys_force(physics_st, hull, 0.0, 0.0, 240.0) + phys_step(physics_st) + } + expect(phys_spin_y(physics_st, hull) > 0.1) + expect(phys_pose(physics_st, hull).z > 0.5) + let p = phys_pose(physics_st, hull) + p.x = 5.0 + phys_set_pose(physics_st, hull, p) + expect(Math.abs(phys_pose(physics_st, hull).x - 5.0) < 0.01) + phys_close(physics_st) + } +} diff --git a/packages/ludic.physics/walker.ludic b/packages/ludic.physics/walker.ludic new file mode 100644 index 00000000..58706e93 --- /dev/null +++ b/packages/ludic.physics/walker.ludic @@ -0,0 +1,74 @@ +# walker.ludic - a body that walks: Jolt's CharacterVirtual, stepped by the game with the speed it +# wants across. Gravity, landing, steps up to ph step_up, slopes to max_slope and following the +# ground down are Jolt's; a walker shoves dynamic things and a ray finds it (its `body`). +export const PHYS_ON_GROUND: int = 0 +export const PHYS_TOO_STEEP: int = 1 +export const PHYS_UNSUPPORTED: int = 2 +export const PHYS_IN_AIR: int = 3 + +export property PhysWalk { + x: float = 0.0 # the feet + y: float = 0.0 + z: float = 0.0 + vx: float = 0.0 + vy: float = 0.0 + vz: float = 0.0 + ground: int = 3 # PHYS_ON_GROUND .. PHYS_IN_AIR + ny: float = 1.0 # the ground normal's y: 1 flat + body: int = -1 # its inner body, what rays and overlaps see +} + +function ph_walker(physics_st: PhysicsState, id: int) -> pointer { + if physics_st.ph_walkers == null or id < 0 or id >= len(physics_st.ph_walkers) { return null } + return physics_st.ph_walkers[id] +} + +# r wide and h tall, feet at (x, y, z), mass in kg; -1 with no world +export function phys_walker_add(physics_st: mut PhysicsState, r: float, h: float, x: float, y: float, z: float, max_slope: float, mass: float) -> int { + if physics_st.ph_world == null { return -1 } + ph_buffers(physics_st) + push(physics_st.ph_walkers, jph_char_new(physics_st.ph_world, r, h, x, y, z, max_slope, mass)) + 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 { + let k = ph_walker(physics_st, id) + if k != null { jph_char_limits(k, step_up, stick) } +} + +export function phys_walker_remove(physics_st: mut PhysicsState, id: int) -> void { + let k = ph_walker(physics_st, id) + if k == null { return } + jph_char_free(k) + physics_st.ph_walkers[id] = null +} + +# one step of dt at (vx, vz) metres a second across; jump > 0 leaves the ground at that speed up. +# Returns the ground state after it. +export function phys_walker_step(physics_st: mut PhysicsState, id: int, dt: float, vx: float, vz: float, jump: float) -> int { + let k = ph_walker(physics_st, id) + if k == null or physics_st.ph_world == null { return PHYS_IN_AIR } + return jph_char_step(physics_st.ph_world, k, dt, vx, vz, jump) +} + +export function phys_walker_pose(physics_st: mut PhysicsState, id: int) -> PhysWalk { + let k = ph_walker(physics_st, id) + if k == null { return null } + ph_buffers(physics_st) + jph_char_read(k, physics_st.ph_vals) + let v = physics_st.ph_vals + let p = new PhysWalk + p.x = v[0]; p.y = v[1]; p.z = v[2] + p.vx = v[3]; p.vy = v[4]; p.vz = v[5] + p.ground = int(v[6]) + p.ny = v[7] + p.body = int(v[8]) + return p +} + +# put it somewhere, standing still: a teleport, a swap, getting off a boat +export function phys_walker_place(physics_st: mut PhysicsState, id: int, x: float, y: float, z: float) -> void { + 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) } +}