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 <noreply@anthropic.com>
This commit is contained in:
parent
b44b88e00e
commit
c7a18fd4f0
12 changed files with 334 additions and 2 deletions
|
|
@ -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` |
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,4 +9,5 @@ import "shapes.ludic"
|
|||
import "bodies.ludic"
|
||||
import "queries.ludic"
|
||||
import "water.ludic"
|
||||
import "walker.ludic"
|
||||
import "system.ludic"
|
||||
|
|
|
|||
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.
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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<World *>(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<World *>(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<World *>(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<World *>(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<World *>(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));
|
||||
}
|
||||
|
|
|
|||
87
packages/ludic.physics/native/shim/jph_character.inl
Normal file
87
packages/ludic.physics/native/shim/jph_character.inl
Normal file
|
|
@ -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<World *>(w);
|
||||
float half = (h - 2.0f * r) * 0.5f;
|
||||
if (half < 0.01f) half = 0.01f;
|
||||
RefConst<Shape> cap = CapsuleShapeSettings(half, r).Create().Get();
|
||||
RefConst<Shape> 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<Walker *>(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<Walker *>(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<World *>(w);
|
||||
Walker *wk = static_cast<Walker *>(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<Walker *>(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<World *>(w);
|
||||
CharacterVirtual *c = static_cast<Walker *>(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);
|
||||
}
|
||||
|
|
@ -22,6 +22,8 @@
|
|||
#include <Jolt/Physics/Collision/CollideShape.h>
|
||||
#include <Jolt/Physics/Collision/CollisionCollectorImpl.h>
|
||||
#include <Jolt/Physics/Body/BodyCreationSettings.h>
|
||||
#include <Jolt/Physics/Character/CharacterVirtual.h>
|
||||
#include <Jolt/Physics/Body/BodyFilter.h>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <cfloat>
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<PhysFact> = 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
|
||||
|
|
|
|||
104
packages/ludic.physics/tests/walker_test.ludic
Normal file
104
packages/ludic.physics/tests/walker_test.ludic
Normal file
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
74
packages/ludic.physics/walker.ludic
Normal file
74
packages/ludic.physics/walker.ludic
Normal file
|
|
@ -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) }
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue