From dd86dce1719c3cbec07f9731916f219596245b24 Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Sun, 27 Sep 2026 15:22:30 +0300 Subject: [PATCH] feat(physics): 16 - a rock's own shape: phys_convex (Jolt's convex hull of a point cloud) and phys_scaled (one hull, every rock of that shape at its own size); the libraries rebuilt on the Mac and the PC (still KERNEL32 alone); hull_shape_test holds the scale and that a yaw turns a body the way a renderer turns an instance Co-Authored-By: Claude Opus 5.5 --- packages/ludic.physics/README.md | 2 +- .../lib/macos-arm64/libludicjolt.dylib | 4 +- .../lib/windows-x64/ludicjolt.dll | 4 +- .../lib/windows-x64/ludicjolt.lib | 4 +- packages/ludic.physics/native.ludic | 2 + .../ludic.physics/native/shim/jph_shapes.inl | 13 ++++++ .../ludic.physics/native/shim/jph_shim.cpp | 2 + packages/ludic.physics/shapes.ludic | 12 +++++ .../ludic.physics/tests/hull_shape_test.ludic | 44 +++++++++++++++++++ 9 files changed, 80 insertions(+), 7 deletions(-) create mode 100644 packages/ludic.physics/tests/hull_shape_test.ludic diff --git a/packages/ludic.physics/README.md b/packages/ludic.physics/README.md index 167eee0c..c8768c7d 100644 --- a/packages/ludic.physics/README.md +++ b/packages/ludic.physics/README.md @@ -29,7 +29,7 @@ import "ludic.physics" | | | | --- | --- | | `phys_open(st, max_bodies, threads) -> bool`, `phys_close(st)`, `phys_is_open(st)` | a world, and everything in it let go | -| `phys_box`, `phys_sphere`, `phys_capsule`, `phys_cylinder`, `phys_dome(r, h)`, `phys_offset(shape, x, y, z, yaw)` | shapes, each an int (`-1` refused); a dome is a boulder standing on its origin | +| `phys_box`, `phys_sphere`, `phys_capsule`, `phys_cylinder`, `phys_dome(r, h)`, `phys_convex(points, n)`, `phys_scaled(shape, k)`, `phys_offset(shape, x, y, z, yaw)` | shapes, each an int (`-1` refused); a dome is a boulder standing on its origin; a convex hull of n points (x, y, z) is a rock's own shape, made once and scaled for every rock of that shape; a body's yaw turns it as a renderer turns an instance (x' = cos x + sin z, z' = -sin x + cos z) | | `phys_heightfield(st, h, n, ox, oz, cell)`, `phys_mesh(st, v, nv, tri, nt)` | the ground (`PHYS_HOLE` is none) and a dock or cabin | | `phys_ground_add`, `phys_static_add`, `phys_kinematic_add`, `phys_body_add(..., mass)` | bodies: the ground, still things, what the game moves (the player), what falls and floats | | `phys_settle(st)` | after adding many still things, before the first query | diff --git a/packages/ludic.physics/lib/macos-arm64/libludicjolt.dylib b/packages/ludic.physics/lib/macos-arm64/libludicjolt.dylib index 6830f514..36c149dd 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:203b9c83905942e8da10d3e0a1dfe82949360dfa134b5b43c90113fc2e9acae7 -size 1161200 +oid sha256:b87a8d51bcc30d4479d60d28456cc6cc529820f90281feac9b69d420e0ccb623 +size 1161296 diff --git a/packages/ludic.physics/lib/windows-x64/ludicjolt.dll b/packages/ludic.physics/lib/windows-x64/ludicjolt.dll index 89416318..169c634e 100644 --- a/packages/ludic.physics/lib/windows-x64/ludicjolt.dll +++ b/packages/ludic.physics/lib/windows-x64/ludicjolt.dll @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ee8a0b0b909f87b7579917c01690add38638d4eada5c62e9ed1bdd388d72f831 -size 1720320 +oid sha256:0d4d413e11175d6ebcd62fdc25e7ef1ed54687b7850a0b804ac9cdc21615b728 +size 1721856 diff --git a/packages/ludic.physics/lib/windows-x64/ludicjolt.lib b/packages/ludic.physics/lib/windows-x64/ludicjolt.lib index 6ad76060..9bc6b13d 100644 --- a/packages/ludic.physics/lib/windows-x64/ludicjolt.lib +++ b/packages/ludic.physics/lib/windows-x64/ludicjolt.lib @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:32c6013b31d4ce2edee1a6901dafae5754638fb234d6f11e06130c0c9f7c60e4 -size 10050 +oid sha256:4d206c9b857d9832c730b8159fda0f327cec0339f4327ba39e031e4934154342 +size 10466 diff --git a/packages/ludic.physics/native.ludic b/packages/ludic.physics/native.ludic index 1961db78..5f47798d 100644 --- a/packages/ludic.physics/native.ludic +++ b/packages/ludic.physics/native.ludic @@ -14,6 +14,8 @@ extern function jph_shape_capsule(half_h: float, r: float) -> pointer = "jph_sha extern function jph_shape_cylinder(half_h: float, r: float) -> pointer = "jph_shape_cylinder" extern function jph_shape_dome(r: float, h: float) -> pointer = "jph_shape_dome" extern function jph_shape_offset(s: pointer, x: float, y: float, z: float, yaw: float) -> pointer = "jph_shape_offset" +extern function jph_shape_convex(v: pointer, n: int, radius: float) -> pointer = "jph_shape_convex" +extern function jph_shape_scaled(s: pointer, k: float) -> pointer = "jph_shape_scaled" extern function jph_shape_heightfield(h: pointer, n: int, ox: float, oz: float, cell: float) -> pointer = "jph_shape_heightfield" extern function jph_shape_mesh(v: pointer, nv: int, tri: pointer, nt: int) -> pointer = "jph_shape_mesh" extern function jph_shape_free(s: pointer) -> void = "jph_shape_free" diff --git a/packages/ludic.physics/native/shim/jph_shapes.inl b/packages/ludic.physics/native/shim/jph_shapes.inl index f43416e8..e61a0f87 100644 --- a/packages/ludic.physics/native/shim/jph_shapes.inl +++ b/packages/ludic.physics/native/shim/jph_shapes.inl @@ -17,6 +17,19 @@ JPH_SHIM void *jph_shape_dome(float r, float h) { return keep(RotatedTranslatedShapeSettings(Vec3(0.0f, h - big, 0.0f), Quat::sIdentity(), s).Create()); } +// a rock: the convex hull of n points (x, y, z) about its origin, its edges rounded by radius +JPH_SHIM void *jph_shape_convex(const float *v, int n, float radius) { + Array pts; + pts.reserve(n); + for (int i = 0; i < n; ++i) pts.push_back(Vec3(v[3 * i], v[3 * i + 1], v[3 * i + 2])); + return keep(ConvexHullShapeSettings(pts, radius).Create()); +} + +// another shape, k times its size on every axis (one hull, every boulder of that shape) +JPH_SHIM void *jph_shape_scaled(void *s, float k) { + return keep(ScaledShapeSettings(static_cast(s), Vec3::sReplicate(k)).Create()); +} + // a shape moved and turned about y, inside its body JPH_SHIM void *jph_shape_offset(void *s, float x, float y, float z, float yaw) { return keep(RotatedTranslatedShapeSettings(Vec3(x, y, z), yaw_q(yaw), static_cast(s)).Create()); diff --git a/packages/ludic.physics/native/shim/jph_shim.cpp b/packages/ludic.physics/native/shim/jph_shim.cpp index 4346538f..ab3f5da9 100644 --- a/packages/ludic.physics/native/shim/jph_shim.cpp +++ b/packages/ludic.physics/native/shim/jph_shim.cpp @@ -16,6 +16,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/packages/ludic.physics/shapes.ludic b/packages/ludic.physics/shapes.ludic index 3011970e..2e9598b4 100644 --- a/packages/ludic.physics/shapes.ludic +++ b/packages/ludic.physics/shapes.ludic @@ -59,6 +59,18 @@ export function phys_cylinder(physics_st: mut PhysicsState, half_h: float, r: fl # a boulder: a dome r across and h tall, standing on its origin export function phys_dome(physics_st: mut PhysicsState, r: float, h: float) -> int { return ph_keep(physics_st, jph_shape_dome(r, h)) } +# a rock: the convex hull of n points (x, y, z) about its origin, its edges rounded by 2 cm +export function phys_convex(physics_st: mut PhysicsState, v: []float, n: int) -> int { + if n < 4 or len(v) < n * 3 { return -1 } + return ph_keep(physics_st, jph_shape_convex(v, n, 0.02)) +} +# another shape k times its size, so one hull serves every boulder of its shape +export function phys_scaled(physics_st: mut PhysicsState, shape: int, k: float) -> int { + let s = ph_shape(physics_st, shape) + if s == null { return -1 } + return ph_keep(physics_st, jph_shape_scaled(s, k)) +} + # another shape moved and turned about y inside its body (a post whose foot is its origin) export function phys_offset(physics_st: mut PhysicsState, shape: int, x: float, y: float, z: float, yaw: float) -> int { let s = ph_shape(physics_st, shape) diff --git a/packages/ludic.physics/tests/hull_shape_test.ludic b/packages/ludic.physics/tests/hull_shape_test.ludic new file mode 100644 index 00000000..b8717015 --- /dev/null +++ b/packages/ludic.physics/tests/hull_shape_test.ludic @@ -0,0 +1,44 @@ +# hull_shape_test.ludic - a rock's shape: a convex hull of points, scaled for one boulder and +# turned by its yaw the way a renderer turns an instance (x' = cos x + sin z, z' = -sin x + cos z) +import "ludic.physics" +import "ludic.base" +program HullShapeTest { + numbers float + + # a cube two metres across, its centre two metres along x from its origin + function cube(physics_st: mut PhysicsState) -> int { + let v = floats(8 * 3) + for i in 0 .. 8 { + v[i * 3] = 2.0 + float(i % 2) * 2.0 - 1.0 + v[i * 3 + 1] = float((i / 2) % 2) * 2.0 - 1.0 + v[i * 3 + 2] = float(i / 4) * 2.0 - 1.0 + } + return phys_convex(physics_st, v, 8) + } + function down(physics_st: mut PhysicsState, x: float, z: float) -> PhysHit { return phys_ray(physics_st, x, 10.0, z, 0.0, -20.0, 0.0, PHYS_M_ALL) } + + test "a hull stands where its points are, and too few points are no shape" (physics_st: mut PhysicsState) { + expect(phys_open(physics_st, 16, 1)) + phys_static_add(physics_st, cube(physics_st), 0.0, 0.0, 0.0, 0.0) + phys_settle(physics_st) + let h = down(physics_st, 2.0, 0.0) + expect(h.body >= 0) + expect(Math.abs(h.y - 1.0) < 0.01) + expect(down(physics_st, 0.0, 0.0).body < 0) + expect_eq(phys_convex(physics_st, floats(9), 3), -1) + phys_close(physics_st) + } + + test "scaled twice as big, and turned a quarter the renderer's way" (physics_st: mut PhysicsState) { + expect(phys_open(physics_st, 16, 1)) + phys_static_add(physics_st, phys_scaled(physics_st, cube(physics_st), 2.0), 0.0, 0.0, 0.0, 1.5707963) + phys_settle(physics_st) + # (4, 0, 0) turned by +90 degrees about y is (0, 0, -4): x' = cos x + sin z, z' = -sin x + cos z + let h = down(physics_st, 0.0, -4.0) + expect(h.body >= 0) + expect(Math.abs(h.y - 2.0) < 0.01) + expect(down(physics_st, 4.0, 0.0).body < 0) + expect(down(physics_st, 0.0, 4.0).body < 0) + phys_close(physics_st) + } +}