From 861ea330b1b40fabafc8d7887b16f5ab27230d79 Mon Sep 17 00:00:00 2001 From: Nicolas Maman Date: Sun, 4 Oct 2026 15:53:43 -0300 Subject: [PATCH] shape: overlap_shape copies the proxy's points before testing A proxy made by make_shape_proxy for a sphere or capsule points into the sphere module's per-worker scratch, and overlap_sphere / overlap_capsule build their own shape's proxy in that same scratch before GJK reads the given one: the shape was tested against a copy of itself, so a ball far away overlapped (shape.overlap_shape, and through it the world and body overlap queries, for every shape after the first sphere or capsule). The reference's proxies point into the shapes and share nothing. Now overlap_shape copies the given proxy into its own buffer first, as shape_cast_shape already did. test_shape's test_overlap_own_proxy: a proxy made from a ball at the origin against a ball and a capsule 10 m away -- apart (main: touching). Found by an audit of the shape queries against Box3D e77352c, otherwise faithful. --- aephysics/shape/module.ae | 7 ++++++- tests/test_shape.ae | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/aephysics/shape/module.ae b/aephysics/shape/module.ae index 89e0744..8b8ea94 100644 --- a/aephysics/shape/module.ae +++ b/aephysics/shape/module.ae @@ -326,7 +326,12 @@ shape_cast_shape(shape: *Shape, t: Transform, proxy: ShapeProxy, translation: Ve return output } -overlap_shape(shape: *Shape, t: Transform, proxy: ShapeProxy) -> bool { +overlap_shape(shape: *Shape, t: Transform, given: ShapeProxy) -> bool { + // The proxy's points copied first, as shape_cast_shape does: one made by + // make_shape_proxy for a sphere or capsule lives in the scratch the + // sphere's and capsule's overlaps write their own shape into, so GJK + // would have tested the shape against itself. + proxy = mesh.make_local_proxy(given, math.transform_identity(), local_buffer()) kind = shape.kind if kind == SHAPE_CAPSULE { return capsule.overlap_capsule(shape.capsule, t, proxy) } if kind == SHAPE_COMPOUND { return compound.overlap_compound(shape.compound, t, proxy) } diff --git a/tests/test_shape.ae b/tests/test_shape.ae index 587d247..1875d01 100644 --- a/tests/test_shape.ae +++ b/tests/test_shape.ae @@ -558,6 +558,21 @@ test_far_origin() { free(offsets_block) } +// A proxy made from one sphere tested against another far away: the +// sphere's (and capsule's) overlap used to write its own shape into the +// scratch the proxy's points live in, and so found its own copy touching. +test_overlap_own_proxy() { + ball_a = shape.sphere_shape(manifold.sphere(math.vec3_zero(), 0.5), 1.0) + ball_b = shape.sphere_shape(manifold.sphere(math.vec3(10.0, 0.0, 0.0), 0.5), 1.0) + rod = shape.capsule_shape(manifold.capsule(math.vec3(10.0, 0.0, 0.0), math.vec3(10.0, 2.0, 0.0), 0.5), 1.0) + p = shape.make_shape_proxy(&ball_a) + ensure("own proxy: far sphere apart", shape.overlap_shape(&ball_b, math.transform_identity(), p) == false) + p = shape.make_shape_proxy(&ball_a) + ensure("own proxy: far capsule apart", shape.overlap_shape(&rod, math.transform_identity(), p) == false) + near = shape.sphere_shape(manifold.sphere(math.vec3(0.8, 0.0, 0.0), 0.5), 1.0) + p = shape.make_shape_proxy(&ball_a) + ensure("own proxy: near sphere touching", shape.overlap_shape(&near, math.transform_identity(), p)) +} test_dispatch() { origin = math.vec3(0.0 - 4.0, 0.0, 0.0) translation = math.vec3(8.0, 0.0, 0.0) @@ -817,6 +832,7 @@ test_filters() { main() { before = basics.alloc_count() test_mass() + test_overlap_own_proxy() test_aabb() test_ray_cast_sphere() test_ray_cast_capsule()