diff --git a/.gitattributes b/.gitattributes index 25300b4ef..d808581de 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,6 +5,10 @@ Makefile text eol=lf *.sh text eol=lf vendor/box2d/lib/box2d_windows_amd64_avx2.lib filter=lfs diff=lfs merge=lfs -text vendor/box2d/lib/box2d_windows_amd64_sse2.lib filter=lfs diff=lfs merge=lfs -text +vendor/box3d/lib/linux-amd64/libbox3d.a filter=lfs diff=lfs merge=lfs -text +vendor/box3d/lib/linux-arm64/libbox3d.a filter=lfs diff=lfs merge=lfs -text +vendor/box3d/lib/darwin/libbox3d.a filter=lfs diff=lfs merge=lfs -text +vendor/box3d/lib/box3d.lib filter=lfs diff=lfs merge=lfs -text vendor/miniaudio/lib/miniaudio.lib filter=lfs diff=lfs merge=lfs -text vendor/sdl3/SDL3.dll filter=lfs diff=lfs merge=lfs -text vendor/sdl3/SDL3.lib filter=lfs diff=lfs merge=lfs -text @@ -31,5 +35,3 @@ vendor/raylib/windows/raylibdll.lib filter=lfs diff=lfs merge=lfs -text vendor/raylib/windows/raygui.dll filter=lfs diff=lfs merge=lfs -text vendor/raylib/windows/raygui.lib filter=lfs diff=lfs merge=lfs -text vendor/raylib/windows/rayguidll.lib filter=lfs diff=lfs merge=lfs -text -vendor/box3d/lib/linux-amd64/libbox3d.a filter=lfs diff=lfs merge=lfs -text -vendor/box3d/lib/darwin/libbox3d.a filter=lfs diff=lfs merge=lfs -text diff --git a/vendor/box3d/box3d.odin b/vendor/box3d/box3d.odin index 75639c63e..25be6ebad 100644 --- a/vendor/box3d/box3d.odin +++ b/vendor/box3d/box3d.odin @@ -833,6 +833,14 @@ foreign lib { // Is this body a bullet? Body_IsBullet :: proc(bodyId: BodyId) -> bool --- + // Allow this body to rotate fast. Useful for axially symmetric bodies, such as vehicle wheels. + // Normally rotation speed is clamped to improve CCD. However, this clamping is unnecessary for + // bodies that only rotate fast around an axis of symmetry. + Body_AllowFastRotation :: proc(bodyId: BodyId, flag: bool) --- + + // Is this body allowed to rotate fast? + Body_IsFastRotationAllowed :: proc(bodyId: BodyId) -> bool --- + // Enable or disable contact recycling for this body. Contact recycling is a performance optimization // that reuses contact manifolds when bodies move slightly. Disabling it can avoid ghost collisions // on characters at the cost of higher per-step work. Existing contacts retain their prior setting; @@ -895,6 +903,10 @@ foreign lib { * @defgroup shape Shape * Functions to create, destroy, and access. * Shapes bind raw geometry to bodies and hold material properties including friction and restitution. + * You may add multiple shapes to a single body. There are no hard limits on shape count per body. + * + * When you create a shape on a body the center of mass moves. This can lead to the body linear velocity + * changing if the angular velocity is non-zero. * @{ */ @@ -933,8 +945,10 @@ foreign lib { // @return the shape id for accessing the shape CreateHeightFieldShape :: proc(bodyId: BodyId, #by_ptr def: ShapeDef, heightField: ^HeightFieldData) -> ShapeId --- - // Compound shapes are only allowed on static bodies. - CreateCompoundShape :: proc(bodyId: BodyId, #by_ptr def: ShapeDef, compound: ^CompoundData) -> ShapeId --- + // Baked compound shapes are only allowed on static bodies. + // Note: runtime compounds are achieved by adding multiple shapes to a body. + // Runtime compounds can be dynamic and/or kinematic. + CreateBakedCompoundShape :: proc(bodyId: BodyId, #by_ptr def: ShapeDef, compound: ^CompoundData) -> ShapeId --- // Destroy a shape. You may defer the body mass update which can improve performance if several shapes on a // body are destroyed at once. diff --git a/vendor/box3d/box3d_collision.odin b/vendor/box3d/box3d_collision.odin index e80584f7a..33c3961c9 100644 --- a/vendor/box3d/box3d_collision.odin +++ b/vendor/box3d/box3d_collision.odin @@ -265,14 +265,12 @@ foreign lib { // Destroy a compound shape. DestroyCompound :: proc(compound: ^CompoundData) --- - // If bytes is null then this returns the number of required bytes. This clones all the - // data into the bytes buffer. This is expected to run offline or asynchronously. - // This mutates the compound to nullify pointers, leaving the compound in an unusable state. + // Cast the provided compound data to bytes, setting the internal pointers to null. + // Use this before serializing the compound bytes. ConvertCompoundToBytes :: proc(compound: ^CompoundData) -> [^]u8 --- - // Convert bytes to compound. This does not clone. The bytes must remain in scope while the - // compound is used. This is done to improve run-time performance and allow for instancing. - // The bytes are mutated to fixup pointers. + // Cast the provided bytes to compound data, setting up internal pointers. + // Use this after de-serializing the compound bytes. ConvertBytesToCompound :: proc(bytes: [^]u8, byteCount: c.int) -> ^CompoundData --- /**@}*/ // compound @@ -440,15 +438,16 @@ foreign lib { // Collide two hulls. CollideHulls :: proc(manifold: ^LocalManifold, capacity: c.int, #by_ptr hullA: HullData, #by_ptr hullB: HullData, transformBtoA: Transform, cache: ^SATCache) --- - // Collide a capsule and a triangle. - CollideCapsuleAndTriangle :: proc(manifold: ^LocalManifold, capacity: c.int, #by_ptr capsuleA: Capsule, #by_ptr triangleB: [3]Vec3, cache: ^SimplexCache) --- + // Collide a triangle and capsule. Normal points from triangle to capsule. + CollideTriangleAndCapsule :: proc(manifold: ^LocalManifold, capacity: c.int, #by_ptr triangleA: [3]Vec3, #by_ptr capsuleB: Capsule, cache: ^SimplexCache) --- - // Collide a hull and a triangle. - CollideHullAndTriangle :: proc(manifold: ^LocalManifold, capacity: c.int, #by_ptr hullA: HullData, v1, v2, v3: Vec3, - triangleFlags: c.int, cache: ^SATCache, enableSpeculative: bool) --- + // Collide a triangle and hull. Normal points from triangle to hull. + CollideTriangleAndHull :: proc(manifold: ^LocalManifold, capacity: c.int, v1, v2, v3: Vec3, triangleFlags: c.int, + #by_ptr hullB: HullData, cache: ^SATCache, enableSpeculative: bool) --- + + // Collide a triangle and sphere. Normal points from triangle to sphere. + CollideTriangleAndSphere :: proc(manifold: ^LocalManifold, capacity: c.int, #by_ptr triangleA: [3]Vec3, #by_ptr sphereB: Sphere) --- - // Collide a sphere and a triangle. - CollideSphereAndTriangle :: proc(manifold: ^LocalManifold, capacity: c.int, #by_ptr sphereA: Sphere, #by_ptr triangleB: [3]Vec3) --- /**@}*/ // collision @@ -513,16 +512,6 @@ GetHullEdges :: proc "c" (hull: ^HullData) -> Maybe(^HullHalfEdge) { return (^HullHalfEdge)(uintptr(hull) + uintptr(hull.edgeOffset)) } -// Get read only hull faces. -@(require_results) -GetHullFaces :: proc "c" (hull: ^HullData) -> Maybe(^HullFace) { - if hull.faceOffset == 0 { - return nil - } - - return (^HullFace)(uintptr(hull) + uintptr(hull.faceOffset)) -} - // Get read only hull planes. @(require_results) GetHullPlanes :: proc "c" (hull: ^HullData) -> Maybe(^Plane) { @@ -533,6 +522,38 @@ GetHullPlanes :: proc "c" (hull: ^HullData) -> Maybe(^Plane) { return (^Plane)(uintptr(hull) + uintptr(hull.planeOffset)) } +// Get read only hull faces. +@(require_results) +GetHullFaces :: proc "c" (hull: ^HullData) -> Maybe(^HullFace) { + if hull.faceOffset == 0 { + return nil + } + + return (^HullFace)(uintptr(hull) + uintptr(hull.faceOffset)) +} + +// Get read only SOA vertices. This is an array of vertices with all x values, +// y values, and z values as separate arrays. The array lengths are padded to +// a multiple of 4. The padded values are repeats of the first value. +@(require_results) +GetHullSoaVertices :: proc "c" (hull: ^HullData) -> Maybe([^]f32) { + if hull.soaVertexOffset == 0 { + return nil + } + + return ([^]f32)(uintptr(hull) + uintptr(hull.soaVertexOffset)) +} + +// Get read only SOA unit normal vectors. This is an array of normals with all x values, +// y values, and z values as separate arrays. The array lengths are padded to +// a multiple of 4. The padded values are repeats of the first value. +@(require_results) +GetHullSoaNormals :: proc "c" (hull: ^HullData) -> Maybe([^]f32) { + if hull.soaNormalOffset == 0 { + return nil + } + return ([^]f32)(uintptr(hull) + uintptr(hull.soaNormalOffset)) +} // Get read only mesh BVH nodes. @(require_results) diff --git a/vendor/box3d/box3d_constants.odin b/vendor/box3d/box3d_constants.odin index ccb93678a..37edbeaaa 100644 --- a/vendor/box3d/box3d_constants.odin +++ b/vendor/box3d/box3d_constants.odin @@ -62,6 +62,10 @@ MIN_CAPSULE_LENGTH :: #force_inline proc "c" () -> f32 { return LINEAR_SLOP() } +// Minimum contact point friction weight, lower bound for speculative points. Made small +// enough to be washed away by weights that hit 1. +MIN_FRICTION_WEIGHT :: 1e-10 + // The distance between shapes where they are considered overlapped. This is needed // because GJK may return small positive values for overlapped shapes in degenerate // configurations. @@ -121,11 +125,30 @@ TIME_TO_SLEEP :: 0.5 // The maximum number of contact points between two touching shapes. MAX_MANIFOLD_POINTS :: 4 +// The number of iterations for gyroscopic torques. +GYROSCOPIC_ITERATIONS :: 1 + +// The maximum number of convex hull vertices. This is fixed for performance reasons. +MAX_HULL_VERTICES :: 128 + +// The maximum number of convex hull faces. +MAX_HULL_FACES :: 128 + +// The maximum number of convex hull edges. Full edges, not half-edges. +MAX_HULL_EDGES :: 128 + +// Relative tolerance used to determine if two edges are parallel. +PARALLEL_EDGE_TOL :: 0.005 + // The maximum number points to use for shape cast proxies (swept point cloud). -MAX_SHAPE_CAST_POINTS :: 64 +MAX_SHAPE_CAST_POINTS :: MAX_HULL_VERTICES // These generous limits allow for easy hashing. See b3ShapePairKey. SHAPE_POWER :: 22 CHILD_POWER :: 64 - 2 * SHAPE_POWER MAX_SHAPES :: 1 << SHAPE_POWER -MAX_CHILD_SHAPES :: 1 << CHILD_POWER \ No newline at end of file +MAX_CHILD_SHAPES :: 1 << CHILD_POWER + +// Increase this if your application needs more accurate restitution. Doing so will +// slow down the simulation. Must be 1 or more. +RESTITUTION_ITERATIONS :: 1 \ No newline at end of file diff --git a/vendor/box3d/box3d_types.odin b/vendor/box3d/box3d_types.odin index 42b72615f..84b8cb53f 100644 --- a/vendor/box3d/box3d_types.odin +++ b/vendor/box3d/box3d_types.odin @@ -479,6 +479,9 @@ SurfaceMaterial :: struct { // carry a b3DebugMaterial preset, see b3MakeDebugColor. // @see b3HexColor customColor: u32, + + // Explicit padding. Must be zero. + padding: u32, } @@ -542,6 +545,7 @@ ShapeDef :: struct { isSensor: bool, // Enable sensor events for this shape. This applies to sensors and non-sensors. False by default, even for sensors. + // Only convex shapes may act as sensor visitors. enableSensorEvents: bool, // Enable contact events for this shape. Only applies to kinematic and dynamic bodies. Ignored for sensors. False by default. @@ -1871,8 +1875,7 @@ Capsule :: struct { * @{ */ -// A hull vertex. Identified by a half-edge with this -// vertex as its tail. +// A hull vertex. Identified by a half-edge with this vertex as its tail. HullVertex :: struct { // A half-edge that has this vertex as the origin // Can be used along with edge twins and winding order @@ -1903,7 +1906,7 @@ HullFace :: struct { } // 64-bit hull version. Useful for validating serialized data. -HULL_VERSION :: 0x9D4716CE3793900E +HULL_VERSION :: 0xDA5150191B994C01 // A convex hull. // @note This data structure has data hanging off the end and cannot be directly copied. @@ -1953,11 +1956,17 @@ HullData :: struct { // The face count. Hulls faces are convex polygons. faceCount: c.int, + // Offset of the face plane array in bytes from the struct address. + planeOffset: c.int, + // Offset of the face array in bytes from the struct address. faceOffset: c.int, - // Offset of the face plane array in bytes from the struct address. - planeOffset: c.int, + // Offset of structure of array (SOA) vertices + soaVertexOffset: c.int, + + // Offset of structure of array (SOA) unit normal vectors + soaNormalOffset: c.int, // Explicit padding. Hull identity is a content hash and memcmp over raw bytes, // so there must be no unnamed padding for struct copies to scramble. @@ -1971,9 +1980,15 @@ BoxHull :: struct { boxVertices: [8]HullVertex, //< Box vertices. boxPoints: [8]Vec3, //< Box points. boxEdges: [24]HullHalfEdge, //< Box half-edges. - boxFaces: [6]HullFace, //< Box faces. - padding: [2]u8, //< Explicit padding, see b3HullData::padding. boxPlanes: [6]Plane, //< Box face planes. + boxFaces: [6]HullFace, //< Box faces. + padding: [10]u8, //< Explicit padding, see b3HullData::padding. + vx: [8]f32, //< vertex x + vy: [8]f32, //< vertex y + vz: [8]f32, //< vertex z + nx: [8]f32, //< normal x, padded to multiple of 4 + ny: [8]f32, //< normal y, padded to multiple of 4 + nz: [8]f32, //< normal z, padded to multiple of 4 } /**@}*/ // hull @@ -1989,7 +2004,7 @@ MeshDef :: struct { // Triangle vertices vertices: [^]Vec3 `fmt:"v,vertexCount"`, - // Triangle vertex indices. 3 for each triangle. + // Triangle vertex indices. 3 for each triangle. CCW winding. indices: [^]i32, // Triangle material index. 1 per triangle. Indexes into b3ShapeDef::materials. @@ -2333,13 +2348,13 @@ CompoundDef :: struct { } // The compound version depends on the tree, mesh, and hull versions. -COMPOUND_VERSION :: 0x830778DB07086EB4 ~ DYNAMIC_TREE_VERSION ~ MESH_VERSION ~ HULL_VERSION +COMPOUND_VERSION :: 0xB11DCE70FAD5622B ~ DYNAMIC_TREE_VERSION ~ MESH_VERSION ~ HULL_VERSION // Meshes used in compounds have limited space for materials. If you have // a mesh with many materials, you can use it outside of the compound. MAX_COMPOUND_MESH_MATERIALS :: 4 -// The runtime data for a baked compound shape. This is a potentially large yet highly optimized +// The data for a baked compound shape. This is a potentially large yet highly optimized // data structure. It can contain thousands of child shapes, yet at runtime it populates // into the world as a single shape in the runtime broad-phase. // This data structure has data living off the end and must be accessed using offsets. @@ -2851,7 +2866,9 @@ DebugShape :: struct { // Callbacks receive world coordinates. In large world mode the translation is double precision so // it stays accurate far from the origin. Shift into your own camera frame inside the callbacks. DebugDraw :: struct { - // Draws a shape and returns true if drawing should continue + // Draws a user shape. The userShape pointer is owned by the application and is known to Box3D as + // an opaque pointer returned from b3CreateDebugShapeCallback. When this is called the drawn shape has + // passed a culling test against drawingBounds below. DrawShapeFcn: proc "c" (userShape: rawptr, transform: WorldTransform, color: HexColor, ctx: rawptr) -> bool, // Draw a line segment. @@ -2912,7 +2929,7 @@ DebugDraw :: struct { drawContacts: bool, // Draw contact anchor A or B - drawAnchorA: c.int, + drawAnchorA: bool, // Option to visualize the graph coloring used for contacts and joints drawGraphColors: bool, @@ -2926,9 +2943,6 @@ DebugDraw :: struct { // Option to draw contact normal forces drawContactForces: bool, - // Option to draw contact friction forces - drawFrictionForces: bool, - // Option to draw islands as bounding boxes drawIslands: bool, diff --git a/vendor/box3d/lib/box3d.lib b/vendor/box3d/lib/box3d.lib index 38b5dad36..42ada6bba 100644 Binary files a/vendor/box3d/lib/box3d.lib and b/vendor/box3d/lib/box3d.lib differ diff --git a/vendor/box3d/lib/darwin/libbox3d.a b/vendor/box3d/lib/darwin/libbox3d.a index 712d53de5..491d6a685 100644 --- a/vendor/box3d/lib/darwin/libbox3d.a +++ b/vendor/box3d/lib/darwin/libbox3d.a @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6b000b0b69028af14d6a481b6bf6ca3b70d2c3a29cd9e73eab8b96e0deffa07d -size 2524600 +oid sha256:92ad74c3967667704cb7589ae6c64612a66112ea12ce4462b849f7aac4bc689f +size 2551264 diff --git a/vendor/box3d/lib/linux-amd64/libbox3d.a b/vendor/box3d/lib/linux-amd64/libbox3d.a index ac6223456..402424fa2 100644 --- a/vendor/box3d/lib/linux-amd64/libbox3d.a +++ b/vendor/box3d/lib/linux-amd64/libbox3d.a @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:948651aca53740a4a47a8251b68982b839fa62da91689035132be1c273cbd7da -size 1536158 +oid sha256:d698fbbe655db1d1ec6f52dcc23bfed4a2c981fa88040ba449f9f83609edba88 +size 3137140 diff --git a/vendor/box3d/src/include/box3d/base.h b/vendor/box3d/src/include/box3d/base.h index 25828b3fa..30cee4604 100644 --- a/vendor/box3d/src/include/box3d/base.h +++ b/vendor/box3d/src/include/box3d/base.h @@ -38,7 +38,7 @@ #ifdef __cplusplus #define B3_API extern "C" BOX3D_EXPORT #define B3_INLINE inline - + #define B3_ALIGN_AS(N) alignas(N) #if defined( _MSC_VER ) #define B3_FORCE_INLINE __forceinline #elif defined( __GNUC__ ) || defined( __clang__ ) @@ -52,6 +52,7 @@ #else #define B3_API BOX3D_EXPORT #define B3_INLINE static inline + #define B3_ALIGN_AS(N) _Alignas(N) #if defined( _MSC_VER ) #define B3_FORCE_INLINE static __forceinline @@ -67,6 +68,13 @@ #endif // clang-format on +// This is used to validate arguments for functions similar to printf. +#if defined( __GNUC__ ) || defined( __clang__ ) +#define B3_PRINTF_FORMAT( INDEX1, INDEX2 ) __attribute__( ( format( printf, INDEX1, INDEX2 ) ) ) +#else +#define B3_PRINTF_FORMAT( INDEX1, INDEX2 ) +#endif + #if defined( BOX3D_VALIDATE ) && !defined( NDEBUG ) #define B3_ENABLE_VALIDATION 1 #else @@ -124,7 +132,7 @@ B3_API void b3SetAssertFcn( b3AssertFcn* assertFcn ); /// Internal assertion handler. Allows for host intervention. B3_API int b3InternalAssert( const char* condition, const char* fileName, int lineNumber ); /// Assert that a condition is true. -#define B3_ASSERT( condition ) \ +#define B3_ASSERT( condition ) \ ( (void)( ( !!( condition ) ) || ( b3InternalAssert( #condition, __FILE__, (int)( __LINE__ ) ), 0 ) ) ) #else #define B3_ASSERT( ... ) ( (void)0 ) diff --git a/vendor/box3d/src/include/box3d/box3d.h b/vendor/box3d/src/include/box3d/box3d.h index 05b69dd63..d9938107c 100644 --- a/vendor/box3d/src/include/box3d/box3d.h +++ b/vendor/box3d/src/include/box3d/box3d.h @@ -524,31 +524,31 @@ B3_API b3WorldTransform b3Body_GetTransform( b3BodyId bodyId ); /// Set the world transform of a body. This acts as a teleport and is fairly expensive. /// @note Generally you should create a body with the intended transform. -/// @see b3BodyDef::position and b3BodyDef::rotation +/// @see b3BodyDef::position and b3BodyDef::rotation. B3_API void b3Body_SetTransform( b3BodyId bodyId, b3Pos position, b3Quat rotation ); -/// Get a local point on a body given a world point +/// Get a local point on a body given a world point. B3_API b3Vec3 b3Body_GetLocalPoint( b3BodyId bodyId, b3Pos worldPoint ); -/// Get a world point on a body given a local point +/// Get a world point on a body given a local point. B3_API b3Pos b3Body_GetWorldPoint( b3BodyId bodyId, b3Vec3 localPoint ); -/// Get a local vector on a body given a world vector +/// Get a local vector on a body given a world vector. B3_API b3Vec3 b3Body_GetLocalVector( b3BodyId bodyId, b3Vec3 worldVector ); -/// Get a world vector on a body given a local vector +/// Get a world vector on a body given a local vector. B3_API b3Vec3 b3Body_GetWorldVector( b3BodyId bodyId, b3Vec3 localVector ); /// Get the linear velocity of a body's center of mass. Usually in meters per second. B3_API b3Vec3 b3Body_GetLinearVelocity( b3BodyId bodyId ); -/// Get the angular velocity of a body in radians per second +/// Get the angular velocity of a body in radians per second. B3_API b3Vec3 b3Body_GetAngularVelocity( b3BodyId bodyId ); -/// Set the linear velocity of a body. Usually in meters per second. +/// Set the linear velocity of a body at the center of mass. Usually in meters per second. B3_API void b3Body_SetLinearVelocity( b3BodyId bodyId, b3Vec3 linearVelocity ); -/// Set the angular velocity of a body in radians per second +/// Set the angular velocity of a body in radians per second. B3_API void b3Body_SetAngularVelocity( b3BodyId bodyId, b3Vec3 angularVelocity ); /// Set the velocity to reach the given transform after a given time step. @@ -710,6 +710,14 @@ B3_API void b3Body_SetBullet( b3BodyId bodyId, bool flag ); /// Is this body a bullet? B3_API bool b3Body_IsBullet( b3BodyId bodyId ); +/// Allow this body to rotate fast. Useful for axially symmetric bodies, such as vehicle wheels. +/// Normally rotation speed is clamped to improve CCD. However, this clamping is unnecessary for +/// bodies that only rotate fast around an axis of symmetry. +B3_API void b3Body_AllowFastRotation( b3BodyId bodyId, bool flag ); + +/// Is this body allowed to rotate fast? +B3_API bool b3Body_IsFastRotationAllowed( b3BodyId bodyId ); + /// Enable or disable contact recycling for this body. Contact recycling is a performance optimization /// that reuses contact manifolds when bodies move slightly. Disabling it can avoid ghost collisions /// on characters at the cost of higher per-step work. Existing contacts retain their prior setting; @@ -777,6 +785,10 @@ B3_API int b3Body_CollideMover( b3BodyId bodyId, b3BodyPlaneResult* bodyPlanes, * @defgroup shape Shape * Functions to create, destroy, and access. * Shapes bind raw geometry to bodies and hold material properties including friction and restitution. + * You may add multiple shapes to a single body. There are no hard limits on shape count per body. + * + * When you create a shape on a body the center of mass moves. This can lead to the body linear velocity + * changing if the angular velocity is non-zero. * @{ */ @@ -816,8 +828,10 @@ B3_API b3ShapeId b3CreateMeshShape( b3BodyId bodyId, const b3ShapeDef* def, cons /// @return the shape id for accessing the shape B3_API b3ShapeId b3CreateHeightFieldShape( b3BodyId bodyId, const b3ShapeDef* def, const b3HeightFieldData* heightField ); -/// Compound shapes are only allowed on static bodies. -B3_API b3ShapeId b3CreateCompoundShape( b3BodyId bodyId, b3ShapeDef* def, const b3CompoundData* compound ); +/// Baked compound shapes are only allowed on static bodies. +/// Note: runtime compounds are achieved by adding multiple shapes to a body. +/// Runtime compounds can be dynamic and/or kinematic. +B3_API b3ShapeId b3CreateBakedCompoundShape( b3BodyId bodyId, b3ShapeDef* def, const b3CompoundData* compound ); /// Destroy a shape. You may defer the body mass update which can improve performance if several shapes on a /// body are destroyed at once. diff --git a/vendor/box3d/src/include/box3d/collision.h b/vendor/box3d/src/include/box3d/collision.h index 1ba0070a6..809afb921 100644 --- a/vendor/box3d/src/include/box3d/collision.h +++ b/vendor/box3d/src/include/box3d/collision.h @@ -44,7 +44,8 @@ B3_API uint64_t b3DynamicTree_GetCategoryBits( b3DynamicTree* tree, int proxyId B3_API b3TreeStats b3DynamicTree_Query( const b3DynamicTree* tree, b3AABB aabb, uint64_t maskBits, bool requireAllBits, b3TreeQueryCallbackFcn* callback, void* context ); -/// Query an AABB for the closest object. The callback function is called for each proxy that might be closest to the supplied point. +/// Query an AABB for the closest object. The callback function is called for each proxy that might be closest to the supplied +/// point. /// @param tree the dynamic tree to query /// @param point the query point /// @param maskBits nodes are skipped if the bit-wise AND with the node category bits is zero @@ -163,6 +164,17 @@ B3_INLINE const b3HullHalfEdge* b3GetHullEdges( const b3HullData* hull ) return (const b3HullHalfEdge*)( (intptr_t)hull + hull->edgeOffset ); } +/// Get read only hull planes. +B3_INLINE const b3Plane* b3GetHullPlanes( const b3HullData* hull ) +{ + if ( hull->planeOffset == 0 ) + { + return NULL; + } + + return (const b3Plane*)( (intptr_t)hull + hull->planeOffset ); +} + /// Get read only hull faces. B3_INLINE const b3HullFace* b3GetHullFaces( const b3HullData* hull ) { @@ -174,15 +186,30 @@ B3_INLINE const b3HullFace* b3GetHullFaces( const b3HullData* hull ) return (const b3HullFace*)( (intptr_t)hull + hull->faceOffset ); } -/// Get read only hull planes. -B3_INLINE const b3Plane* b3GetHullPlanes( const b3HullData* hull ) +/// Get read only SOA vertices. This is an array of vertices with all x values, +/// y values, and z values as separate arrays. The array lengths are padded to +/// a multiple of 4. The padded values are repeats of the first value. +B3_INLINE const float* b3GetHullSoaVertices( const b3HullData* hull ) { - if ( hull->planeOffset == 0 ) + if ( hull->soaVertexOffset == 0 ) { return NULL; } - return (const b3Plane*)( (intptr_t)hull + hull->planeOffset ); + return (const float*)( (intptr_t)hull + hull->soaVertexOffset ); +} + +/// Get read only SOA unit normal vectors. This is an array of normals with all x values, +/// y values, and z values as separate arrays. The array lengths are padded to +/// a multiple of 4. The padded values are repeats of the first value. +B3_INLINE const float* b3GetHullSoaNormals( const b3HullData* hull ) +{ + if ( hull->soaNormalOffset == 0 ) + { + return NULL; + } + + return (const float*)( (intptr_t)hull + hull->soaNormalOffset ); } /// Create a tessellated cylinder as a hull. @@ -426,14 +453,12 @@ B3_API b3CompoundData* b3CreateCompound( const b3CompoundDef* def ); /// Destroy a compound shape. B3_API void b3DestroyCompound( b3CompoundData* compound ); -/// If bytes is null then this returns the number of required bytes. This clones all the -/// data into the bytes buffer. This is expected to run offline or asynchronously. -/// This mutates the compound to nullify pointers, leaving the compound in an unusable state. +/// Cast the provided compound data to bytes, setting the internal pointers to null. +/// Use this before serializing the compound bytes. B3_API uint8_t* b3ConvertCompoundToBytes( b3CompoundData* compound ); -/// Convert bytes to compound. This does not clone. The bytes must remain in scope while the -/// compound is used. This is done to improve run-time performance and allow for instancing. -/// The bytes are mutated to fixup pointers. +/// Cast the provided bytes to compound data, setting up internal pointers. +/// Use this after de-serializing the compound bytes. B3_API b3CompoundData* b3ConvertBytesToCompound( uint8_t* bytes, int byteCount ); /**@}*/ // compound @@ -610,17 +635,17 @@ B3_API void b3CollideHullAndCapsule( b3LocalManifold* manifold, int capacity, co B3_API void b3CollideHulls( b3LocalManifold* manifold, int capacity, const b3HullData* hullA, const b3HullData* hullB, b3Transform transformBtoA, b3SATCache* cache ); -/// Collide a capsule and a triangle. -B3_API void b3CollideCapsuleAndTriangle( b3LocalManifold* manifold, int capacity, const b3Capsule* capsuleA, - const b3Vec3* triangleB, b3SimplexCache* cache ); +/// Collide a triangle and capsule. Normal points from triangle to capsule. +B3_API void b3CollideTriangleAndCapsule( b3LocalManifold* manifold, int capacity, const b3Vec3* triangleA, + const b3Capsule* capsuleB, b3SimplexCache* cache ); -/// Collide a hull and a triangle. -B3_API void b3CollideHullAndTriangle( b3LocalManifold* manifold, int capacity, const b3HullData* hullA, b3Vec3 v1, b3Vec3 v2, - b3Vec3 v3, int triangleFlags, b3SATCache* cache, bool enableSpeculative ); +/// Collide a triangle and hull. Normal points from triangle to hull. +B3_API void b3CollideTriangleAndHull( b3LocalManifold* manifold, int capacity, b3Vec3 v1, b3Vec3 v2, b3Vec3 v3, int triangleFlags, + const b3HullData* hullB, b3SATCache* cache, bool enableSpeculative ); -/// Collide a sphere and a triangle. -B3_API void b3CollideSphereAndTriangle( b3LocalManifold* manifold, int capacity, const b3Sphere* sphereA, - const b3Vec3* triangleB ); +/// Collide a triangle and sphere. Normal points from triangle to sphere. +B3_API void b3CollideTriangleAndSphere( b3LocalManifold* manifold, int capacity, const b3Vec3* triangleA, + const b3Sphere* sphereB ); /**@}*/ // collision diff --git a/vendor/box3d/src/include/box3d/config.h b/vendor/box3d/src/include/box3d/config.h index 2790fc0b8..1309827c9 100644 --- a/vendor/box3d/src/include/box3d/config.h +++ b/vendor/box3d/src/include/box3d/config.h @@ -29,3 +29,7 @@ // so this suits embedding more than shipping Box3D as its own DLL. //#define BOX3D_EXPORT MYENGINE_API +// Enable assertions when NDEBUG is defined +// #define B3_ENABLE_ASSERT + +// #define B3_RESTITUTION_ITERATIONS diff --git a/vendor/box3d/src/include/box3d/constants.h b/vendor/box3d/src/include/box3d/constants.h index 87dd56d7e..3ac61067a 100644 --- a/vendor/box3d/src/include/box3d/constants.h +++ b/vendor/box3d/src/include/box3d/constants.h @@ -52,8 +52,14 @@ B3_API float b3GetStallThreshold( void ); // @warning modifying this can have a significant impact on stability #define B3_LINEAR_SLOP ( 0.005f * b3GetLengthUnitsPerMeter() ) +/// The minimum length of a capsules. Very short capsules should be created as spheres +/// to avoid numerical problems. #define B3_MIN_CAPSULE_LENGTH ( B3_LINEAR_SLOP ) +/// Minimum contact point friction weight, lower bound for speculative points. Made small +/// enough to be washed away by weights that hit 1. +#define B3_MIN_FRICTION_WEIGHT ( 1e-10f ) + /// The distance between shapes where they are considered overlapped. This is needed /// because GJK may return small positive values for overlapped shapes in degenerate /// configurations. @@ -100,11 +106,34 @@ B3_API float b3GetStallThreshold( void ); /// The maximum number of contact points between two touching shapes. #define B3_MAX_MANIFOLD_POINTS 4 +/// The number of iterations for gyroscopic torques. +#ifndef B3_GYROSCOPIC_ITERATIONS +#define B3_GYROSCOPIC_ITERATIONS 1 +#endif + +/// The maximum number of convex hull vertices. This is fixed for performance reasons. +#define B3_MAX_HULL_VERTICES 128 + +/// The maximum number of convex hull faces. +#define B3_MAX_HULL_FACES 128 + +/// The maximum number of convex hull edges. Full edges, not half-edges. +#define B3_MAX_HULL_EDGES 128 + +/// Relative tolerance used to determine if two edges are parallel. +#define B3_PARALLEL_EDGE_TOL 0.005f + /// The maximum number points to use for shape cast proxies (swept point cloud). -#define B3_MAX_SHAPE_CAST_POINTS 64 +#define B3_MAX_SHAPE_CAST_POINTS B3_MAX_HULL_VERTICES /// These generous limits allow for easy hashing. See b3ShapePairKey. #define B3_SHAPE_POWER 22 #define B3_CHILD_POWER ( 64 - 2 * B3_SHAPE_POWER ) #define B3_MAX_SHAPES ( 1 << B3_SHAPE_POWER ) #define B3_MAX_CHILD_SHAPES ( 1 << B3_CHILD_POWER ) + +/// Increase this if your application needs more accurate restitution. Doing so will +/// slow down the simulation. Must be 1 or more. +#ifndef B3_RESTITUTION_ITERATIONS +#define B3_RESTITUTION_ITERATIONS 1 +#endif diff --git a/vendor/box3d/src/include/box3d/id.h b/vendor/box3d/src/include/box3d/id.h index 447fd34ed..9f05e1287 100644 --- a/vendor/box3d/src/include/box3d/id.h +++ b/vendor/box3d/src/include/box3d/id.h @@ -96,13 +96,14 @@ static const b3JointId b3_nullJointId = B3_NULL_ID; static const b3ContactId b3_nullContactId = B3_NULL_ID; /// Macro to determine if any id is null. -#define B3_IS_NULL( id ) ( id.index1 == 0 ) +#define B3_IS_NULL( id ) ( ( id ).index1 == 0 ) /// Macro to determine if any id is non-null. -#define B3_IS_NON_NULL( id ) ( id.index1 != 0 ) +#define B3_IS_NON_NULL( id ) ( ( id ).index1 != 0 ) /// Compare two ids for equality. Doesn't work for b3WorldId. Don't mix types. -#define B3_ID_EQUALS( id1, id2 ) ( id1.index1 == id2.index1 && id1.world0 == id2.world0 && id1.generation == id2.generation ) +#define B3_ID_EQUALS( id1, id2 ) \ + ( ( id1 ).index1 == ( id2 ).index1 && ( id1 ).world0 == ( id2 ).world0 && ( id1 ).generation == ( id2 ).generation ) /// Store a world id into a uint32_t. B3_ID_INLINE uint32_t b3StoreWorldId( b3WorldId id ) diff --git a/vendor/box3d/src/include/box3d/types.h b/vendor/box3d/src/include/box3d/types.h index 40e685357..2f9483a4c 100644 --- a/vendor/box3d/src/include/box3d/types.h +++ b/vendor/box3d/src/include/box3d/types.h @@ -419,6 +419,9 @@ typedef struct b3SurfaceMaterial /// carry a b3DebugMaterial preset, see b3MakeDebugColor. /// @see b3HexColor uint32_t customColor; + + /// Explicit padding. Must be zero. + uint32_t padding; } b3SurfaceMaterial; /// Use this to initialize your surface material @@ -490,6 +493,7 @@ typedef struct b3ShapeDef bool isSensor; /// Enable sensor events for this shape. This applies to sensors and non-sensors. False by default, even for sensors. + /// Only convex shapes may act as sensor visitors. bool enableSensorEvents; /// Enable contact events for this shape. Only applies to kinematic and dynamic bodies. Ignored for sensors. False by default. @@ -1930,8 +1934,7 @@ typedef struct b3Capsule * @{ */ -/// A hull vertex. Identified by a half-edge with this -/// vertex as its tail. +/// A hull vertex. Identified by a half-edge with this vertex as its tail. typedef struct b3HullVertex { /// A half-edge that has this vertex as the origin @@ -1965,7 +1968,7 @@ typedef struct b3HullFace } b3HullFace; /// 64-bit hull version. Useful for validating serialized data. -#define B3_HULL_VERSION 0x9D4716CE3793900Eull +#define B3_HULL_VERSION 0xDA5150191B994C01ull /// A convex hull. /// @note This data structure has data hanging off the end and cannot be directly copied. @@ -2016,11 +2019,17 @@ typedef struct b3HullData /// The face count. Hulls faces are convex polygons. int faceCount; + /// Offset of the face plane array in bytes from the struct address. + int planeOffset; + /// Offset of the face array in bytes from the struct address. int faceOffset; - /// Offset of the face plane array in bytes from the struct address. - int planeOffset; + /// Offset of structure of array (SOA) vertices + int soaVertexOffset; + + /// Offset of structure of array (SOA) unit normal vectors + int soaNormalOffset; /// Explicit padding. Hull identity is a content hash and memcmp over raw bytes, /// so there must be no unnamed padding for struct copies to scramble. @@ -2035,9 +2044,15 @@ typedef struct b3BoxHull b3HullVertex boxVertices[8]; ///< Box vertices. b3Vec3 boxPoints[8]; ///< Box points. b3HullHalfEdge boxEdges[24]; ///< Box half-edges. - b3HullFace boxFaces[6]; ///< Box faces. - uint8_t padding[2]; ///< Explicit padding, see b3HullData::padding. b3Plane boxPlanes[6]; ///< Box face planes. + b3HullFace boxFaces[6]; ///< Box faces. + uint8_t padding[10]; ///< Explicit padding, see b3HullData::padding. + float vx[8]; ///< vertex x + float vy[8]; ///< vertex y + float vz[8]; ///< vertex z + float nx[8]; ///< normal x, padded to multiple of 4 + float ny[8]; ///< normal y, padded to multiple of 4 + float nz[8]; ///< normal z, padded to multiple of 4 } b3BoxHull; /**@}*/ // hull @@ -2048,13 +2063,13 @@ typedef struct b3BoxHull * @{ */ -/// This is used to create a re-usable collision mesh +/// This is used to create a re-usable collision mesh. typedef struct b3MeshDef { /// Triangle vertices b3Vec3* vertices; - /// Triangle vertex indices. 3 for each triangle. + /// Triangle vertex indices. 3 for each triangle. CCW winding. int32_t* indices; /// Triangle material index. 1 per triangle. Indexes into b3ShapeDef::materials. @@ -2407,14 +2422,14 @@ typedef struct b3CompoundDef int sphereCount; } b3CompoundDef; -/// The compound version depends on the tree, mesh, and hull versions. -#define B3_COMPOUND_VERSION ( 0x830778DB07086EB4ull ^ B3_DYNAMIC_TREE_VERSION ^ B3_MESH_VERSION ^ B3_HULL_VERSION ) +/// The baked compound version depends on the tree, mesh, and hull versions. +#define B3_COMPOUND_VERSION ( 0xB11DCE70FAD5622Bull ^ B3_DYNAMIC_TREE_VERSION ^ B3_MESH_VERSION ^ B3_HULL_VERSION ) /// Meshes used in compounds have limited space for materials. If you have /// a mesh with many materials, you can use it outside of the compound. #define B3_MAX_COMPOUND_MESH_MATERIALS 4 -/// The runtime data for a baked compound shape. This is a potentially large yet highly optimized +/// The data for a baked compound shape. This is a potentially large yet highly optimized /// data structure. It can contain thousands of child shapes, yet at runtime it populates /// into the world as a single shape in the runtime broad-phase. /// This data structure has data living off the end and must be accessed using offsets. @@ -2530,10 +2545,10 @@ typedef struct b3ChildShape /// Tagged union. union { - b3Capsule capsule; ///< Capsule. + b3Capsule capsule; ///< Capsule. const b3HullData* hull; ///< Hull. - b3Mesh mesh; ///< Mesh. - b3Sphere sphere; ///< Sphere. + b3Mesh mesh; ///< Mesh. + b3Sphere sphere; ///< Sphere. }; /// Transform of the shape into compound local space. @@ -2943,12 +2958,12 @@ typedef struct b3DebugShape /// Tagged union. union { - const b3Capsule* capsule; ///< Capsule shape. + const b3Capsule* capsule; ///< Capsule shape. const b3CompoundData* compound; ///< Compound shape. const b3HeightFieldData* heightField; ///< Height-field shape. - const b3HullData* hull; ///< Convex hull shape. - const b3Mesh* mesh; ///< Mesh shape with scale. - const b3Sphere* sphere; ///< Sphere shape. + const b3HullData* hull; ///< Convex hull shape. + const b3Mesh* mesh; ///< Mesh shape with scale. + const b3Sphere* sphere; ///< Sphere shape. }; } b3DebugShape; @@ -2957,8 +2972,10 @@ typedef struct b3DebugShape /// it stays accurate far from the origin. Shift into your own camera frame inside the callbacks. typedef struct b3DebugDraw { - /// Draws a shape and returns true if drawing should continue - bool ( *DrawShapeFcn )( void* userShape, b3WorldTransform transform, b3HexColor color, void* context ); + /// Draws a user shape. The userShape pointer is owned by the application and is known to Box3D as + /// an opaque pointer returned from b3CreateDebugShapeCallback. When this is called the drawn shape has + /// passed a culling test against drawingBounds below. + void ( *DrawShapeFcn )( void* userShape, b3WorldTransform transform, b3HexColor color, void* context ); /// Draw a line segment. void ( *DrawSegmentFcn )( b3Pos p1, b3Pos p2, b3HexColor color, void* context ); @@ -3018,7 +3035,7 @@ typedef struct b3DebugDraw bool drawContacts; /// Draw contact anchor A or B - int drawAnchorA; + bool drawAnchorA; /// Option to visualize the graph coloring used for contacts and joints bool drawGraphColors; @@ -3032,9 +3049,6 @@ typedef struct b3DebugDraw /// Option to draw contact normal forces bool drawContactForces; - /// Option to draw contact friction forces - bool drawFrictionForces; - /// Option to draw islands as bounding boxes bool drawIslands; diff --git a/vendor/box3d/src/src/CMakeLists.txt b/vendor/box3d/src/src/CMakeLists.txt index 0460f85ba..3f43e7da1 100644 --- a/vendor/box3d/src/src/CMakeLists.txt +++ b/vendor/box3d/src/src/CMakeLists.txt @@ -33,6 +33,7 @@ set(BOX3D_SOURCE_FILES dynamic_tree.c height_field.c hull.c + hull.h id_pool.c id_pool.h island.c @@ -154,7 +155,8 @@ if (BOX3D_PROFILE) endif() if(BOX3D_VALIDATE) - message(STATUS "Box3D validation ON") + # Only effective when NDEBUG is undefined, see base.h + message(STATUS "Box3D validation ON (debug builds)") target_compile_definitions(box3d PRIVATE BOX3D_VALIDATE) endif() @@ -169,8 +171,11 @@ if (BOX3D_DOUBLE_PRECISION) target_compile_definitions(box3d PUBLIC BOX3D_DOUBLE_PRECISION) endif() +# Enable asserts in release with debug info +target_compile_definitions(box3d PUBLIC "$<$:B3_ENABLE_ASSERT>") + if (MSVC) - message(STATUS "Box3D on MSVC") + message(STATUS "Box3D on MSVC") if (BUILD_SHARED_LIBS) # this is needed by DLL users to import Box3D symbols target_compile_definitions(box3d INTERFACE BOX3D_DLL) @@ -179,9 +184,6 @@ if (MSVC) # Visual Studio won't load the natvis unless it is in the project target_sources(box3d PRIVATE box3d.natvis) - # Enable asserts in release with debug info - target_compile_definitions(box3d PUBLIC "$<$:B3_ENABLE_ASSERT>") - # Warnings Wall is problematic in mixed C/C++, so using W4 target_compile_options(box3d PRIVATE /W4) # target_compile_options(box3d PRIVATE /wd4820 /wd5045 /wd4061 /wd4711 /wd4514 /wd4365 /wd5219 /wd5039) diff --git a/vendor/box3d/src/src/block_allocator.c b/vendor/box3d/src/src/block_allocator.c index 4c1f1544e..ebc6d7942 100644 --- a/vendor/box3d/src/src/block_allocator.c +++ b/vendor/box3d/src/src/block_allocator.c @@ -9,6 +9,10 @@ b3BlockAllocator b3CreateBlockAllocator( int elementSize, int initialCount ) { B3_ASSERT( elementSize >= (int)sizeof( void* ) ); + // The free list stores a pointer inside each element, so the stride must keep every element aligned. + // Blocks come from b3Alloc, so a stride that is a multiple of B3_ALIGNMENT aligns all elements. + elementSize = ( ( elementSize - 1 ) | ( B3_ALIGNMENT - 1 ) ) + 1; + b3BlockAllocator allocator = { 0 }; b3Array_Create( allocator.blocks ); @@ -81,6 +85,7 @@ void b3FreeElement( b3BlockAllocator* allocator, void* element ) B3_ASSERT( allocator != NULL ); B3_ASSERT( element != NULL ); B3_ASSERT( allocator->allocationCount > 0 ); + B3_ASSERT( ( (uintptr_t)element & ( B3_ALIGNMENT - 1 ) ) == 0 ); allocator->allocationCount -= 1; diff --git a/vendor/box3d/src/src/block_allocator.h b/vendor/box3d/src/src/block_allocator.h index e9922d012..1c62ed8b4 100644 --- a/vendor/box3d/src/src/block_allocator.h +++ b/vendor/box3d/src/src/block_allocator.h @@ -26,7 +26,7 @@ typedef struct b3BlockAllocator int allocationCount; } b3BlockAllocator; -// Element must be large enough to hold a pointer +// Element must be large enough to hold a pointer. The element size is rounded up to B3_ALIGNMENT. b3BlockAllocator b3CreateBlockAllocator( int elementSize, int initialCount ); void b3DestroyBlockAllocator( b3BlockAllocator* allocator ); diff --git a/vendor/box3d/src/src/body.c b/vendor/box3d/src/src/body.c index b78642c6c..bce16dea0 100644 --- a/vendor/box3d/src/src/body.c +++ b/vendor/box3d/src/src/body.c @@ -253,8 +253,6 @@ b3BodyId b3CreateBody( b3WorldId worldId, const b3BodyDef* def ) bodyState->angularVelocity = def->angularVelocity; bodyState->deltaRotation = b3Quat_identity; bodyState->flags = bodySim->flags; - - bodySim->maxAngularVelocity = b3Length( def->angularVelocity ) + 5.0f; } if ( bodyId == world->bodies.count ) @@ -2305,6 +2303,37 @@ bool b3Body_IsBullet( b3BodyId bodyId ) return ( body->flags & b3_isBullet ) != 0; } +void b3Body_AllowFastRotation(b3BodyId bodyId, bool flag) +{ + b3World* world = b3GetUnlockedWorld( bodyId.world0 ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, BodyAllowFastRotation, bodyId, flag ); + + uint32_t newFlag = flag ? b3_allowFastRotation : 0; + + b3Body* body = b3GetBodyFullId( world, bodyId ); + if ( ( body->flags & b3_allowFastRotation ) == newFlag ) + { + return; + } + + body->flags &= ~b3_allowFastRotation; + body->flags |= newFlag; + + b3SyncBodyFlags( world, body ); +} + +bool b3Body_IsFastRotationAllowed(b3BodyId bodyId) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + return ( body->flags & b3_allowFastRotation ) != 0; +} + void b3Body_EnableContactRecycling( b3BodyId bodyId, bool flag ) { b3World* world = b3GetUnlockedWorld( bodyId.world0 ); diff --git a/vendor/box3d/src/src/body.h b/vendor/box3d/src/src/body.h index 5eebf67ab..ae90105da 100644 --- a/vendor/box3d/src/src/body.h +++ b/vendor/box3d/src/src/body.h @@ -208,7 +208,6 @@ typedef struct b3BodySim float minExtent; b3Vec3 maxExtent; - float maxAngularVelocity; float linearDamping; float angularDamping; float gravityScale; diff --git a/vendor/box3d/src/src/compound.c b/vendor/box3d/src/src/compound.c index 29da3c662..1df1f1e82 100644 --- a/vendor/box3d/src/src/compound.c +++ b/vendor/box3d/src/src/compound.c @@ -3,7 +3,7 @@ #include "compound.h" -#include "hull_map.h" +#include "hull.h" #include "math_internal.h" #include "shape.h" @@ -226,13 +226,19 @@ static bool b3CompareMeshes( const b3MeshData* mesh1, const b3MeshData* mesh2 ) #define FREE_FN b3Free #include "verstable.h" +_Static_assert( sizeof( b3SurfaceMaterial ) == 40, "review padding" ); + static inline uint64_t b3HashMaterial( const b3SurfaceMaterial* material ) { + B3_ASSERT( material->padding == 0 ); + return vt_wyhash( material, sizeof( b3SurfaceMaterial ) ); } static bool b3CompareMaterials( const b3SurfaceMaterial* mat1, const b3SurfaceMaterial* mat2 ) { + B3_ASSERT( mat1->padding == 0 && mat2->padding == 0); + if ( mat1 == mat2 ) { return true; @@ -276,7 +282,7 @@ b3CompoundData* b3CreateCompound( const b3CompoundDef* def ) int sphereCount = def->sphereCount; b3CompoundSphere* sphereInstances = b3AllocZeroed( sphereCount * sizeof( b3CompoundSphere ) ); - // Determine material capacity + // Determine material capacity. int materialCapacity = convexCount; for ( int i = 0; i < def->meshCount; ++i ) { @@ -296,10 +302,10 @@ b3CompoundData* b3CreateCompound( const b3CompoundDef* def ) const b3CompoundCapsuleDef* capsuleDef = def->capsules + i; capsuleInstances[i].capsule = capsuleDef->capsule; - // Look for an existing material + // Look for an existing material. b3MaterialMap_itr materialItr = b3MaterialMap_get_or_insert( &materialMap, &capsuleDef->material, materialCount ); - // Get the shared material index + // Get the shared material index. int materialIndex = materialItr.data->val; capsuleInstances[i].materialIndex = materialIndex; @@ -333,10 +339,10 @@ b3CompoundData* b3CreateCompound( const b3CompoundDef* def ) b3DynamicTree_CreateProxy( &tree, aabb, ~0ull, childIndex ); childIndex += 1; - // Look for an existing material + // Look for an existing material. b3MaterialMap_itr materialItr = b3MaterialMap_get_or_insert( &materialMap, &hullDef->material, materialCount ); - // Get the shared material index + // Get the shared material index. int materialIndex = materialItr.data->val; hullInstances[i].materialIndex = materialIndex; @@ -395,11 +401,11 @@ b3CompoundData* b3CreateCompound( const b3CompoundDef* def ) for ( int j = 0; j < meshDef->materialCount; ++j ) { - // Look for an existing material + // Look for an existing material. b3MaterialMap_itr materialItr = b3MaterialMap_get_or_insert( &materialMap, &meshDef->materials[j], materialCount ); - // Get the shared material index + // Get the shared material index. int materialIndex = materialItr.data->val; meshInstances[i].materialIndices[j] = materialIndex; @@ -411,17 +417,17 @@ b3CompoundData* b3CreateCompound( const b3CompoundDef* def ) } } - // Look for an existing matching mesh + // Look for an existing matching mesh. b3MeshMap_itr itr = b3MeshMap_get_or_insert( &meshMap, meshData, sharedMeshCount ); - // Get the shared mesh index + // Get the shared mesh index. int sharedMeshIndex = itr.data->val; - // Create mesh instance + // Create mesh instance. meshInstances[i].transform = def->meshes[i].transform; meshInstances[i].scale = def->meshes[i].scale; - // The offset isn't known yet, so store the index of the shared mesh + // The offset isn't known yet, so store the index of the shared mesh. meshInstances[i].meshOffset = sharedMeshIndex; // Is this a new mesh? @@ -443,10 +449,10 @@ b3CompoundData* b3CreateCompound( const b3CompoundDef* def ) const b3CompoundSphereDef* sphereDef = def->spheres + i; sphereInstances[i].sphere = sphereDef->sphere; - // Look for an existing material + // Look for an existing material. b3MaterialMap_itr materialItr = b3MaterialMap_get_or_insert( &materialMap, &sphereDef->material, materialCount ); - // Get the shared material index + // Get the shared material index. int materialIndex = materialItr.data->val; sphereInstances[i].materialIndex = materialIndex; @@ -467,58 +473,55 @@ b3CompoundData* b3CreateCompound( const b3CompoundDef* def ) b3DynamicTree_Rebuild( &tree, true ); - int byteCount = sizeof( b3CompoundData ); - - // Tree nodes - todo 64 byte alignment - int nodeOffset = byteCount; - byteCount += tree.nodeCapacity * sizeof( b3TreeNode ); - - int materialOffset = byteCount; - byteCount += materialCount * sizeof( b3SurfaceMaterial ); - - int capsuleOffset = byteCount; - byteCount += def->capsuleCount * sizeof( b3CompoundCapsule ); + // Tree nodes + size_t byteCount = b3AlignUp8( sizeof( b3CompoundData ) ); + int nodeOffset = (int)byteCount; + byteCount += b3AlignUp8( tree.nodeCapacity * sizeof( b3TreeNode ) ); + int materialOffset = (int)byteCount; + byteCount += b3AlignUp8( materialCount * sizeof( b3SurfaceMaterial ) ); + int capsuleOffset = (int)byteCount; + byteCount += b3AlignUp8( def->capsuleCount * sizeof( b3CompoundCapsule ) ); // Hull data layout has another level of indirection to allow for tight data packing // 1. hull instance array : hull count array of b3HullInstance with individual hull transforms and offsets // 2. heterogeneous array of shared hull data : each shared hull can have a different byte count, so direct indexing is not // possible - int hullArrayOffset = byteCount; + int hullArrayOffset = (int)byteCount; // Array of hull instances - byteCount += hullCount * sizeof( b3HullInstance ); + byteCount += b3AlignUp8( hullCount * sizeof( b3HullInstance ) ); // Packed shared hull blobs for ( int i = 0; i < sharedHullCount; ++i ) { - sharedHulls[i].hullOffset = byteCount; - byteCount += sharedHulls[i].hull->byteCount; + sharedHulls[i].hullOffset = (int)byteCount; + byteCount += b3AlignUp8( sharedHulls[i].hull->byteCount ); } // Mesh data layout has another level of indirection to allow for tight data packing // 1. mesh instance array : mesh count array of b3MeshInstance with individual mesh transform, scale, and offset // 2. heterogeneous array of shared mesh data : each shared mesh can have a different byte count, so direct indexing is not // possible - int meshArrayOffset = byteCount; + int meshArrayOffset = (int)byteCount; // Array of mesh instances - byteCount += meshCount * sizeof( b3MeshInstance ); + byteCount += b3AlignUp8( meshCount * sizeof( b3MeshInstance ) ); // Packed shared mesh blobs for ( int i = 0; i < sharedMeshCount; ++i ) { - sharedMeshes[i].meshOffset = byteCount; - byteCount += sharedMeshes[i].meshData->byteCount; + sharedMeshes[i].meshOffset = (int)byteCount; + byteCount += b3AlignUp8( sharedMeshes[i].meshData->byteCount ); } - int sphereOffset = byteCount; - byteCount += def->sphereCount * sizeof( b3CompoundSphere ); + int sphereOffset = (int)byteCount; + byteCount += b3AlignUp8( def->sphereCount * sizeof( b3CompoundSphere ) ); b3CompoundData* compound = b3Alloc( byteCount ); memset( compound, 0, byteCount ); compound->version = B3_COMPOUND_VERSION; - compound->byteCount = byteCount; + compound->byteCount = (int)byteCount; compound->nodeOffset = nodeOffset; memcpy( &compound->tree, &tree, sizeof( b3DynamicTree ) ); diff --git a/vendor/box3d/src/src/contact.c b/vendor/box3d/src/src/contact.c index 92e38b018..1506bdbe5 100644 --- a/vendor/box3d/src/src/contact.c +++ b/vendor/box3d/src/src/contact.c @@ -745,6 +745,16 @@ bool b3UpdateContact( b3World* world, int workerIndex, b3Contact* contact, b3Sha childShapeA.type = child.type; + // Handle child material for non-meshes. + if ( child.type != b3_meshShape ) + { + B3_ASSERT( 0 <= child.materialIndices[0] && child.materialIndices[0] < shapeA->materialCount ); + const b3SurfaceMaterial* parentMaterials = b3GetShapeMaterials( shapeA ); + childShapeA.material = parentMaterials[child.materialIndices[0]]; + childShapeA.materials = NULL; + childShapeA.materialCount = 1; + } + if ( child.type == b3_capsuleShape ) { childShapeA.capsule = child.capsule; diff --git a/vendor/box3d/src/src/contact_solver.c b/vendor/box3d/src/src/contact_solver.c index 501f85876..48de40973 100644 --- a/vendor/box3d/src/src/contact_solver.c +++ b/vendor/box3d/src/src/contact_solver.c @@ -10,14 +10,13 @@ #include "math_internal.h" #include "physics_world.h" #include "platform.h" +#include "simd.h" #include "solver_set.h" #if B3_ENABLE_VALIDATION #include "shape.h" #endif -#define FIXED_ANCHORS 1 - // contact separation for sub-stepping // s = s0 + dot(cB + rB - cA - rA, normal) // normal is held constant @@ -38,6 +37,9 @@ void b3PrepareContacts_Mesh( b3SolverBlock block, b3StepContext* context ) float warmStartScale = world->enableWarmStarting ? 1.0f : 0.0f; + // Used for friction center weighting. + float invTau = 1.0f / B3_SPECULATIVE_DISTANCE; + // Need to use spans in order to find the associated b2Contact, which is per color b3ContactPrepareSpan* spans = context->contactPrepareSpans; b3ManifoldConstraint* manifoldBase = context->manifoldConstraints; @@ -77,7 +79,7 @@ void b3PrepareContacts_Mesh( b3SolverBlock block, b3StepContext* context ) int localIndex = index - colorStart; B3_ASSERT( 0 <= localIndex && localIndex < spans[colorIndex].count ); int contactId = specs[localIndex].contactId; - b3Contact* contact = b3Array_Get( world->contacts, contactId ); + b3Contact* contact = b3Array_Get( world->contacts, contactId ); B3_ASSERT( contact->contactId == contactId ); int indexA = contact->bodySimIndexA; @@ -86,13 +88,13 @@ void b3PrepareContacts_Mesh( b3SolverBlock block, b3StepContext* context ) #if B3_ENABLE_VALIDATION if ( indexA != B3_NULL_INDEX ) { - b3Body* bodyA = b3Array_Get( world->bodies, contact->edges[0].bodyId ); + b3Body* bodyA = b3Array_Get( world->bodies, contact->edges[0].bodyId ); B3_ASSERT( indexA == bodyA->localIndex ); } if ( indexB != B3_NULL_INDEX ) { - b3Body* bodyB = b3Array_Get( world->bodies, contact->edges[1].bodyId ); + b3Body* bodyB = b3Array_Get( world->bodies, contact->edges[1].bodyId ); B3_ASSERT( indexB == bodyB->localIndex ); } #endif @@ -184,6 +186,7 @@ void b3PrepareContacts_Mesh( b3SolverBlock block, b3StepContext* context ) b3Vec3 centerA = b3Vec3_zero; b3Vec3 centerB = b3Vec3_zero; + float totalFrictionWeight = 0.0f; for ( int pointIndex = 0; pointIndex < pointCount; ++pointIndex ) { @@ -193,7 +196,9 @@ void b3PrepareContacts_Mesh( b3SolverBlock block, b3StepContext* context ) b3ManifoldPoint* mp = manifold->points + pointIndex; cp->rA = mp->anchorA; cp->rB = mp->anchorB; - cp->baseSeparation = mp->separation - b3Dot( b3Sub( cp->rB, cp->rA ), normal ); + + float s = mp->separation; + cp->baseSeparation = s - b3Dot( b3Sub( cp->rB, cp->rA ), normal ); cp->normalImpulse = warmStartScale * mp->normalImpulse; cp->totalNormalImpulse = 0.0f; @@ -210,15 +215,22 @@ void b3PrepareContacts_Mesh( b3SolverBlock block, b3StepContext* context ) b3Vec3 vrB = b3Add( vB, b3Cross( wB, rB ) ); cp->relativeVelocity = b3Dot( normal, b3Sub( vrB, vrA ) ); - centerA = b3Add( centerA, rA ); - centerB = b3Add( centerB, rB ); + // C0 friction center decay. Needed to prevent spinning top drift (GyroscopicPrecession sample). + // Contacts with separation greater than twice the speculative distance only matter for CCD and + // should not contribute to the friction center. They are not important for jitter reduction. Closer + // points may begin to touch on and off, so the friction center needs to move smoothly. + // Epsilon to avoid a branch below (or divide by zero). Small enough to get washed out normally. + float weight = b3ClampFloat( 2.0f - s * invTau, B3_MIN_FRICTION_WEIGHT, 1.0f ); + centerA = b3MulAdd( centerA, weight, rA ); + centerB = b3MulAdd( centerB, weight, rB ); + totalFrictionWeight += weight; } - float invCount = 1.0f / pointCount; - centerA = b3MulSV( invCount, centerA ); - centerB = b3MulSV( invCount, centerB ); - constraint->originA = centerA; - constraint->originB = centerB; + float invWeight = 1.0f / totalFrictionWeight; + centerA = b3MulSV( invWeight, centerA ); + centerB = b3MulSV( invWeight, centerB ); + constraint->centerA = centerA; + constraint->centerB = centerB; for ( int pointIndex = 0; pointIndex < pointCount; ++pointIndex ) { @@ -265,7 +277,7 @@ void b3WarmStartContacts_Mesh( b3SolverBlock block, b3StepContext* context ) { b3World* world = context->world; b3GraphColor* color = world->constraintGraph.colors + block.colorIndex; - b3SolverSet* awakeSet = b3Array_Get( world->solverSets, b3_awakeSet ); + b3SolverSet* awakeSet = b3Array_Get( world->solverSets, b3_awakeSet ); b3BodyState* states = awakeSet->bodyStates.data; b3ContactConstraint* constraints = color->contactConstraints; @@ -319,8 +331,8 @@ void b3WarmStartContacts_Mesh( b3SolverBlock block, b3StepContext* context ) // Central friction { - b3Vec3 rA = constraint->originA; - b3Vec3 rB = constraint->originB; + b3Vec3 rA = constraint->centerA; + b3Vec3 rB = constraint->centerB; b3Vec3 impulse = b3MulSV( constraint->frictionImpulse.x, constraint->tangent1 ); impulse = b3Add( impulse, b3MulSV( constraint->frictionImpulse.y, constraint->tangent2 ) ); @@ -515,8 +527,8 @@ void b3SolveContacts_Mesh( b3SolverBlock block, b3StepContext* context, bool use b3Vec3 tangent2 = constraint->tangent2; // Fixed anchor points for applying impulses - b3Vec3 rA = constraint->originA; - b3Vec3 rB = constraint->originB; + b3Vec3 rA = constraint->centerA; + b3Vec3 rB = constraint->centerB; // Relative tangent velocity at contact b3Vec3 vrA = b3Add( vA, b3Cross( wA, rA ) ); @@ -756,8 +768,8 @@ void b3StoreImpulses_Mesh( b3SolverBlock block, b3StepContext* context, int work mp->totalNormalImpulse = cp->totalNormalImpulse; mp->normalVelocity = cp->relativeVelocity; - if ( checkHitEvents && flagged == false && - mp->normalVelocity < negHitThreshold && mp->totalNormalImpulse > 0.0f ) + if ( checkHitEvents && flagged == false && mp->normalVelocity < negHitThreshold && + mp->totalNormalImpulse > 0.0f ) { b3SetBit( hitEventBitSet, contact->contactId ); hasHitEvents = true; @@ -774,30 +786,6 @@ void b3StoreImpulses_Mesh( b3SolverBlock block, b3StepContext* context, int work taskContext->hasHitEvents = hasHitEvents; } -#if defined( B3_SIMD_NEON ) - -#include - -// wide float holds 4 numbers -typedef float32x4_t b3FloatW; - -#elif defined( B3_SIMD_SSE2 ) - -#include - -// wide float holds 4 numbers -typedef __m128 b3FloatW; - -#else - -// scalar math -typedef struct b3FloatW -{ - float x, y, z, w; -} b3FloatW; - -#endif - // Wide vec2 typedef struct b3Vec2W { @@ -829,372 +817,6 @@ typedef struct b3SymMatrix3W b3FloatW cxx, cxy, cxz, cyy, cyz, czz; } b3SymMatrix3W; -#if defined( B3_SIMD_NEON ) - -static inline b3FloatW b3ZeroW( void ) -{ - return vdupq_n_f32( 0.0f ); -} - -static inline b3FloatW b3SplatW( float scalar ) -{ - return vdupq_n_f32( scalar ); -} - -static inline b3FloatW b3NegW( b3FloatW a ) -{ - return vnegq_f32( a ); -} - -static inline b3FloatW b3SetW( float a, float b, float c, float d ) -{ - float32_t array[4] = { a, b, c, d }; - return vld1q_f32( array ); -} - -static inline b3FloatW b3AddW( b3FloatW a, b3FloatW b ) -{ - return vaddq_f32( a, b ); -} - -static inline b3FloatW b3SubW( b3FloatW a, b3FloatW b ) -{ - return vsubq_f32( a, b ); -} - -static inline b3FloatW b3MulW( b3FloatW a, b3FloatW b ) -{ - return vmulq_f32( a, b ); -} - -static inline b3FloatW b3DivW( b3FloatW a, b3FloatW b ) -{ - return vdivq_f32( a, b ); -} - -static inline b3FloatW b3SqrtW( b3FloatW a ) -{ - return vsqrtq_f32( a ); -} - -// Cannot use real FMA because it doesn't match the non-SIMD path -static inline b3FloatW b3MulAddW( b3FloatW a, b3FloatW b, b3FloatW c ) -{ - return vaddq_f32( a, vmulq_f32( b, c ) ); -} - -// static inline b3FloatW b3MulSubW( b3FloatW a, b3FloatW b, b3FloatW c ) -//{ -// return vsubq_f32( a, vmulq_f32( b, c ) ); -// } - -static inline b3FloatW b3MinW( b3FloatW a, b3FloatW b ) -{ - return vminq_f32( a, b ); -} - -static inline b3FloatW b3MaxW( b3FloatW a, b3FloatW b ) -{ - return vmaxq_f32( a, b ); -} - -// clamp a to [-b, b] -static inline b3FloatW b3SymClampW( b3FloatW a, b3FloatW b ) -{ - b3FloatW nb = b3NegW( b ); - b3FloatW c = b3MaxW( nb, a ); - return b3MinW( c, b ); -} - -static inline b3FloatW b3OrW( b3FloatW a, b3FloatW b ) -{ - return vreinterpretq_f32_u32( vorrq_u32( vreinterpretq_u32_f32( a ), vreinterpretq_u32_f32( b ) ) ); -} - -static inline b3FloatW b3GreaterThanW( b3FloatW a, b3FloatW b ) -{ - return vreinterpretq_f32_u32( vcgtq_f32( a, b ) ); -} - -static inline b3FloatW b3EqualsW( b3FloatW a, b3FloatW b ) -{ - return vreinterpretq_f32_u32( vceqq_f32( a, b ) ); -} - -static inline bool b3AllZeroW( b3FloatW a ) -{ - // Create a zero vector for comparison - b3FloatW zero = vdupq_n_f32( 0.0f ); - - // Compare the input vector with zero - uint32x4_t cmp_result = vceqq_f32( a, zero ); - -// Check if all comparison results are non-zero using vminvq -#ifdef __ARM_FEATURE_SVE - // ARM v8.2+ has horizontal minimum instruction - return vminvq_u32( cmp_result ) != 0; -#else - // For older ARM architectures, we need to manually check all lanes - return vgetq_lane_u32( cmp_result, 0 ) != 0 && vgetq_lane_u32( cmp_result, 1 ) != 0 && vgetq_lane_u32( cmp_result, 2 ) != 0 && - vgetq_lane_u32( cmp_result, 3 ) != 0; -#endif -} - -// component-wise returns mask ? b : a -static inline b3FloatW b3BlendW( b3FloatW a, b3FloatW b, b3FloatW mask ) -{ - uint32x4_t mask32 = vreinterpretq_u32_f32( mask ); - return vbslq_f32( mask32, b, a ); -} - -#elif defined( B3_SIMD_SSE2 ) - -static inline b3FloatW b3ZeroW( void ) -{ - return _mm_setzero_ps(); -} - -static inline b3FloatW b3SplatW( float scalar ) -{ - return _mm_set1_ps( scalar ); -} - -static inline b3FloatW b3NegW( b3FloatW a ) -{ - // Create a mask with the sign bit set for each element - __m128 mask = _mm_set1_ps( -0.0f ); - - // XOR the input with the mask to negate each element - return _mm_xor_ps( a, mask ); -} - -static inline b3FloatW b3SetW( float a, float b, float c, float d ) -{ - return _mm_setr_ps( a, b, c, d ); -} - -static inline b3FloatW b3AddW( b3FloatW a, b3FloatW b ) -{ - return _mm_add_ps( a, b ); -} - -static inline b3FloatW b3SubW( b3FloatW a, b3FloatW b ) -{ - return _mm_sub_ps( a, b ); -} - -static inline b3FloatW b3MulW( b3FloatW a, b3FloatW b ) -{ - return _mm_mul_ps( a, b ); -} - -static inline b3FloatW b3DivW( b3FloatW a, b3FloatW b ) -{ - return _mm_div_ps( a, b ); -} - -static inline b3FloatW b3SqrtW( b3FloatW a ) -{ - return _mm_sqrt_ps( a ); -} - -static inline b3FloatW b3MulAddW( b3FloatW a, b3FloatW b, b3FloatW c ) -{ - return _mm_add_ps( a, _mm_mul_ps( b, c ) ); -} - -// static inline b3FloatW b3MulSubW( b3FloatW a, b3FloatW b, b3FloatW c ) -//{ -// return _mm_sub_ps( a, _mm_mul_ps( b, c ) ); -// } - -static inline b3FloatW b3MinW( b3FloatW a, b3FloatW b ) -{ - return _mm_min_ps( a, b ); -} - -static inline b3FloatW b3MaxW( b3FloatW a, b3FloatW b ) -{ - return _mm_max_ps( a, b ); -} - -// clamp a to [-b, b] -static inline b3FloatW b3SymClampW( b3FloatW a, b3FloatW b ) -{ - b3FloatW nb = b3NegW( b ); - b3FloatW c = b3MaxW( nb, a ); - return b3MinW( c, b ); -} - -static inline b3FloatW b3OrW( b3FloatW a, b3FloatW b ) -{ - return _mm_or_ps( a, b ); -} - -static inline b3FloatW b3GreaterThanW( b3FloatW a, b3FloatW b ) -{ - return _mm_cmpgt_ps( a, b ); -} - -static inline b3FloatW b3EqualsW( b3FloatW a, b3FloatW b ) -{ - return _mm_cmpeq_ps( a, b ); -} - -static inline bool b3AllZeroW( b3FloatW a ) -{ - // Compare each element with zero - b3FloatW zero = _mm_setzero_ps(); - b3FloatW cmp = _mm_cmpeq_ps( a, zero ); - - // Create a mask from the comparison results - int mask = _mm_movemask_ps( cmp ); - - // If all elements are zero, the mask will be 0xF (1111 in binary) - return mask == 0xF; -} - -// component-wise returns mask ? b : a -static inline b3FloatW b3BlendW( b3FloatW a, b3FloatW b, b3FloatW mask ) -{ - return _mm_or_ps( _mm_and_ps( mask, b ), _mm_andnot_ps( mask, a ) ); -} - -#else - -static inline b3FloatW b3ZeroW( void ) -{ - return (b3FloatW){ 0.0f, 0.0f, 0.0f, 0.0f }; -} - -static inline b3FloatW b3SplatW( float scalar ) -{ - return (b3FloatW){ scalar, scalar, scalar, scalar }; -} - -static inline b3FloatW b3NegW( b3FloatW a ) -{ - return (b3FloatW){ -a.x, -a.y, -a.z, -a.w }; -} - -static inline b3FloatW b3AddW( b3FloatW a, b3FloatW b ) -{ - return (b3FloatW){ a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w }; -} - -static inline b3FloatW b3SubW( b3FloatW a, b3FloatW b ) -{ - return (b3FloatW){ a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w }; -} - -static inline b3FloatW b3MulW( b3FloatW a, b3FloatW b ) -{ - return (b3FloatW){ a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w }; -} - -static inline b3FloatW b3DivW( b3FloatW a, b3FloatW b ) -{ - return (b3FloatW){ a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w }; -} - -static inline b3FloatW b3SqrtW( b3FloatW a ) -{ - return (b3FloatW){ sqrtf( a.x ), sqrtf( a.y ), sqrtf( a.z ), sqrtf( a.w ) }; -} - -static inline b3FloatW b3MulAddW( b3FloatW a, b3FloatW b, b3FloatW c ) -{ - return (b3FloatW){ a.x + b.x * c.x, a.y + b.y * c.y, a.z + b.z * c.z, a.w + b.w * c.w }; -} - -// static inline b3FloatW b3MulSubW( b3FloatW a, b3FloatW b, b3FloatW c ) -//{ -// return { a.x - b.x * c.x, a.y - b.y * c.y, a.z - b.z * c.z, a.w - b.w * c.w }; -// } - -// static inline b3FloatW b3MinW( b3FloatW a, b3FloatW b ) -//{ -// b3FloatW r; -// r.x = a.x <= b.x ? a.x : b.x; -// r.y = a.y <= b.y ? a.y : b.y; -// r.z = a.z <= b.z ? a.z : b.z; -// r.w = a.w <= b.w ? a.w : b.w; -// return r; -// } - -static inline b3FloatW b3MaxW( b3FloatW a, b3FloatW b ) -{ - b3FloatW r; - r.x = a.x >= b.x ? a.x : b.x; - r.y = a.y >= b.y ? a.y : b.y; - r.z = a.z >= b.z ? a.z : b.z; - r.w = a.w >= b.w ? a.w : b.w; - return r; -} - -// clamp a to [-b, b] -static inline b3FloatW b3SymClampW( b3FloatW a, b3FloatW b ) -{ - b3FloatW r; - r.x = a.x <= b.x ? a.x : b.x; - r.y = a.y <= b.y ? a.y : b.y; - r.z = a.z <= b.z ? a.z : b.z; - r.w = a.w <= b.w ? a.w : b.w; - r.x = r.x <= -b.x ? -b.x : r.x; - r.y = r.y <= -b.y ? -b.y : r.y; - r.z = r.z <= -b.z ? -b.z : r.z; - r.w = r.w <= -b.w ? -b.w : r.w; - return r; -} - -static inline b3FloatW b3OrW( b3FloatW a, b3FloatW b ) -{ - b3FloatW r; - r.x = a.x != 0.0f || b.x != 0.0f ? 1.0f : 0.0f; - r.y = a.y != 0.0f || b.y != 0.0f ? 1.0f : 0.0f; - r.z = a.z != 0.0f || b.z != 0.0f ? 1.0f : 0.0f; - r.w = a.w != 0.0f || b.w != 0.0f ? 1.0f : 0.0f; - return r; -} - -static inline b3FloatW b3GreaterThanW( b3FloatW a, b3FloatW b ) -{ - b3FloatW r; - r.x = a.x > b.x ? 1.0f : 0.0f; - r.y = a.y > b.y ? 1.0f : 0.0f; - r.z = a.z > b.z ? 1.0f : 0.0f; - r.w = a.w > b.w ? 1.0f : 0.0f; - return r; -} - -static inline b3FloatW b3EqualsW( b3FloatW a, b3FloatW b ) -{ - b3FloatW r; - r.x = a.x == b.x ? 1.0f : 0.0f; - r.y = a.y == b.y ? 1.0f : 0.0f; - r.z = a.z == b.z ? 1.0f : 0.0f; - r.w = a.w == b.w ? 1.0f : 0.0f; - return r; -} - -static inline bool b3AllZeroW( b3FloatW a ) -{ - return a.x == 0.0f && a.y == 0.0f && a.z == 0.0f && a.w == 0.0f; -} - -// component-wise returns mask ? b : a -static inline b3FloatW b3BlendW( b3FloatW a, b3FloatW b, b3FloatW mask ) -{ - b3FloatW r; - r.x = mask.x != 0.0f ? b.x : a.x; - r.y = mask.y != 0.0f ? b.y : a.y; - r.z = mask.z != 0.0f ? b.z : a.z; - r.w = mask.w != 0.0f ? b.w : a.w; - return r; -} - -#endif - // s * a static inline b3Vec3W b3MulSVW( b3FloatW s, b3Vec3W a ) { @@ -1342,6 +964,8 @@ typedef struct b3ContactConstraintWide int indexA[B3_SIMD_WIDTH]; int indexB[B3_SIMD_WIDTH]; + int pointCounts[B3_SIMD_WIDTH]; + b3FloatW invMassA, invMassB; b3SymMatrix3W invIA, invIB; b3Vec3W normal; @@ -1350,7 +974,8 @@ typedef struct b3ContactConstraintWide b3Vec3W tangent1; b3Vec3W tangent2; - b3Vec3W originA, originB; + // Friction centers + b3Vec3W centerA, centerB; b3FloatW twistMass; b3FloatW twistImpulse; b3SymMatrix2W tangentMass; @@ -1629,6 +1254,7 @@ void b3PrepareContacts_Convex( b3SolverBlock block, b3StepContext* context ) b3Softness staticSoftness = context->staticSoftness; float warmStartScale = world->enableWarmStarting ? 1.0f : 0.0f; + float invTau = 1.0f / B3_SPECULATIVE_DISTANCE; int wideIndex = block.startIndex; int endWideIndex = block.startIndex + block.count; @@ -1664,7 +1290,7 @@ void b3PrepareContacts_Convex( b3SolverBlock block, b3StepContext* context ) } int contactId = contactIds[contactIndex]; - b3Contact* contact = b3Array_Get( world->contacts, contactId ); + b3Contact* contact = b3Array_Get( world->contacts, contactId ); B3_ASSERT( contact->manifoldCount == 1 ); b3Manifold* manifold = contact->manifolds + 0; @@ -1779,8 +1405,11 @@ void b3PrepareContacts_Convex( b3SolverBlock block, b3StepContext* context ) ( (float*)&constraint->impulseScale )[lane] = soft.impulseScale; int pointCount = manifold->pointCount; - b3Vec3 originA = b3Vec3_zero; - b3Vec3 originB = b3Vec3_zero; + constraint->pointCounts[lane] = pointCount; + + b3Vec3 centerA = b3Vec3_zero; + b3Vec3 centerB = b3Vec3_zero; + float totalFrictionWeight = 0.0f; for ( int pointIndex = 0; pointIndex < pointCount; ++pointIndex ) { @@ -1789,8 +1418,14 @@ void b3PrepareContacts_Convex( b3SolverBlock block, b3StepContext* context ) b3Vec3 rA = mp->anchorA; b3Vec3 rB = mp->anchorB; - originA = b3Add( originA, rA ); - originB = b3Add( originB, rB ); + float s = mp->separation; + + // C0 friction center decay. Needed to prevent spinning top drift (GyroscopicPrecession sample). + // See details in b3PrepareContacts_Mesh. This code should stay in sync. + float weight = b3ClampFloat( 2.0f - s * invTau, B3_MIN_FRICTION_WEIGHT, 1.0f ); + centerA = b3MulAdd( centerA, weight, rA ); + centerB = b3MulAdd( centerB, weight, rB ); + totalFrictionWeight += weight; ( (float*)&cp->anchorAs.X )[lane] = rA.x; ( (float*)&cp->anchorAs.Y )[lane] = rA.y; @@ -1800,7 +1435,7 @@ void b3PrepareContacts_Convex( b3SolverBlock block, b3StepContext* context ) ( (float*)&cp->anchorBs.Y )[lane] = rB.y; ( (float*)&cp->anchorBs.Z )[lane] = rB.z; - float baseSeparation = mp->separation - b3Dot( b3Sub( rB, rA ), normal ); + float baseSeparation = s - b3Dot( b3Sub( rB, rA ), normal ); ( (float*)&cp->baseSeparations )[lane] = baseSeparation; ( (float*)&cp->normalImpulses )[lane] = warmStartScale * mp->normalImpulse; @@ -1817,29 +1452,29 @@ void b3PrepareContacts_Convex( b3SolverBlock block, b3StepContext* context ) ( (float*)&cp->relativeVelocities )[lane] = b3Dot( normal, b3Sub( vrB, vrA ) ); } - float invCount = 1.0f / pointCount; - originA = b3MulSV( invCount, originA ); - originB = b3MulSV( invCount, originB ); + float invWeight = 1.0f / totalFrictionWeight; + centerA = b3MulSV( invWeight, centerA ); + centerB = b3MulSV( invWeight, centerB ); - ( (float*)&constraint->originA.X )[lane] = originA.x; - ( (float*)&constraint->originA.Y )[lane] = originA.y; - ( (float*)&constraint->originA.Z )[lane] = originA.z; - ( (float*)&constraint->originB.X )[lane] = originB.x; - ( (float*)&constraint->originB.Y )[lane] = originB.y; - ( (float*)&constraint->originB.Z )[lane] = originB.z; + ( (float*)&constraint->centerA.X )[lane] = centerA.x; + ( (float*)&constraint->centerA.Y )[lane] = centerA.y; + ( (float*)&constraint->centerA.Z )[lane] = centerA.z; + ( (float*)&constraint->centerB.X )[lane] = centerB.x; + ( (float*)&constraint->centerB.Y )[lane] = centerB.y; + ( (float*)&constraint->centerB.Z )[lane] = centerB.z; for ( int pointIndex = 0; pointIndex < pointCount; ++pointIndex ) { const b3ManifoldPoint* mp = manifold->points + pointIndex; b3ContactConstraintPointWide* cp = constraint->points + pointIndex; - ( (float*)&cp->leverArms )[lane] = b3Distance( mp->anchorA, originA ); + ( (float*)&cp->leverArms )[lane] = b3Distance( mp->anchorA, centerA ); } - b3Vec3 rtA1 = b3Cross( originA, tangent1 ); - b3Vec3 rtA2 = b3Cross( originA, tangent2 ); + b3Vec3 rtA1 = b3Cross( centerA, tangent1 ); + b3Vec3 rtA2 = b3Cross( centerA, tangent2 ); - b3Vec3 rtB1 = b3Cross( originB, tangent1 ); - b3Vec3 rtB2 = b3Cross( originB, tangent2 ); + b3Vec3 rtB1 = b3Cross( centerB, tangent1 ); + b3Vec3 rtB2 = b3Cross( centerB, tangent2 ); { b3Matrix2 k; @@ -1919,8 +1554,14 @@ void b3WarmStartContacts_Convex( b3SolverBlock block, b3StepContext* context ) b3BodyStateW bA = b3GatherBodies( states, c->indexA ); b3BodyStateW bB = b3GatherBodies( states, c->indexB ); + _Static_assert( B3_SIMD_WIDTH == 4, "width" ); + int pointCount1 = b3MaxInt( c->pointCounts[0], c->pointCounts[1] ); + int pointCount2 = b3MaxInt( c->pointCounts[2], c->pointCounts[3] ); + int pointCount = b3MaxInt( pointCount1, pointCount2 ); + B3_VALIDATE( 0 < pointCount && pointCount <= B3_MAX_MANIFOLD_POINTS ); + // Normal impulses - for ( int j = 0; j < B3_MAX_MANIFOLD_POINTS; ++j ) + for ( int j = 0; j < pointCount; ++j ) { b3ContactConstraintPointWide* cp = c->points + j; @@ -1940,8 +1581,8 @@ void b3WarmStartContacts_Convex( b3SolverBlock block, b3StepContext* context ) // Central friction { - b3Vec3W rA = c->originA; - b3Vec3W rB = c->originB; + b3Vec3W rA = c->centerA; + b3Vec3W rB = c->centerB; b3Vec3W impulse = b3MulSVW( c->frictionImpulse.x, c->tangent1 ); impulse = b3MulAddSVW( impulse, c->frictionImpulse.y, c->tangent2 ); @@ -1987,6 +1628,12 @@ void b3SolveContacts_Convex( b3SolverBlock block, b3StepContext* context, bool u { b3ContactConstraintWide* c = constraints + wideIndex; + _Static_assert( B3_SIMD_WIDTH == 4, "width" ); + int pointCount1 = b3MaxInt( c->pointCounts[0], c->pointCounts[1] ); + int pointCount2 = b3MaxInt( c->pointCounts[2], c->pointCounts[3] ); + int pointCount = b3MaxInt( pointCount1, pointCount2 ); + B3_VALIDATE( 0 < pointCount && pointCount <= B3_MAX_MANIFOLD_POINTS ); + b3BodyStateW bA = b3GatherBodies( states, c->indexA ); b3BodyStateW bB = b3GatherBodies( states, c->indexB ); @@ -2009,8 +1656,7 @@ void b3SolveContacts_Convex( b3SolverBlock block, b3StepContext* context, bool u b3FloatW totalNormalImpulse = b3ZeroW(); b3FloatW totalTwistLimit = b3ZeroW(); - // todo_erin use the max point count of the four manifolds - for ( int pointIndex = 0; pointIndex < B3_MAX_MANIFOLD_POINTS; ++pointIndex ) + for ( int pointIndex = 0; pointIndex < pointCount; ++pointIndex ) { b3ContactConstraintPointWide* cp = c->points + pointIndex; @@ -2120,8 +1766,8 @@ void b3SolveContacts_Convex( b3SolverBlock block, b3StepContext* context, bool u b3Vec3W tangent2 = c->tangent2; // Fixed anchor points for applying impulses - b3Vec3W rA = c->originA; - b3Vec3W rB = c->originB; + b3Vec3W rA = c->centerA; + b3Vec3W rB = c->centerB; // Relative tangent velocity at contact b3Vec3W vrA = b3AddVW( bA.v, b3CrossW( bA.w, rA ) ); @@ -2197,6 +1843,12 @@ void b3ApplyRestitution_Convex( b3SolverBlock block, b3StepContext* context ) continue; } + _Static_assert( B3_SIMD_WIDTH == 4, "width" ); + int pointCount1 = b3MaxInt( c->pointCounts[0], c->pointCounts[1] ); + int pointCount2 = b3MaxInt( c->pointCounts[2], c->pointCounts[3] ); + int pointCount = b3MaxInt( pointCount1, pointCount2 ); + B3_VALIDATE( 0 < pointCount && pointCount <= B3_MAX_MANIFOLD_POINTS ); + // Single gather for all manifolds b3BodyStateW bA = b3GatherBodies( states, c->indexA ); b3BodyStateW bB = b3GatherBodies( states, c->indexB ); @@ -2205,7 +1857,7 @@ void b3ApplyRestitution_Convex( b3SolverBlock block, b3StepContext* context ) // by the calculations below. b3FloatW restitutionMask = b3EqualsW( c->restitution, zero ); - for ( int pointIndex = 0; pointIndex < B3_MAX_MANIFOLD_POINTS; ++pointIndex ) + for ( int pointIndex = 0; pointIndex < pointCount; ++pointIndex ) { b3ContactConstraintPointWide* cp = c->points + pointIndex; @@ -2372,7 +2024,7 @@ void b3PrepareContacts_Overflow( b3StepContext* context ) b3GraphColor* color = graph->colors + B3_OVERFLOW_INDEX; uint16_t count = (uint16_t)color->contacts.count; - if (count == 0) + if ( count == 0 ) { return; } diff --git a/vendor/box3d/src/src/contact_solver.h b/vendor/box3d/src/src/contact_solver.h index fab17ee53..d85f2331a 100644 --- a/vendor/box3d/src/src/contact_solver.h +++ b/vendor/box3d/src/src/contact_solver.h @@ -19,13 +19,13 @@ typedef struct b3ManifoldConstraintPoint typedef struct b3ManifoldConstraint { - // todo use pointer buffer b3ManifoldConstraintPoint points[4]; int pointCount; b3Vec3 normal; b3Vec3 tangent1; b3Vec3 tangent2; - b3Vec3 originA, originB; + // Friction centers + b3Vec3 centerA, centerB; float twistMass; float twistImpulse; b3Matrix2 tangentMass; diff --git a/vendor/box3d/src/src/convex_manifold.c b/vendor/box3d/src/src/convex_manifold.c index 2422567c4..d3b998ae5 100644 --- a/vendor/box3d/src/src/convex_manifold.c +++ b/vendor/box3d/src/src/convex_manifold.c @@ -1,9 +1,10 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2026 Erin Catto // SPDX-License-Identifier: MIT #include "algorithm.h" #include "manifold.h" #include "shape.h" +#include "simd.h" #include "box3d/base.h" #include "box3d/collision.h" @@ -12,31 +13,6 @@ #include #include -static inline bool b3IsMinkowskiFaceIsolated( b3Vec3 a, b3Vec3 b, b3Vec3 n ) -{ - // An isolated edge (e.g. like in a capsule) defines a circle through the - // origin on the Gauss map. So testing for overlap between this circle and - // the arc AB simplifies to a simple plane test. - float an = b3Dot( a, n ); - float bn = b3Dot( b, n ); - - return an * bn <= 0.0f; -} - -// bxa = cross(b, a) and dxc = cross(d, c) -// but in practice we use the edge vector between the faces for robustness -static inline bool b3IsMinkowskiFace( b3Vec3 a, b3Vec3 b, b3Vec3 bxa, b3Vec3 c, b3Vec3 d, b3Vec3 dxc ) -{ - // Two edges build a face on the Minkowski sum if the associated arcs ab and cd intersect on the Gauss map. - // The associated arcs are defined by the adjacent face normals of each edge. - float cba = b3Dot( c, bxa ); - float dba = b3Dot( d, bxa ); - float adc = b3Dot( a, dxc ); - float bdc = b3Dot( b, dxc ); - - return cba * dba < 0.0f && adc * bdc < 0.0f && cba * bdc > 0.0f; -} - static int b3ClipSegment( b3ClipVertex segment[2], b3Plane plane ) { int vertexCount = 0; @@ -106,8 +82,8 @@ static int b3ClipSegmentToHullFace( b3ClipVertex segment[2], const b3HullData* h return 2; } -static b3FaceQuery b3QueryFaceDirectionHullAndCapsule( const b3HullData* hull, const b3Capsule* capsule, - b3Transform capsuleTransform ) +static b3SeparatingAxis b3QueryFaceDirectionHullAndCapsule( const b3HullData* hull, const b3Capsule* capsule, + b3Transform capsuleTransform ) { int maxFaceIndex = -1; int maxVertexIndex = -1; @@ -134,62 +110,32 @@ static b3FaceQuery b3QueryFaceDirectionHullAndCapsule( const b3HullData* hull, c } } - return (b3FaceQuery){ + return (b3SeparatingAxis){ + .normal = planes[maxFaceIndex].normal, .separation = maxFaceSeparation, - .faceIndex = (uint8_t)maxFaceIndex, - .vertexIndex = (uint8_t)maxVertexIndex, + .indexA = (uint8_t)maxFaceIndex, + .indexB = (uint8_t)maxVertexIndex, }; } -static b3FaceQuery b3QueryFaceDirections( const b3HullData* hullA, const b3HullData* hullB, b3Transform relativeTransform ) -{ - // We perform all computations in local space of the second hull - b3Transform transform = b3InvertTransform( relativeTransform ); - const b3Plane* planesA = b3GetHullPlanes( hullA ); - const b3Vec3* pointsB = b3GetHullPoints( hullB ); - - int maxFaceIndex = -1; - int maxVertexIndex = -1; - float maxFaceSeparation = -FLT_MAX; - - for ( int faceIndex = 0; faceIndex < hullA->faceCount; ++faceIndex ) - { - b3Plane plane = b3TransformPlane( transform, planesA[faceIndex] ); - - int vertexIndex = b3FindHullSupportVertex( hullB, b3Neg( plane.normal ) ); - b3Vec3 support = pointsB[vertexIndex]; - float separation = b3PlaneSeparation( plane, support ); - if ( separation > maxFaceSeparation ) - { - maxFaceIndex = faceIndex; - maxVertexIndex = vertexIndex; - maxFaceSeparation = separation; - } - } - - return (b3FaceQuery){ - .separation = maxFaceSeparation, - .faceIndex = (uint8_t)maxFaceIndex, - .vertexIndex = (uint8_t)maxVertexIndex, - }; -} - -static b3EdgeQuery b3QueryEdgeDirectionHullAndCapsule( const b3HullData* hull, const b3Capsule* capsule, - b3Transform capsuleTransform ) +static b3SeparatingAxis b3QueryEdgeDirectionHullAndCapsule( const b3HullData* hull, const b3Capsule* capsule, + b3Transform capsuleTransform ) { // Find axis of minimum penetration + b3Vec3 maxNormal = b3Vec3_zero; float maxSeparation = -FLT_MAX; - int maxIndex1 = -1; - int maxIndex2 = -1; + int maxIndexA = B3_NULL_INDEX; + int maxIndexB = B3_NULL_INDEX; // We perform all computations in local space of the hull - b3Vec3 p1 = b3TransformPoint( capsuleTransform, capsule->center1 ); - b3Vec3 q1 = b3TransformPoint( capsuleTransform, capsule->center2 ); - b3Vec3 e1 = b3Sub( q1, p1 ); + b3Vec3 pA = b3TransformPoint( capsuleTransform, capsule->center1 ); + b3Vec3 qA = b3TransformPoint( capsuleTransform, capsule->center2 ); + b3Vec3 eA = b3Sub( qA, pA ); const b3HullHalfEdge* edges = b3GetHullEdges( hull ); const b3Vec3* points = b3GetHullPoints( hull ); const b3Plane* planes = b3GetHullPlanes( hull ); + float squaredTolerance = B3_PARALLEL_EDGE_TOL * B3_PARALLEL_EDGE_TOL; for ( int index = 0; index < hull->edgeCount; index += 2 ) { @@ -197,111 +143,60 @@ static b3EdgeQuery b3QueryEdgeDirectionHullAndCapsule( const b3HullData* hull, c const b3HullHalfEdge* twin = edges + index + 1; B3_ASSERT( edge->twin == index + 1 && twin->twin == index ); - b3Vec3 p2 = points[edge->origin]; - b3Vec3 q2 = points[twin->origin]; - b3Vec3 e2 = b3Sub( q2, p2 ); + b3Vec3 qB = points[twin->origin]; + b3Vec3 uB = planes[edge->face].normal; + b3Vec3 vB = planes[twin->face].normal; - b3Vec3 u2 = planes[edge->face].normal; - b3Vec3 v2 = planes[twin->face].normal; + // An isolated edge (e.g. like in a capsule) defines a circle through the + // origin on the Gauss map. So testing for overlap between this circle and + // the arc AB simplifies to a plane test. + float cba = b3Dot( uB, eA ); + float dba = b3Dot( vB, eA ); - if ( b3IsMinkowskiFaceIsolated( u2, v2, e1 ) ) + if ( cba * dba < 0.0f ) { - // We can pass any point on the edge and choose - // the edge centers for better numerical precision. - b3Vec3 c1 = b3MulSV( 0.5f, b3Add( q1, p1 ) ); - b3Vec3 c2 = hull->center; - float separation = b3EdgeEdgeSeparation( q1, e1, c1, q2, e2, c2 ); + // Avoid nearly parallel edges that may lead to invalid separation values at the noise floor. + if ( b3MaxFloat( cba * cba, dba * dba ) < squaredTolerance * b3LengthSquared( eA ) ) + { + continue; + } + + // The intersection of the arcs on the Gauss map is the edge pair axis. Cast the + // arc of hull B (from uB to vB) against the plane containing the arc of hull A: + // dot(uB + t * (vB - uB), eA) == 0 + // then + // t = cba / (cba - dba) + // + // The signs of cba and dba differ (Minkowski test), so the division is safe. + // + // The axis generated points from B to A by construction since it lands between + // two face normals on B. This removes the need to orient the separation axis + // using the hull centers. + // + // The axis is perpendicular to both edges so I can use qA and qB as arbitrary + // points on edgeA and edgeB to measure the separation. + + float t = cba / ( cba - dba ); + b3Vec3 axis = b3Lerp( uB, vB, t ); + B3_VALIDATE( b3LengthSquared( axis ) > 1000.0f * FLT_MIN ); + axis = b3Normalize( axis ); + float separation = b3Dot( axis, b3Sub( qA, qB ) ); + if ( separation > maxSeparation ) { // Note: We don't exit early if we find a separating axis here since we want to // find the best one for caching and account for the convex radius later. + maxNormal = axis; maxSeparation = separation; - maxIndex1 = 0; - maxIndex2 = index; + maxIndexA = 0; + maxIndexB = index; } } } // Save result - return (b3EdgeQuery){ - .separation = maxSeparation, - .indexA = (uint8_t)maxIndex1, - .indexB = (uint8_t)maxIndex2, - }; -} - -static b3EdgeQuery b3QueryEdgeDirections( const b3HullData* hullA, const b3HullData* hullB, b3Transform transformBtoA ) -{ - // Find axis of minimum penetration - float maxSeparation = -FLT_MAX; - int maxIndexA = B3_NULL_INDEX; - int maxIndexB = B3_NULL_INDEX; - - const b3HullHalfEdge* edgesA = b3GetHullEdges( hullA ); - const b3Vec3* pointsA = b3GetHullPoints( hullA ); - const b3Plane* planesA = b3GetHullPlanes( hullA ); - const b3HullHalfEdge* edgesB = b3GetHullEdges( hullB ); - const b3Vec3* pointsB = b3GetHullPoints( hullB ); - const b3Plane* planesB = b3GetHullPlanes( hullB ); - - // Work in frame A - b3Matrix3 matrix = b3MakeMatrixFromQuat( transformBtoA.q ); - - // Arranged to minimize transform operations - for ( int indexB = 0; indexB < hullB->edgeCount; indexB += 2 ) - { - const b3HullHalfEdge* edgeB = edgesB + indexB; - const b3HullHalfEdge* twinB = edgesB + indexB + 1; - B3_ASSERT( edgeB->twin == indexB + 1 && twinB->twin == indexB ); - - b3Vec3 qB = pointsB[twinB->origin]; - b3Vec3 eB = b3MulMV( matrix, b3Sub( qB, pointsB[edgeB->origin] ) ); - qB = b3Add( b3MulMV( matrix, qB ), transformBtoA.p ); - - b3Vec3 uB = b3MulMV( matrix, planesB[edgeB->face].normal ); - b3Vec3 vB = b3MulMV( matrix, planesB[twinB->face].normal ); - - for ( int indexA = 0; indexA < hullA->edgeCount; indexA += 2 ) - { - const b3HullHalfEdge* edgeA = edgesA + indexA; - const b3HullHalfEdge* twinA = edgesA + indexA + 1; - B3_ASSERT( edgeA->twin == indexA + 1 && twinA->twin == indexA ); - - b3Vec3 qA = pointsA[twinA->origin]; - b3Vec3 eA = b3Sub( qA, pointsA[edgeA->origin] ); - b3Vec3 uA = planesA[edgeA->face].normal; - b3Vec3 vA = planesA[twinA->face].normal; - - bool isMinkowski; - { - // Two edges build a face on the Minkowski sum if the associated arcs AB and CD intersect on the Gauss map. - // The associated arcs are defined by the adjacent face normals of each edge. - float cba = b3Dot( uB, eA ); - float dba = b3Dot( vB, eA ); - float adc = -b3Dot( uA, eB ); - float bdc = -b3Dot( vA, eB ); - - isMinkowski = cba * dba < 0.0f && adc * bdc < 0.0f && cba * bdc > 0.0f; - } - - if ( isMinkowski ) - { - b3Vec3 centerA = hullA->center; - b3Vec3 centerB = b3TransformPoint( transformBtoA, hullB->center ); - float separation = b3EdgeEdgeSeparation( qA, eA, centerA, qB, eB, centerB ); - - if ( separation > maxSeparation ) - { - // Continues to find the maximum separating axis - maxSeparation = separation; - maxIndexA = indexA; - maxIndexB = indexB; - } - } - } - } - - return (b3EdgeQuery){ + return (b3SeparatingAxis){ + .normal = maxNormal, .separation = maxSeparation, .indexA = maxIndexA, .indexB = maxIndexB, @@ -810,13 +705,13 @@ void b3CollideCapsules( b3LocalManifold* manifold, int capacity, const b3Capsule } static bool b3BuildHullFaceAndCapsuleContact( b3LocalManifold* manifold, const b3HullData* hullA, const b3Capsule* capsuleB, - b3Transform transformBtoA, b3FaceQuery query ) + b3Transform transformBtoA, b3SeparatingAxis query ) { // Work in shapeA coordinates const b3Plane* planes = b3GetHullPlanes( hullA ); // Clip the capsule edge against the side planes of the reference face - int refFace = query.faceIndex; + int refFace = query.indexA; b3Plane refPlane = planes[refFace]; b3ClipVertex segmentB[2]; @@ -863,21 +758,8 @@ static bool b3BuildHullFaceAndCapsuleContact( b3LocalManifold* manifold, const b return false; } -static inline float b3DeepestPointSeparation( const b3LocalManifold* manifold ) -{ - // Deepest point - float minSeparation = FLT_MAX; - int pointCount = manifold->pointCount; - for ( int i = 0; i < pointCount; ++i ) - { - minSeparation = b3MinFloat( minSeparation, manifold->points[i].separation ); - } - - return minSeparation; -} - static bool b3BuildHullAndCapsuleEdgeContact( b3LocalManifold* manifold, int capacity, const b3HullData* hullA, - const b3Capsule* capsuleB, b3Transform transformBtoA, b3EdgeQuery query ) + const b3Capsule* capsuleB, b3Transform transformBtoA, b3SeparatingAxis query ) { if ( capacity < 1 ) { @@ -895,19 +777,11 @@ static bool b3BuildHullAndCapsuleEdgeContact( b3LocalManifold* manifold, int cap const b3HullHalfEdge* edge2 = edges + query.indexB; const b3HullHalfEdge* twin2 = edges + edge2->twin; - b3Vec3 ch = hullA->center; b3Vec3 ph = points[edge2->origin]; b3Vec3 qh = points[twin2->origin]; b3Vec3 eh = b3Sub( qh, ph ); - b3Vec3 normal = b3Cross( ec, eh ); - normal = b3Normalize( normal ); - - // Normal should point outward from hull - if ( b3Dot( normal, b3Sub( ph, ch ) ) < 0.0f ) - { - normal = b3Neg( normal ); - } + b3Vec3 normal = query.normal; b3SegmentDistanceResult result = b3LineDistance( ph, eh, pc, ec ); @@ -918,7 +792,6 @@ static bool b3BuildHullAndCapsuleEdgeContact( b3LocalManifold* manifold, int cap } b3Vec3 point = b3MulSV( 0.5f, b3Add( b3MulSub( result.point1, capsuleB->radius, normal ), result.point2 ) ); - float separation = b3Dot( normal, b3Sub( result.point2, result.point1 ) ); B3_VALIDATE( b3AbsFloat( separation - query.separation ) < B3_LINEAR_SLOP ); @@ -1031,14 +904,14 @@ void b3CollideHullAndCapsule( b3LocalManifold* manifold, int capacity, const b3H // Deep penetration - b3FaceQuery faceQuery = b3QueryFaceDirectionHullAndCapsule( hullA, capsuleB, transformBtoA ); + b3SeparatingAxis faceQuery = b3QueryFaceDirectionHullAndCapsule( hullA, capsuleB, transformBtoA ); if ( faceQuery.separation > capsuleB->radius ) { // We found a separating axis return; } - b3EdgeQuery edgeQuery = b3QueryEdgeDirectionHullAndCapsule( hullA, capsuleB, transformBtoA ); + b3SeparatingAxis edgeQuery = b3QueryEdgeDirectionHullAndCapsule( hullA, capsuleB, transformBtoA ); if ( edgeQuery.separation > capsuleB->radius ) { // We found a separating axis @@ -1048,21 +921,24 @@ void b3CollideHullAndCapsule( b3LocalManifold* manifold, int capacity, const b3H // Create face contact float faceSeparation = faceQuery.separation - capsuleB->radius; b3BuildHullFaceAndCapsuleContact( manifold, hullA, capsuleB, transformBtoA, faceQuery ); - if ( manifold->pointCount > 1 ) + B3_VALIDATE( manifold->pointCount == 0 || manifold->pointCount == 2 ); + if ( manifold->pointCount == 2 ) { - // If ( Out.PointCount <= 1 ) -> Compare with unclipped separation - // If ( Out.PointCount > 1 ) -> Be aggressive and compare with clipped separation - // Face contact can be empty if it does not realize the axis of minimum penetration - faceSeparation = b3DeepestPointSeparation( manifold ); + // This becomes the clipped separation. + faceSeparation = b3MinFloat( manifold->points[0].separation, manifold->points[1].separation ); + } + + // Is there a valid edge-edge axis? + if ( edgeQuery.indexA == B3_NULL_INDEX ) + { + return; } - B3_VALIDATE( faceSeparation <= 0.0f ); // Face contact can be empty if it does not realize the axis of minimum penetration. // Create edge contact if face contact fails or edge contact is significantly better! - const float kRelEdgeTolerance = 0.90f; - const float kAbsTolerance = 0.5f * B3_LINEAR_SLOP; + float linearSlop = B3_LINEAR_SLOP; float edgeSeparation = edgeQuery.separation - capsuleB->radius; - if ( manifold->pointCount == 0 || edgeSeparation > kRelEdgeTolerance * faceSeparation + kAbsTolerance ) + if ( manifold->pointCount == 0 || edgeSeparation > faceSeparation + linearSlop ) { // Edge contact b3BuildHullAndCapsuleEdgeContact( manifold, capacity, hullA, capsuleB, transformBtoA, edgeQuery ); @@ -1108,20 +984,24 @@ static int b3BuildPolygon( b3ClipVertex* out, b3Transform transform, const b3Hul } static bool b3BuildFaceAContact( b3LocalManifold* manifold, int capacity, const b3HullData* hullA, const b3HullData* hullB, - b3Transform transformBtoA, b3FaceQuery query, b3SATCache* cache ) + b3Transform transformBtoA, b3SeparatingAxis query, b3SATCache* cache ) { + B3_VALIDATE( query.type == b3_faceAxisA ); + B3_VALIDATE( 0 <= query.indexA && query.indexA < hullA->faceCount ); + B3_VALIDATE( 0 <= query.indexB && query.indexB < hullB->vertexCount ); + const b3HullFace* facesA = b3GetHullFaces( hullA ); const b3HullHalfEdge* edgesA = b3GetHullEdges( hullA ); const b3Plane* planesA = b3GetHullPlanes( hullA ); const b3Vec3* pointsA = b3GetHullPoints( hullA ); // Reference face - int refFace = query.faceIndex; + int refFace = query.indexA; b3Plane refPlane = planesA[refFace]; // Find incident face b3Vec3 refNormalInB = b3InvRotateVector( transformBtoA.q, refPlane.normal ); - int incFace = b3FindIncidentFace( hullB, refNormalInB, query.vertexIndex ); + int incFace = b3FindIncidentFace( hullB, refNormalInB, query.indexB ); // Build clip polygon from incident face in frame A b3ClipVertex buffer1[B3_MAX_CLIP_POINTS], buffer2[B3_MAX_CLIP_POINTS]; @@ -1198,19 +1078,30 @@ static bool b3BuildFaceAContact( b3LocalManifold* manifold, int capacity, const // Save cache cache->separation = minSeparation; cache->type = (uint8_t)b3_faceAxisA; - cache->indexA = (uint8_t)query.faceIndex; - cache->indexB = (uint8_t)query.vertexIndex; + cache->indexA = (uint8_t)query.indexA; + cache->indexB = (uint8_t)query.indexB; return true; } static bool b3BuildFaceBContact( b3LocalManifold* manifold, int capacity, const b3HullData* hullA, const b3HullData* hullB, - b3Transform transformBtoA, b3FaceQuery query, b3SATCache* cache ) + b3Transform transformBtoA, b3SeparatingAxis query, b3SATCache* cache ) { + B3_VALIDATE( query.type == b3_faceAxisB ); + b3Transform transformAtoB = b3InvertTransform( transformBtoA ); - bool touching = b3BuildFaceAContact( manifold, capacity, hullB, hullA, transformAtoB, query, cache ); + b3SeparatingAxis flippedQuery = { + .normal = b3Neg( query.normal ), + .separation = query.separation, + .indexA = query.indexB, + .indexB = query.indexA, + .type = b3_faceAxisA, + }; + + bool touching = b3BuildFaceAContact( manifold, capacity, hullB, hullA, transformAtoB, flippedQuery, cache ); if ( touching == false ) { + *cache = (b3SATCache){ 0 }; return false; } @@ -1219,9 +1110,6 @@ static bool b3BuildFaceBContact( b3LocalManifold* manifold, int capacity, const // Transform and flip normal so it points from A to B, even though the B has the reference face. manifold->normal = b3Neg( b3MulMV( matrix, manifold->normal ) ); - cache->type = (uint8_t)b3_faceAxisB; - cache->indexA = (uint8_t)query.vertexIndex; - cache->indexB = (uint8_t)query.faceIndex; // Transform points from frame B to frame A. // Also flip the pairs to ensure correct matches. @@ -1232,12 +1120,20 @@ static bool b3BuildFaceBContact( b3LocalManifold* manifold, int capacity, const pt->pair = b3FlipPair( pt->pair ); } + cache->type = (uint8_t)b3_faceAxisB; + cache->indexA = (uint8_t)query.indexA; + cache->indexB = (uint8_t)query.indexB; + return true; } -static bool b3BuildEdgeContact( b3LocalManifold* manifold, const b3HullData* hullA, const b3HullData* hullB, b3Transform transformBtoA, - b3EdgeQuery query, b3SATCache* cache ) +static bool b3BuildEdgeContact( b3LocalManifold* manifold, const b3HullData* hullA, const b3HullData* hullB, + b3Transform transformBtoA, b3SeparatingAxis query, b3SATCache* cache ) { + B3_VALIDATE( query.type == b3_edgePairAxis ); + B3_VALIDATE( 0 <= query.indexA && query.indexA < hullA->edgeCount ); + B3_VALIDATE( 0 <= query.indexB && query.indexB < hullB->edgeCount ); + // Work in shapeA coordinates const b3HullHalfEdge* edgesA = b3GetHullEdges( hullA ); const b3Vec3* pointsA = b3GetHullPoints( hullA ); @@ -1249,7 +1145,6 @@ static bool b3BuildEdgeContact( b3LocalManifold* manifold, const b3HullData* hul const b3HullHalfEdge* edgeA = edgesA + query.indexA; const b3HullHalfEdge* twinA = edgesA + edgeA->twin; - b3Vec3 centerA = hullA->center; b3Vec3 pA = pointsA[edgeA->origin]; b3Vec3 qA = pointsA[twinA->origin]; b3Vec3 eA = b3Sub( qA, pA ); @@ -1260,14 +1155,7 @@ static bool b3BuildEdgeContact( b3LocalManifold* manifold, const b3HullData* hul b3Vec3 qB = b3TransformPoint( transformBtoA, pointsB[twinB->origin] ); b3Vec3 eB = b3Sub( qB, pB ); - b3Vec3 normal = b3Cross( eA, eB ); - normal = b3Normalize( normal ); - - if ( b3Dot( normal, b3Sub( pA, centerA ) ) < 0.0f ) - { - normal = b3Neg( normal ); - } - + b3Vec3 normal = query.normal; b3SegmentDistanceResult result = b3LineDistance( pA, eA, pB, eB ); if ( b3IsWithinSegments( &result ) == false ) @@ -1278,10 +1166,6 @@ static bool b3BuildEdgeContact( b3LocalManifold* manifold, const b3HullData* hul // This can slide off the end from caching float separation = b3Dot( normal, b3Sub( result.point2, result.point1 ) ); - - // todo I suspect this could trip if the cache becomes invalid - // B3_VALIDATE( b3AbsFloat( separation - query.separation ) < B3_LINEAR_SLOP ); - b3Vec3 point = b3MulSV( 0.5f, b3Add( result.point1, result.point2 ) ); // Result in frame A @@ -1302,8 +1186,1101 @@ static bool b3BuildEdgeContact( b3LocalManifold* manifold, const b3HullData* hul return true; } -void b3CollideHulls( b3LocalManifold* manifold, int capacity, const b3HullData* hullA, const b3HullData* hullB, b3Transform transformBtoA, - b3SATCache* cache ) +// Transform a SoA point/normal stream (already split into X/Y/Z) by out = -(R*v (+t)). +// The inputs come straight from the hull's stored SoA arrays, so there's no transpose here. +// IsPoint is a template arg so the translation add is only emitted for points. +static inline void b3NegativeTransformFromSoA( b3Matrix3* R, b3Vec3 p, const float* inX, const float* inY, const float* inZ, + int n, float* outX, float* outY, float* outZ, bool isPoint ) +{ + B3_VALIDATE( ( (uintptr_t)outX & 0xF ) == 0 ); + B3_VALIDATE( ( (uintptr_t)outY & 0xF ) == 0 ); + B3_VALIDATE( ( (uintptr_t)outZ & 0xF ) == 0 ); + + // row-column + b3FloatW r00 = b3SplatW( R->cx.x ); + b3FloatW r01 = b3SplatW( R->cy.x ); + b3FloatW r02 = b3SplatW( R->cz.x ); + b3FloatW r10 = b3SplatW( R->cx.y ); + b3FloatW r11 = b3SplatW( R->cy.y ); + b3FloatW r12 = b3SplatW( R->cz.y ); + b3FloatW r20 = b3SplatW( R->cx.z ); + b3FloatW r21 = b3SplatW( R->cy.z ); + b3FloatW r22 = b3SplatW( R->cz.z ); + + b3FloatW tx = b3ZeroW(); + b3FloatW ty = b3ZeroW(); + b3FloatW tz = b3ZeroW(); + + if ( isPoint ) + { + tx = b3SplatW( p.x ); + ty = b3SplatW( p.y ); + tz = b3SplatW( p.z ); + } + + for ( int i = 0; i < n; i += 4 ) + { + b3FloatW x = b3LoadW( inX + i ); + b3FloatW y = b3LoadW( inY + i ); + b3FloatW z = b3LoadW( inZ + i ); + + // Rotate four vectors at a time + b3FloatW ox = b3Dot3W( r00, r01, r02, x, y, z ); + b3FloatW oy = b3Dot3W( r10, r11, r12, x, y, z ); + b3FloatW oz = b3Dot3W( r20, r21, r22, x, y, z ); + + if ( isPoint ) + { + ox = b3AddW( ox, tx ); + oy = b3AddW( oy, ty ); + oz = b3AddW( oz, tz ); + } + + b3StoreW( outX + i, b3NegW( ox ) ); + b3StoreW( outY + i, b3NegW( oy ) ); + b3StoreW( outZ + i, b3NegW( oz ) ); + } +} + +_Static_assert( B3_MAX_HULL_VERTICES == 128, "must be 128" ); + +#define B3_HULL_BIT_COUNT 7 + +// SIMD support point calculation using a SoA vertex array padded with repeats of the first vertex +// to a multiple of 4. +// +// This minimizes (bias - dot), where the caller is expected to provide a bias that makes this always positive. +// It can be direction dependent. The bias should be just big enough to ensure the value if positive because +// an excessive bias causes a precision loss in the support calculation. +// +// The vertex index is embedded in the low B3_HULL_BIT_COUNT mantissa bits of the value. By minimizing a value that +// is always positive, the minimum carries the smallest index so that padded SoA values will never win. This is +// purpose of using the bias instead of maximizing the dot directly. +// +// The support is then recomputed exactly as dot(normal, vertex), without the embedded index. +// todo consider using this for GJK +static inline void b3GetSupportWide( b3Vec3 normal, const float* vx, const float* vy, const float* vz, int n, float bias, + float* support, int* vertexIndex ) +{ + const b3FloatW nx = b3SplatW( normal.x ); + const b3FloatW ny = b3SplatW( normal.y ); + const b3FloatW nz = b3SplatW( normal.z ); + const b3FloatW biasV = b3SplatW( bias ); + + // Start the minimum at a large value. + b3FloatW minValue = b3SplatW( B3_HUGE ); + + // Tail lanes hold vertex 0 with index bits >= vertexCount, so they never become the min value. + for ( int i = 0; i < n; i += 4 ) + { + b3FloatW x = b3LoadW( vx + i ); + b3FloatW y = b3LoadW( vy + i ); + b3FloatW z = b3LoadW( vz + i ); + b3FloatW d = b3AddW( b3MulW( nz, z ), b3AddW( b3MulW( ny, y ), b3MulW( nx, x ) ) ); + + // This is always positive. + b3FloatW value = b3SubW( biasV, d ); + b3FloatW augmentedValue = b3EmbedIndexW( value, i, B3_HULL_BIT_COUNT ); + minValue = b3MinW( minValue, augmentedValue ); + } + + // One horizontal min, the winning lane's value and index bits ride through. + int vi = b3MinIndexW( minValue, B3_HULL_BIT_COUNT ); + + // Exact support for the chosen vertex. + *vertexIndex = vi; + + // Dot product + *support = normal.x * vx[vi] + normal.y * vy[vi] + normal.z * vz[vi]; +} + +#define NE ( B3_MAX_HULL_EDGES + 4 ) +#define NF ( B3_MAX_HULL_FACES + 4 ) +#define NV ( B3_MAX_HULL_VERTICES + 4 ) + +// SIMD separating axis test based on an implementation developed by Cairn Overturf. +// See his article: https://cairno.substack.com/p/improvements-to-the-separating-axis +b3AxisQuery b3ComputeSeparatingAxis( const b3HullData* hullA, const b3HullData* hullB, b3Transform xfB, bool earlyReturn ) +{ + b3Matrix3 R = b3MakeMatrixFromQuat( xfB.q ); + b3Matrix3 invR = b3Transpose( R ); + + float speculativeDistance = B3_SPECULATIVE_DISTANCE; + + b3AxisQuery res = { + .faceA = + { + .normal = b3Vec3_zero, + .separation = -INFINITY, + .indexA = B3_NULL_INDEX, + .indexB = B3_NULL_INDEX, + .type = b3_faceAxisA, + }, + .faceB = + { + .normal = b3Vec3_zero, + .separation = -INFINITY, + .indexA = B3_NULL_INDEX, + .indexB = B3_NULL_INDEX, + .type = b3_faceAxisB, + }, + .edge = + { + .normal = b3Vec3_zero, + .separation = -INFINITY, + .indexA = B3_NULL_INDEX, + .indexB = B3_NULL_INDEX, + .type = b3_edgePairAxis, + }, + .separatedFeature = b3_invalidAxis, + }; + + int faceCountA = hullA->faceCount; + const b3Plane* planesA = b3GetHullPlanes( hullA ); + + int soaVertexCountB = ( hullB->vertexCount + 3 ) & ~3; + const float* vxB = b3GetHullSoaVertices( hullB ); + const float* vyB = vxB + soaVertexCountB; + const float* vzB = vyB + soaVertexCountB; + + b3Vec3 cB = b3AABB_Center( hullB->aabb ); + b3Vec3 hB = b3AABB_Extents( hullB->aabb ); + + // Test A's face planes against B's vertices. + for ( int i = 0; i < faceCountA; ++i ) + { + b3Plane plane = planesA[i]; + b3Vec3 direction = b3Neg( b3MulMV( invR, plane.normal ) ); + float planeSeparation = b3Dot( plane.normal, xfB.p ) - plane.offset; + float biasB = b3Dot( direction, cB ) + 1.0625f * b3Dot( b3Abs( direction ), hB ); + float support; + int vertexIndex; + b3GetSupportWide( direction, vxB, vyB, vzB, soaVertexCountB, biasB, &support, &vertexIndex ); + float separation = planeSeparation - support; + if ( separation > res.faceA.separation ) + { + res.faceA.normal = plane.normal; + res.faceA.separation = separation; + res.faceA.indexA = i; + res.faceA.indexB = vertexIndex; + if ( separation > speculativeDistance && earlyReturn ) + { + res.separatedFeature = b3_faceAxisA; + return res; + } + } + } + + int faceCountB = hullB->faceCount; + const b3Plane* planesB = b3GetHullPlanes( hullB ); + + int soaVertexCountA = ( hullA->vertexCount + 3 ) & ~3; + const float* vxA = b3GetHullSoaVertices( hullA ); + const float* vyA = vxA + soaVertexCountA; + const float* vzA = vyA + soaVertexCountA; + + b3Vec3 cA = b3AABB_Center( hullA->aabb ); + b3Vec3 hA = b3AABB_Extents( hullA->aabb ); + + // Test B's face planes against A's vertices. + for ( int i = 0; i < faceCountB; ++i ) + { + b3Plane plane = planesB[i]; + b3Vec3 direction = b3Neg( b3MulMV( R, plane.normal ) ); + float planeSeparation = b3Dot( direction, xfB.p ) - plane.offset; + float biasA = b3Dot( direction, cA ) + 1.0625f * b3Dot( b3Abs( direction ), hA ); + float support; + int vertexIndex; + b3GetSupportWide( direction, vxA, vyA, vzA, soaVertexCountA, biasA, &support, &vertexIndex ); + float separation = planeSeparation - support; + if ( separation > res.faceB.separation ) + { + res.faceB.normal = direction; + res.faceB.separation = separation; + res.faceB.indexA = vertexIndex; + res.faceB.indexB = i; + // This points from A to B and is in frame A + if ( separation > speculativeDistance && earlyReturn ) + { + res.separatedFeature = b3_faceAxisB; + return res; + } + } + } + + // Transform B into A's space once, into SoA arrays. Extra space so + // tail can be set to zero in all cases. + _Static_assert( ( B3_MAX_HULL_EDGES & 3 ) == 0, "must be multiple of 4" ); + _Static_assert( ( B3_MAX_HULL_FACES & 3 ) == 0, "must be multiple of 4" ); + _Static_assert( ( B3_MAX_HULL_VERTICES & 3 ) == 0, "must be multiple of 4" ); + + // The alignments below are not necessary, but they don't hurt. + + // B face normals in A space, negated. + _Alignas( 16 ) float bFNx[NF]; + _Alignas( 16 ) float bFNy[NF]; + _Alignas( 16 ) float bFNz[NF]; + + // B vertices in A space, negated. + _Alignas( 16 ) float bWx[NV]; + _Alignas( 16 ) float bWy[NV]; + _Alignas( 16 ) float bWz[NV]; + + int soaFaceCountB = ( faceCountB + 3 ) & ~3; + const float* nxB = b3GetHullSoaNormals( hullB ); + const float* nyB = nxB + soaFaceCountB; + const float* nzB = nyB + soaFaceCountB; + + b3NegativeTransformFromSoA( &R, xfB.p, nxB, nyB, nzB, soaFaceCountB, bFNx, bFNy, bFNz, false ); + b3NegativeTransformFromSoA( &R, xfB.p, vxB, vyB, vzB, soaVertexCountB, bWx, bWy, bWz, true ); + + // Per B edge data. C and D are the two face normals, v0 a vertex, DC the edge vector. + _Alignas( 16 ) float bCx[NE]; + _Alignas( 16 ) float bCy[NE]; + _Alignas( 16 ) float bCz[NE]; + _Alignas( 16 ) float bDx[NE]; + _Alignas( 16 ) float bDy[NE]; + _Alignas( 16 ) float bDz[NE]; + _Alignas( 16 ) float bV0x[NE]; + _Alignas( 16 ) float bV0y[NE]; + _Alignas( 16 ) float bV0z[NE]; + _Alignas( 16 ) float bDCx[NE]; + _Alignas( 16 ) float bDCy[NE]; + _Alignas( 16 ) float bDCz[NE]; + + int halfEdgeCountB = hullB->edgeCount; + const b3HullHalfEdge* halfEdgesB = b3GetHullEdges( hullB ); + int nb = 0; + for ( int i = 0; i < halfEdgeCountB; i += 2 ) + { + const b3HullHalfEdge* edge = halfEdgesB + i; + const b3HullHalfEdge* twin = edge + 1; + int f0 = edge->face; + int f1 = twin->face; + int v0 = edge->origin; + int v1 = twin->origin; + + bCx[nb] = bFNx[f0]; + bCy[nb] = bFNy[f0]; + bCz[nb] = bFNz[f0]; + bDx[nb] = bFNx[f1]; + bDy[nb] = bFNy[f1]; + bDz[nb] = bFNz[f1]; + bV0x[nb] = bWx[v0]; + bV0y[nb] = bWy[v0]; + bV0z[nb] = bWz[v0]; + bDCx[nb] = bWx[v1] - bWx[v0]; + bDCy[nb] = bWy[v1] - bWy[v0]; + bDCz[nb] = bWz[v1] - bWz[v0]; + nb += 1; + } + + // Per A edge data, already in A's space so just gathered. n0 and n1 are the two face + // normals, d the edge vector av1-av0, v0 the first vertex. Tol is the + // parallel edge tolerance, scaled by the edge length. + _Alignas( 16 ) float aN0x[NE]; + _Alignas( 16 ) float aN0y[NE]; + _Alignas( 16 ) float aN0z[NE]; + _Alignas( 16 ) float aN1x[NE]; + _Alignas( 16 ) float aN1y[NE]; + _Alignas( 16 ) float aN1z[NE]; + // dir = av1 - av0 + _Alignas( 16 ) float aDx[NE]; + _Alignas( 16 ) float aDy[NE]; + _Alignas( 16 ) float aDz[NE]; + _Alignas( 16 ) float aV0x[NE]; + _Alignas( 16 ) float aV0y[NE]; + _Alignas( 16 ) float aV0z[NE]; + _Alignas( 16 ) float aTol[NE]; + + int halfEdgeCountA = hullA->edgeCount; + const b3HullHalfEdge* halfEdgesA = b3GetHullEdges( hullA ); + int na = 0; + + float squaredTol = B3_PARALLEL_EDGE_TOL * B3_PARALLEL_EDGE_TOL; + for ( int i = 0; i < halfEdgeCountA; i += 2 ) + { + const b3HullHalfEdge* edge = halfEdgesA + i; + const b3HullHalfEdge* twin = edge + 1; + + b3Vec3 A = planesA[edge->face].normal; + b3Vec3 B = planesA[twin->face].normal; + aN0x[na] = A.x; + aN0y[na] = A.y; + aN0z[na] = A.z; + aN1x[na] = B.x; + aN1y[na] = B.y; + aN1z[na] = B.z; + + int v0 = edge->origin; + int v1 = twin->origin; + + aDx[na] = vxA[v1] - vxA[v0]; + aDy[na] = vyA[v1] - vyA[v0]; + aDz[na] = vzA[v1] - vzA[v0]; + aV0x[na] = vxA[v0]; + aV0y[na] = vyA[v0]; + aV0z[na] = vzA[v0]; + + aTol[na] = squaredTol * ( aDx[na] * aDx[na] + aDy[na] * aDy[na] + aDz[na] * aDz[na] ); + na += 1; + } + + // Zero the tail lanes. + b3FloatW zero = b3ZeroW(); + b3StoreW( aN0x + na, zero ); + b3StoreW( aN0y + na, zero ); + b3StoreW( aN0z + na, zero ); + b3StoreW( aN1x + na, zero ); + b3StoreW( aN1y + na, zero ); + b3StoreW( aN1z + na, zero ); + b3StoreW( aDx + na, zero ); + b3StoreW( aDy + na, zero ); + b3StoreW( aDz + na, zero ); + b3StoreW( aV0x + na, zero ); + b3StoreW( aV0y + na, zero ); + b3StoreW( aV0z + na, zero ); + b3StoreW( aTol + na, zero ); + + int edgeCountB = halfEdgeCountB / 2; + +#if defined( B3_SIMD_NONE ) + + // The SIMD emulated version of this code is very slow. This is a purely scalar version + // for platforms that don't have SIMD capability. It is much faster than SIMD emulation. + // WARNING: this math needs to match the SIMD version for cross platform determinism. + + const float EPS = -0.0001f; + + for ( int j = 0; j < edgeCountB; ++j ) + { + float Cx = bCx[j]; + float Cy = bCy[j]; + float Cz = bCz[j]; + float Dx = bDx[j]; + float Dy = bDy[j]; + float Dz = bDz[j]; + float DCx = bDCx[j]; + float DCy = bDCy[j]; + float DCz = bDCz[j]; + float bv0x = bV0x[j]; + float bv0y = bV0y[j]; + float bv0z = bV0z[j]; + + for ( int i = 0; i < na; ++i ) + { + // CBA = C.dir, DBA = D.dir, where dir = B_x_A + float CBA = Cx * aDx[i] + ( Cy * aDy[i] + Cz * aDz[i] ); + float DBA = Dx * aDx[i] + ( Dy * aDy[i] + Dz * aDz[i] ); + if ( CBA * DBA >= EPS ) + { + continue; + } + + // ADC = n0.DC, BDC = n1.DC, where DC = D_x_C + float ADC = aN0x[i] * DCx + ( aN0y[i] * DCy + aN0z[i] * DCz ); + float BDC = aN1x[i] * DCx + ( aN1y[i] * DCy + aN1z[i] * DCz ); + if ( ADC * BDC >= EPS || CBA * BDC >= EPS ) + { + continue; + } + + // Reject near parallel edges + float maxCD = b3MaxFloat( CBA * CBA, DBA * DBA ); + if ( maxCD <= aTol[i] ) + { + continue; + } + + // t = -CBA / (DBA - CBA) + float t = -CBA / ( DBA - CBA ); + + // normal = lerp(t, C, D) = C + (D-C)*t + float nx = Cx + t * ( Dx - Cx ); + float ny = Cy + t * ( Dy - Cy ); + float nz = Cz + t * ( Dz - Cz ); + float len2 = nx * nx + ( ny * ny + nz * nz ); + float inv = 1.0f / sqrtf( len2 ); + nx *= inv; + ny *= inv; + nz *= inv; + + // separation = -dot(normal, av0 + bv0) + float sx = aV0x[i] + bv0x; + float sy = aV0y[i] + bv0y; + float sz = aV0z[i] + bv0z; + + float separation = -( sx * nx + ( sy * ny + sz * nz ) ); + if ( separation > res.edge.separation ) + { + res.edge.normal = (b3Vec3){ nx, ny, nz }; + res.edge.separation = separation; + + // Half edge index + res.edge.indexA = 2 * i; + res.edge.indexB = 2 * j; + + if ( separation > speculativeDistance && earlyReturn ) + { + res.separatedFeature = b3_edgePairAxis; + return res; + } + } + } + } + +#else + + // Edge phase, one B edge against four A edges at a time, no transforms in the loop. + const b3FloatW EPS = b3SplatW( -0.0001f ); + const b3FloatW INF = b3SplatW( INFINITY ); + + for ( int j = 0; j < edgeCountB; ++j ) + { + const b3FloatW Cx = b3SplatW( bCx[j] ); + const b3FloatW Cy = b3SplatW( bCy[j] ); + const b3FloatW Cz = b3SplatW( bCz[j] ); + const b3FloatW Dx = b3SplatW( bDx[j] ); + const b3FloatW Dy = b3SplatW( bDy[j] ); + const b3FloatW Dz = b3SplatW( bDz[j] ); + const b3FloatW DCx = b3SplatW( bDCx[j] ); + const b3FloatW DCy = b3SplatW( bDCy[j] ); + const b3FloatW DCz = b3SplatW( bDCz[j] ); + const b3FloatW bv0x = b3SplatW( bV0x[j] ); + const b3FloatW bv0y = b3SplatW( bV0y[j] ); + const b3FloatW bv0z = b3SplatW( bV0z[j] ); + + for ( int i = 0; i < na; i += 4 ) + { + b3FloatW n0x = b3LoadW( aN0x + i ); + b3FloatW n0y = b3LoadW( aN0y + i ); + b3FloatW n0z = b3LoadW( aN0z + i ); + b3FloatW n1x = b3LoadW( aN1x + i ); + b3FloatW n1y = b3LoadW( aN1y + i ); + b3FloatW n1z = b3LoadW( aN1z + i ); + b3FloatW dx = b3LoadW( aDx + i ); + b3FloatW dy = b3LoadW( aDy + i ); + b3FloatW dz = b3LoadW( aDz + i ); + b3FloatW v0x = b3LoadW( aV0x + i ); + b3FloatW v0y = b3LoadW( aV0y + i ); + b3FloatW v0z = b3LoadW( aV0z + i ); + b3FloatW tol = b3LoadW( aTol + i ); + + // CBA = C.dir, DBA = D.dir, where dir = B_x_A + b3FloatW CBA = b3Dot3W( Cx, Cy, Cz, dx, dy, dz ); + b3FloatW DBA = b3Dot3W( Dx, Dy, Dz, dx, dy, dz ); + // ADC = n0.DC, BDC = n1.DC, where DC = D_x_C + b3FloatW ADC = b3Dot3W( n0x, n0y, n0z, DCx, DCy, DCz ); + b3FloatW BDC = b3Dot3W( n1x, n1y, n1z, DCx, DCy, DCz ); + + // Gauss map arc crossing test, CBA*DBAsupport only turns negative just before returning, + // so this never skips a lane that would trigger the early out. + b3FloatW improves = b3GreaterThanW( separation, b3SplatW( res.edge.separation ) ); + if ( b3AnyTrueW( improves ) == false ) + { + continue; + } + + _Alignas( 16 ) float sA[4]; + _Alignas( 16 ) float nxA[4]; + _Alignas( 16 ) float nyA[4]; + _Alignas( 16 ) float nzA[4]; + b3StoreW( sA, separation ); + b3StoreW( nxA, nx ); + b3StoreW( nyA, ny ); + b3StoreW( nzA, nz ); + + // Reduce in lane order so ties keep the first edge and the early out takes the first + // improving support below zero. Padded tail lanes carry +INF support, so they never + // update or index a->edges out of range. + for ( int lane = 0; lane < 4; lane++ ) + { + int ei = i + lane; + float s = sA[lane]; + if ( s > res.edge.separation ) + { + res.edge.normal = (b3Vec3){ nxA[lane], nyA[lane], nzA[lane] }; + res.edge.separation = s; + + // Half edge index + res.edge.indexA = 2 * ei; + res.edge.indexB = 2 * j; + + if ( s > speculativeDistance && earlyReturn ) + { + res.separatedFeature = b3_edgePairAxis; + return res; + } + } + } + } + } +#endif + + return res; +} + +#undef NE +#undef NF +#undef NV + +#define B3_SIMD_COLLIDE_HULLS 1 + +#if B3_SIMD_COLLIDE_HULLS == 1 + +void b3CollideHulls( b3LocalManifold* manifold, int capacity, const b3HullData* hullA, const b3HullData* hullB, + b3Transform transformBtoA, b3SATCache* cache ) +{ + manifold->pointCount = 0; + + if ( capacity < 4 ) + { + return; + } + + // Work in shapeA coordinates + float speculativeDistance = B3_SPECULATIVE_DISTANCE; + + float linearSlop = B3_LINEAR_SLOP; + const b3HullHalfEdge* edgesA = b3GetHullEdges( hullA ); + const b3Plane* planesA = b3GetHullPlanes( hullA ); + const b3Vec3* pointsA = b3GetHullPoints( hullA ); + + const b3HullHalfEdge* edgesB = b3GetHullEdges( hullB ); + const b3Plane* planesB = b3GetHullPlanes( hullB ); + const b3Vec3* pointsB = b3GetHullPoints( hullB ); + + cache->hit = 0; + + // Attempt to use the cache to speed up collision + switch ( cache->type ) + { + case b3_invalidAxis: + break; + + case b3_faceAxisA: + { + B3_ASSERT( cache->indexA < hullA->faceCount ); + + // Check for separation using cached face + b3Plane plane = planesA[cache->indexA]; + b3Vec3 searchDirectionInB = b3Neg( b3InvRotateVector( transformBtoA.q, plane.normal ) ); + + // todo use b3GetSupportWide + int vertexIndex = b3FindHullSupportVertex( hullB, searchDirectionInB ); + b3Vec3 support = b3TransformPoint( transformBtoA, pointsB[vertexIndex] ); + float separation = b3PlaneSeparation( plane, support ); + + if ( separation >= speculativeDistance ) + { + // Cache hit, shapes are separated + cache->hit = 1; + return; + } + + // Attempt face contact using cached feature + b3SeparatingAxis faceQuery; + faceQuery.normal = plane.normal; + faceQuery.separation = 0.0f; + faceQuery.indexA = cache->indexA; + faceQuery.indexB = vertexIndex; + faceQuery.type = b3_faceAxisA; + + b3SATCache localCache = { 0 }; + bool touching = b3BuildFaceAContact( manifold, capacity, hullA, hullB, transformBtoA, faceQuery, &localCache ); + if ( touching == true && b3AbsFloat( cache->separation - localCache.separation ) < linearSlop ) + { + // Cache hit, contact points generated + cache->hit = 1; + return; + } + } + break; + + case b3_faceAxisB: + { + B3_ASSERT( cache->indexB < hullB->faceCount ); + + // Check for separation using cached face + b3Plane plane = planesB[cache->indexB]; + b3Vec3 searchDirectionInA = b3Neg( b3RotateVector( transformBtoA.q, plane.normal ) ); + + // todo use b3GetSupportWide + int vertexIndex = b3FindHullSupportVertex( hullA, searchDirectionInA ); + b3Vec3 support = b3InvTransformPoint( transformBtoA, pointsA[vertexIndex] ); + float separation = b3PlaneSeparation( plane, support ); + + if ( separation >= speculativeDistance ) + { + // Cache hit, shapes are separated + cache->hit = 1; + return; + } + + // Attempt face contact using cached feature + b3SeparatingAxis faceQuery; + faceQuery.normal = b3Neg( plane.normal ); + faceQuery.separation = 0.0f; + faceQuery.indexA = vertexIndex; + faceQuery.indexB = cache->indexB; + faceQuery.type = b3_faceAxisB; + + b3SATCache localCache = { 0 }; + bool touching = b3BuildFaceBContact( manifold, capacity, hullA, hullB, transformBtoA, faceQuery, &localCache ); + if ( touching == true && b3AbsFloat( cache->separation - localCache.separation ) < linearSlop ) + { + // Cache hit, contact points generated + cache->hit = 1; + return; + } + } + break; + + case b3_edgePairAxis: + { + int indexA = cache->indexA; + const b3HullHalfEdge* edge1 = edgesA + indexA; + const b3HullHalfEdge* twin1 = edgesA + indexA + 1; + B3_ASSERT( edge1->twin == indexA + 1 && twin1->twin == indexA ); + + b3Vec3 pA = pointsA[edge1->origin]; + b3Vec3 qA = pointsA[twin1->origin]; + b3Vec3 eA = b3Sub( qA, pA ); + + b3Vec3 uA = planesA[edge1->face].normal; + b3Vec3 vA = planesA[twin1->face].normal; + + int indexB = cache->indexB; + const b3HullHalfEdge* edge2 = edgesB + indexB; + const b3HullHalfEdge* twin2 = edgesB + indexB + 1; + B3_ASSERT( edge2->twin == indexB + 1 && twin2->twin == indexB ); + + b3Vec3 pB = b3TransformPoint( transformBtoA, pointsB[edge2->origin] ); + b3Vec3 qB = b3TransformPoint( transformBtoA, pointsB[twin2->origin] ); + b3Vec3 eB = b3Sub( qB, pB ); + + b3Vec3 uB = b3RotateVector( transformBtoA.q, planesB[edge2->face].normal ); + b3Vec3 vB = b3RotateVector( transformBtoA.q, planesB[twin2->face].normal ); + + // flipping the signs of u2 and v2 + // cross(v2, u2) == cross(-v2, -u2) + // so we still use -e2 + // but we can also use e1 = cross(u1, v1) and e2 = cross(u2, v2) + float cba = b3Dot( uB, eA ); + float dba = b3Dot( vB, eA ); + float adc = -b3Dot( uA, eB ); + float bdc = -b3Dot( vA, eB ); + + if ( cba * dba < 0.0f && adc * bdc < 0.0f && cba * bdc > 0.0f ) + { + // Avoid nearly parallel edges that may lead to invalid separation values at the noise floor. + float squaredTolerance = B3_PARALLEL_EDGE_TOL * B3_PARALLEL_EDGE_TOL; + if ( b3MaxFloat( cba * cba, dba * dba ) >= squaredTolerance * b3LengthSquared( eA ) ) + { + // Transform reference center of the first hull into local space of the second hull + float t = cba / ( cba - dba ); + b3Vec3 axis = b3Lerp( uB, vB, t ); + B3_VALIDATE( b3LengthSquared( axis ) > 1000.0f * FLT_MIN ); + axis = b3Normalize( axis ); + float separation = b3Dot( axis, b3Sub( qA, qB ) ); + + if ( separation > speculativeDistance ) + { + // Cache hit, shapes are separated + cache->hit = 1; + return; + } + + // Try to rebuild contact from last features + b3SeparatingAxis edgeQuery = { 0 }; + edgeQuery.normal = b3Neg( axis ); + edgeQuery.separation = 0.0f; + edgeQuery.indexA = cache->indexA; + edgeQuery.indexB = cache->indexB; + edgeQuery.type = b3_edgePairAxis; + + b3SATCache localCache = { 0 }; + bool touching = b3BuildEdgeContact( manifold, hullA, hullB, transformBtoA, edgeQuery, &localCache ); + + // This separation tolerance may have a big impact on performance in some benchmarks. + if ( touching && b3AbsFloat( cache->separation - localCache.separation ) < linearSlop ) + { + // Cache hit, contact point generated + cache->hit = 1; + return; + } + } + } + } + break; + + // This case is for testing + case b3_manualFaceAxisA: + { + b3AxisQuery axisQuery = b3ComputeSeparatingAxis( hullA, hullB, transformBtoA, false ); + b3SeparatingAxis faceQuery = axisQuery.faceA; + b3BuildFaceAContact( manifold, capacity, hullA, hullB, transformBtoA, faceQuery, cache ); + return; + } + + // This case is for testing + case b3_manualFaceAxisB: + { + b3AxisQuery axisQuery = b3ComputeSeparatingAxis( hullA, hullB, transformBtoA, false ); + b3SeparatingAxis faceQuery = axisQuery.faceB; + b3BuildFaceBContact( manifold, capacity, hullA, hullB, transformBtoA, faceQuery, cache ); + return; + } + + // This case is for testing + case b3_manualEdgePairAxis: + { + b3AxisQuery axisQuery = b3ComputeSeparatingAxis( hullA, hullB, transformBtoA, false ); + b3SeparatingAxis edgeQuery = axisQuery.edge; + if ( edgeQuery.indexA != B3_NULL_INDEX ) + { + b3BuildEdgeContact( manifold, hullA, hullB, transformBtoA, edgeQuery, cache ); + } + return; + } + + default: + B3_ASSERT( false ); + break; + } + + manifold->pointCount = 0; + *cache = (b3SATCache){ 0 }; + + b3AxisQuery axisQuery = b3ComputeSeparatingAxis( hullA, hullB, transformBtoA, true ); + + if ( axisQuery.separatedFeature != b3_invalidAxis ) + { + // We found a separating axis + cache->type = axisQuery.separatedFeature; + + if ( axisQuery.separatedFeature == b3_faceAxisA ) + { + B3_VALIDATE( axisQuery.faceA.separation > speculativeDistance ); + cache->separation = axisQuery.faceA.separation; + cache->indexA = (uint8_t)axisQuery.faceA.indexA; + cache->indexB = (uint8_t)axisQuery.faceA.indexB; + } + else if ( axisQuery.separatedFeature == b3_faceAxisB ) + { + B3_VALIDATE( axisQuery.faceB.separation > speculativeDistance ); + cache->separation = axisQuery.faceB.separation; + cache->indexA = (uint8_t)axisQuery.faceB.indexA; + cache->indexB = (uint8_t)axisQuery.faceB.indexB; + } + else + { + B3_ASSERT( axisQuery.separatedFeature == b3_edgePairAxis ); + B3_VALIDATE( axisQuery.edge.separation > speculativeDistance ); + cache->separation = axisQuery.edge.separation; + cache->indexA = (uint8_t)axisQuery.edge.indexA; + cache->indexB = (uint8_t)axisQuery.edge.indexB; + } + return; + } + + B3_VALIDATE( axisQuery.faceA.separation <= speculativeDistance || axisQuery.faceB.separation <= speculativeDistance || + axisQuery.edge.separation <= speculativeDistance ); + + if ( axisQuery.faceA.separation > axisQuery.faceB.separation ) + { + b3SeparatingAxis faceQuery = axisQuery.faceA; + B3_VALIDATE( 0 <= faceQuery.indexA && faceQuery.indexA < hullA->faceCount ); + B3_VALIDATE( 0 <= faceQuery.indexB && faceQuery.indexB < hullB->vertexCount ); + + // Face contact A + b3BuildFaceAContact( manifold, capacity, hullA, hullB, transformBtoA, faceQuery, cache ); + + B3_VALIDATE( cache->indexA < hullA->faceCount ); + B3_VALIDATE( cache->indexB < hullB->vertexCount ); + } + else + { + b3SeparatingAxis faceQuery = axisQuery.faceB; + B3_VALIDATE( 0 <= faceQuery.indexA && faceQuery.indexA < hullA->vertexCount ); + B3_VALIDATE( 0 <= faceQuery.indexB && faceQuery.indexB < hullB->faceCount ); + + // Face contact B + b3BuildFaceBContact( manifold, capacity, hullA, hullB, transformBtoA, faceQuery, cache ); + + B3_VALIDATE( cache->indexA < hullA->vertexCount ); + B3_VALIDATE( cache->indexB < hullB->faceCount ); + } + + b3SeparatingAxis edgeQuery = axisQuery.edge; + + if ( edgeQuery.indexA == B3_NULL_INDEX ) + { + // There are no valid edge pairs (all edges parallel) + return; + } + + float clipSeparation = cache->separation; + float edgeTol = linearSlop; + + // Face contact can be empty if it does not realize the axis of minimum penetration. + // Create edge contact if face contact fails or edge contact is significantly better! + if ( manifold->pointCount == 0 || edgeQuery.separation > clipSeparation + edgeTol ) + { + B3_ASSERT( 0 <= edgeQuery.indexA && edgeQuery.indexA < hullA->edgeCount ); + B3_ASSERT( 0 <= edgeQuery.indexB && edgeQuery.indexB < hullB->edgeCount ); + + // Edge contact + b3LocalManifold edgeManifold = { 0 }; + b3LocalManifoldPoint edgePoint = { 0 }; + edgeManifold.points = &edgePoint; + + b3SATCache edgeCache = { 0 }; + b3BuildEdgeContact( &edgeManifold, hullA, hullB, transformBtoA, edgeQuery, &edgeCache ); + + // It is possible with speculation to have vertex-vertex collision that is missed by SAT, + // so edge contact yields no points. In that case perhaps the face contact has some points. + if ( edgeManifold.pointCount == 1 ) + { + // Copy edge manifold out, being careful to preserve manifold point buffer. + b3LocalManifoldPoint* points = manifold->points; + *manifold = edgeManifold; + manifold->points = points; + manifold->points[0] = edgePoint; + *cache = edgeCache; + } + } +} + +#else + +// todo this code has gone stale, will be deleted soon + +typedef struct b3FaceQuery +{ + float separation; + int faceIndex; + int vertexIndex; +} b3FaceQuery; + +typedef struct b3EdgeQuery +{ + b3Vec3 normal; + float separation; + int indexA; + int indexB; +} b3EdgeQuery; + +// Old non-SIMD version. Keeping this for testing and comparisons + +static b3FaceQuery b3QueryFaceDirections( const b3HullData* hullA, const b3HullData* hullB, b3Transform relativeTransform ) +{ + // We perform all computations in local space of the second hull + b3Transform transform = b3InvertTransform( relativeTransform ); + const b3Plane* planesA = b3GetHullPlanes( hullA ); + const b3Vec3* pointsB = b3GetHullPoints( hullB ); + + int maxFaceIndex = -1; + int maxVertexIndex = -1; + float maxFaceSeparation = -FLT_MAX; + float speculativeDistance = B3_SPECULATIVE_DISTANCE; + + for ( int faceIndex = 0; faceIndex < hullA->faceCount; ++faceIndex ) + { + b3Plane plane = b3TransformPlane( transform, planesA[faceIndex] ); + int vertexIndex = b3FindHullSupportVertex( hullB, b3Neg( plane.normal ) ); + b3Vec3 support = pointsB[vertexIndex]; + + float separation = b3PlaneSeparation( plane, support ); + if ( separation > maxFaceSeparation ) + { + maxFaceIndex = faceIndex; + maxVertexIndex = vertexIndex; + maxFaceSeparation = separation; + + if ( separation >= speculativeDistance ) + { + return (b3FaceQuery){ + .separation = maxFaceSeparation, + .faceIndex = (uint8_t)maxFaceIndex, + .vertexIndex = (uint8_t)maxVertexIndex, + }; + } + } + } + + return (b3FaceQuery){ + .separation = maxFaceSeparation, + .faceIndex = (uint8_t)maxFaceIndex, + .vertexIndex = (uint8_t)maxVertexIndex, + }; +} + +static b3EdgeQuery b3QueryEdgeDirections( const b3HullData* hullA, const b3HullData* hullB, b3Transform transformBtoA ) +{ + // Find axis of minimum penetration + b3Vec3 maxNormal = b3Vec3_zero; + float maxSeparation = -FLT_MAX; + int maxIndexA = B3_NULL_INDEX; + int maxIndexB = B3_NULL_INDEX; + + const b3HullHalfEdge* edgesA = b3GetHullEdges( hullA ); + const b3Vec3* pointsA = b3GetHullPoints( hullA ); + const b3Plane* planesA = b3GetHullPlanes( hullA ); + const b3HullHalfEdge* edgesB = b3GetHullEdges( hullB ); + const b3Vec3* pointsB = b3GetHullPoints( hullB ); + const b3Plane* planesB = b3GetHullPlanes( hullB ); + + // Work in frame A + b3Matrix3 matrix = b3MakeMatrixFromQuat( transformBtoA.q ); + float speculativeDistance = B3_SPECULATIVE_DISTANCE; + float squaredTolerance = B3_PARALLEL_EDGE_TOL * B3_PARALLEL_EDGE_TOL; + + // Arranged to minimize transform operations + for ( int indexB = 0; indexB < hullB->edgeCount; indexB += 2 ) + { + const b3HullHalfEdge* edgeB = edgesB + indexB; + const b3HullHalfEdge* twinB = edgesB + indexB + 1; + B3_ASSERT( edgeB->twin == indexB + 1 && twinB->twin == indexB ); + + b3Vec3 qB = pointsB[twinB->origin]; + b3Vec3 eB = b3MulMV( matrix, b3Sub( qB, pointsB[edgeB->origin] ) ); + qB = b3Add( b3MulMV( matrix, qB ), transformBtoA.p ); + + b3Vec3 uB = b3MulMV( matrix, planesB[edgeB->face].normal ); + b3Vec3 vB = b3MulMV( matrix, planesB[twinB->face].normal ); + + for ( int indexA = 0; indexA < hullA->edgeCount; indexA += 2 ) + { + const b3HullHalfEdge* edgeA = edgesA + indexA; + const b3HullHalfEdge* twinA = edgesA + indexA + 1; + B3_ASSERT( edgeA->twin == indexA + 1 && twinA->twin == indexA ); + + b3Vec3 qA = pointsA[twinA->origin]; + b3Vec3 eA = b3Sub( qA, pointsA[edgeA->origin] ); + b3Vec3 uA = planesA[edgeA->face].normal; + b3Vec3 vA = planesA[twinA->face].normal; + + // See "Collision Detection of Convex Polyhedra Based on Duality Transformation" + // Two edges build a face on the Minkowski sum if the associated arcs AB and CD intersect on the Gauss map. + // The associated arcs are defined by the adjacent face normals of each edge. + + // These are signed volumes with an edge optimization to avoid cross products + // eA parallel to cross(vA, uA) + // eB parallel to cross(vB, uB) + // Since only signs are tested, length doesn't matter. + + float cba = b3Dot( uB, eA ); + float dba = b3Dot( vB, eA ); + float adc = -b3Dot( uA, eB ); + float bdc = -b3Dot( vA, eB ); + + if ( cba * dba < 0.0f && adc * bdc < 0.0f && cba * bdc > 0.0f ) + { + // Avoid nearly parallel edges that may lead to invalid separation values at the noise floor. + if ( b3MaxFloat( cba * cba, dba * dba ) < squaredTolerance * b3LengthSquared( eA ) ) + { + continue; + } + + // The intersection of the arcs on the Gauss map is the edge pair axis. Cast the + // arc of hull B (from uB to vB) against the plane containing the arc of hull A: + // dot(uB + t * (vB - uB), eA) == 0 + // then + // t = cba / (cba - dba) + // + // The signs of cba and dba differ (Minkowski test), so the division is safe. + // + // The axis generated points from B to A by construction since it lands between + // two face normals on B. This removes the need to orient the separation axis + // using the hull centers. + // + // The axis is perpendicular to both edges so I can use qA and qB as arbitrary + // points on edgeA and edgeB to measure the separation. + float t = cba / ( cba - dba ); + b3Vec3 axis = b3Lerp( uB, vB, t ); + B3_VALIDATE( b3LengthSquared( axis ) > 1000.0f * FLT_MIN ); + axis = b3Normalize( axis ); + float separation = b3Dot( axis, b3Sub( qA, qB ) ); + + if ( separation > maxSeparation ) + { + // Continues to find the maximum separating axis + // Flip normal so it points from A to B + maxNormal = b3Neg( axis ); + maxSeparation = separation; + maxIndexA = indexA; + maxIndexB = indexB; + + if ( separation >= speculativeDistance ) + { + // Cache hit, shapes are separated + return (b3EdgeQuery){ + .normal = maxNormal, + .separation = maxSeparation, + .indexA = maxIndexA, + .indexB = maxIndexB, + }; + } + } + } + } + } + + return (b3EdgeQuery){ + .normal = maxNormal, + .separation = maxSeparation, + .indexA = maxIndexA, + .indexB = maxIndexB, + }; +} + +void b3CollideHulls( b3LocalManifold* manifold, int capacity, const b3HullData* hullA, const b3HullData* hullB, + b3Transform transformBtoA, b3SATCache* cache ) { manifold->pointCount = 0; @@ -1405,55 +2382,64 @@ void b3CollideHulls( b3LocalManifold* manifold, int capacity, const b3HullData* case b3_edgePairAxis: { - int index1 = cache->indexA; - const b3HullHalfEdge* edge1 = edgesA + index1; - const b3HullHalfEdge* twin1 = edgesA + index1 + 1; - B3_ASSERT( edge1->twin == index1 + 1 && twin1->twin == index1 ); + int indexA = cache->indexA; + const b3HullHalfEdge* edge1 = edgesA + indexA; + const b3HullHalfEdge* twin1 = edgesA + indexA + 1; + B3_ASSERT( edge1->twin == indexA + 1 && twin1->twin == indexA ); - b3Vec3 p1 = pointsA[edge1->origin]; - b3Vec3 q1 = pointsA[twin1->origin]; - b3Vec3 e1 = b3Sub( q1, p1 ); + b3Vec3 pA = pointsA[edge1->origin]; + b3Vec3 qA = pointsA[twin1->origin]; + b3Vec3 eA = b3Sub( qA, pA ); - b3Vec3 u1 = planesA[edge1->face].normal; - b3Vec3 v1 = planesA[twin1->face].normal; + b3Vec3 uA = planesA[edge1->face].normal; + b3Vec3 vA = planesA[twin1->face].normal; - int index2 = cache->indexB; - const b3HullHalfEdge* edge2 = edgesB + index2; - const b3HullHalfEdge* twin2 = edgesB + index2 + 1; - B3_ASSERT( edge2->twin == index2 + 1 && twin2->twin == index2 ); + int indexB = cache->indexB; + const b3HullHalfEdge* edge2 = edgesB + indexB; + const b3HullHalfEdge* twin2 = edgesB + indexB + 1; + B3_ASSERT( edge2->twin == indexB + 1 && twin2->twin == indexB ); - b3Vec3 p2 = b3TransformPoint( transformBtoA, pointsB[edge2->origin] ); - b3Vec3 q2 = b3TransformPoint( transformBtoA, pointsB[twin2->origin] ); - b3Vec3 e2 = b3Sub( q2, p2 ); + b3Vec3 pB = b3TransformPoint( transformBtoA, pointsB[edge2->origin] ); + b3Vec3 qB = b3TransformPoint( transformBtoA, pointsB[twin2->origin] ); + b3Vec3 eB = b3Sub( qB, pB ); - b3Vec3 u2 = b3RotateVector( transformBtoA.q, planesB[edge2->face].normal ); - b3Vec3 v2 = b3RotateVector( transformBtoA.q, planesB[twin2->face].normal ); + b3Vec3 uB = b3RotateVector( transformBtoA.q, planesB[edge2->face].normal ); + b3Vec3 vB = b3RotateVector( transformBtoA.q, planesB[twin2->face].normal ); // flipping the signs of u2 and v2 // cross(v2, u2) == cross(-v2, -u2) // so we still use -e2 // but we can also use e1 = cross(u1, v1) and e2 = cross(u2, v2) - bool isMinkowski = b3IsMinkowskiFace( u1, v1, e1, b3Neg( u2 ), b3Neg( v2 ), e2 ); - if ( isMinkowski == true ) + float cba = b3Dot( uB, eA ); + float dba = b3Dot( vB, eA ); + float adc = -b3Dot( uA, eB ); + float bdc = -b3Dot( vA, eB ); + + if ( cba * dba < 0.0f && adc * bdc < 0.0f && cba * bdc > 0.0f ) { - // Transform reference center of the first hull into local space of the second hull - b3Vec3 c1 = hullA->center; - b3Vec3 c2 = b3TransformPoint( transformBtoA, hullB->center ); - - float separation = b3EdgeEdgeSeparation( p1, e1, c1, p2, e2, c2 ); - if ( separation > speculativeDistance ) + // Avoid nearly parallel edges that may lead to invalid separation values at the noise floor. + float squaredTolerance = B3_PARALLEL_EDGE_TOL * B3_PARALLEL_EDGE_TOL; + if ( b3MaxFloat( cba * cba, dba * dba ) >= squaredTolerance * b3LengthSquared( eA ) ) { - // Cache hit, shapes are separated - return; - } + // Transform reference center of the first hull into local space of the second hull + float t = cba / ( cba - dba ); + b3Vec3 axis = b3Lerp( uB, vB, t ); + B3_VALIDATE( b3LengthSquared( axis ) > 1000.0f * FLT_MIN ); + axis = b3Normalize( axis ); + float separation = b3Dot( axis, b3Sub( qA, qB ) ); + + if ( separation > speculativeDistance ) + { + // Cache hit, shapes are separated + return; + } - // if ( cache->separation <= speculativeDistance ) - { // Try to rebuild contact from last features - b3EdgeQuery edgeQuery; + b3EdgeQuery edgeQuery = { 0 }; + edgeQuery.normal = b3Neg( axis ); + edgeQuery.separation = 0.0f; edgeQuery.indexA = cache->indexA; edgeQuery.indexB = cache->indexB; - edgeQuery.separation = 0.0f; b3SATCache localCache = { 0 }; bool touching = b3BuildEdgeContact( manifold, hullA, hullB, transformBtoA, edgeQuery, &localCache ); @@ -1598,3 +2584,5 @@ void b3CollideHulls( b3LocalManifold* manifold, int capacity, const b3HullData* } } } + +#endif diff --git a/vendor/box3d/src/src/core.h b/vendor/box3d/src/src/core.h index 3aaf196b1..005e2d6ac 100644 --- a/vendor/box3d/src/src/core.h +++ b/vendor/box3d/src/src/core.h @@ -57,7 +57,12 @@ #define B3_SIMD_WIDTH 4 //#pragma message("B3_SIMD_SSE2") #elif defined( B3_CPU_ARM ) + // ARMv7 Neon doesn't have divide or sqrt so cannot be used. + #if defined( __aarch64__ ) || defined( _M_ARM64 ) #define B3_SIMD_NEON + #else + #define B3_SIMD_NONE + #endif #define B3_SIMD_WIDTH 4 //#pragma message("B3_SIMD_NEON") #elif defined( B3_CPU_WASM ) @@ -132,6 +137,7 @@ void* b3AllocZeroed( size_t size ); void b3Free( void* mem, size_t size ); void* b3GrowAlloc( void* oldMem, int oldSize, int newSize ); +B3_PRINTF_FORMAT( 1, 2 ) void b3Log( const char* format, ... ); // Geometry content hashes reserve zero to mean unhashed diff --git a/vendor/box3d/src/src/height_field.c b/vendor/box3d/src/src/height_field.c index 1b0307fe9..69c2474e9 100644 --- a/vendor/box3d/src/src/height_field.c +++ b/vendor/box3d/src/src/height_field.c @@ -749,7 +749,7 @@ b3CastOutput b3ShapeCastHeightField( const b3HeightFieldData* heightField, const // nextFractionX / nextFractionZ advance in units of the clamped sweep // [minFraction, maxFraction], but bestFraction is a fraction of the full input // translation. Precompute the affine map from clamped space to input space so - // the loop termination test compares like with like — otherwise it can exit + // the loop termination test compares like with like. Otherwise it can exit // early and miss a closer hit in a later cell. float gridFractionScale = input->maxFraction * ( maxFraction - minFraction ); float gridFractionOffset = input->maxFraction * minFraction; @@ -1382,7 +1382,7 @@ b3HeightFieldData* b3CreateGrid( int rowCount, int columnCount, b3Vec3 scale, bo } b3HeightFieldData* b3CreateWave( int rowCount, int columnCount, b3Vec3 scale, float rowFrequency, float columnFrequency, - bool makeHoles ) + bool makeHoles ) { int heightCount = rowCount * columnCount; float* heights = (float*)b3Alloc( heightCount * sizeof( float ) ); @@ -1390,14 +1390,18 @@ b3HeightFieldData* b3CreateWave( int rowCount, int columnCount, b3Vec3 scale, fl float omegaZ = 2.0f * B3_PI * rowFrequency; float omegaX = 2.0f * B3_PI * columnFrequency; + b3CosSin cs = { 0 }; + for ( int i = 0; i < rowCount; ++i ) { - float rowHeight = sinf( omegaZ * i ); + cs = b3ComputeCosSin( omegaZ * i ); + float rowHeight = cs.sine; for ( int j = 0; j < columnCount; ++j ) { int k = i * columnCount + j; - float columnHeight = sinf( omegaX * j ); + cs = b3ComputeCosSin( omegaX * j ); + float columnHeight = cs.sine; heights[k] = rowHeight * columnHeight; } } diff --git a/vendor/box3d/src/src/hull.c b/vendor/box3d/src/src/hull.c index 75bace9f9..ea3ed7532 100644 --- a/vendor/box3d/src/src/hull.c +++ b/vendor/box3d/src/src/hull.c @@ -4,7 +4,7 @@ // Dirk Gregorius contributed portions of this code #include "algorithm.h" -#include "hull_map.h" +#include "hull.h" #include "math_internal.h" #include "shape.h" @@ -26,8 +26,8 @@ #define B3_MARK_VISIBLE 0 #define B3_MARK_DELETE 1 -// Final hull is index-encoded with uint8_t, so vertex/edge/face counts are capped at UINT8_MAX. -#define B3_HULL_LIMIT UINT8_MAX +// Final hull is index-encoded with uint8_t, so vertex/edge/face counts are capped at 256. +#define B3_HULL_MAX_COUNT ( UINT8_MAX + 1 ) typedef struct b3QHListNode { @@ -120,12 +120,16 @@ typedef struct b3HullBuilder b3QHHalfEdge* edgeBase; int edgeCapacity; int edgeCount; - b3QHHalfEdge* edgeFreeHead; // LIFO free list; overlays edge->next + + // LIFO free list. Built with edge->next. + b3QHHalfEdge* edgeFreeHead; b3QHFace* faceBase; int faceCapacity; int faceCount; - b3QHFace* faceFreeHead; // LIFO free list; overlays face->link.next + + // LIFO free list. Built with face->link.next. + b3QHFace* faceFreeHead; // Reusable scratch buffers. b3QHHalfEdge** horizon; @@ -145,7 +149,6 @@ typedef struct b3HullBuilder int horizonStackCapacity; // Final counts of the constructed hull (vertexList / faceList / half-edges around faces). - // Populated by CleanHull; zero until then. int finalVertexCount; int finalHalfEdgeCount; int finalFaceCount; @@ -300,17 +303,18 @@ static b3QHFace* b3HullBuilder_NewFace( b3HullBuilder* b, b3QHVertex* v1, b3QHVe return face; } -// Remove face from faceList if still linked, clear its edge pointer, then push onto faceFreeHead. -// Uses face->link.next as the free-list next pointer (link.prev stays NULL, so b3QHList_Contains -// returns false on a free slot, as required by the retire-guard in ResolveFaces). +// Remove face and add to free list. static void b3HullBuilder_RetireFace( b3HullBuilder* b, b3QHFace* face ) { + // Sometimes a cone face gets merged and never added to the list. if ( b3QHList_Contains( &face->link ) ) { b3QHList_Remove( &face->link ); } + face->edge = NULL; - // link.prev is already NULL after Remove (or was never set). link.next holds free-list ptr. + // link.prev is already NULL after Remove (or was never set). + B3_VALIDATE( face->link.prev == NULL ); face->link.next = (b3QHListNode*)b->faceFreeHead; b->faceFreeHead = face; } @@ -1232,7 +1236,7 @@ static void b3HullBuilder_ResolveVertices( b3HullBuilder* b ) static void b3HullBuilder_ResolveFaces( b3HullBuilder* b ) { // Splice deleted faces out of the face list. Faces already retired by AbsorbFaces are no - // longer on faceList, so we guard with b3QHList_Contains before removing. + // longer on faceList, so guard with b3QHList_Contains before removing. b3QHListNode* node = b->faceList.link.next; while ( node != &b->faceList.link ) { @@ -1242,7 +1246,21 @@ static void b3HullBuilder_ResolveFaces( b3HullBuilder* b ) if ( face->mark == B3_MARK_DELETE && b3QHList_Contains( &face->link ) ) { B3_ASSERT( B3_LIST_EMPTY( &face->conflictListHead.link ) ); - b3QHList_Remove( &face->link ); + + // Each half-edge is owned by exactly one face, so ring walks over the + // dead region retire every interior edge exactly once. Merge deleted + // faces are already off the face list. + b3QHHalfEdge* start = face->edge; + b3QHHalfEdge* edge = start; + do + { + b3QHHalfEdge* next = edge->next; + b3HullBuilder_RetireEdge( b, edge ); + edge = next; + } + while ( edge != start ); + + b3HullBuilder_RetireFace( b, face ); } } @@ -1421,12 +1439,13 @@ static bool b3HullBuilder_Construct( b3HullBuilder* b, const b3Vec3* points, int } b3HullBuilder_ComputeTolerance( b, pointCount, shiftedPoints ); - if ( !b3HullBuilder_BuildInitialHull( b, pointCount, shiftedPoints ) ) + bool haveInitialHull = b3HullBuilder_BuildInitialHull( b, pointCount, shiftedPoints ); + if ( haveInitialHull == false ) { return false; } - int budget = b3ClampInt( maxVertexCount - 4, 0, B3_HULL_LIMIT - 4 ); + int budget = b3ClampInt( maxVertexCount - 4, 0, B3_HULL_MAX_COUNT - 4 ); b3QHVertex* vertex = b3HullBuilder_NextConflictVertex( b ); while ( vertex && budget > 0 ) @@ -1447,8 +1466,10 @@ static bool b3HullBuilder_Construct( b3HullBuilder* b, const b3Vec3* points, int typedef struct b3HullWorkSizes { - int N; // pointCount - int M; // clamped maxVertexCount, in [4, B3_HULL_LIMIT] + // Input point count + int N; + // Output point limit + int M; int vertexCapacity; int edgeCapacity; int faceCapacity; @@ -1478,39 +1499,19 @@ static b3HullWorkSizes b3ComputeHullWorkSizes( int pointCount, int clampedMaxCou s.vertexCapacity = pointCount + 4; // Edges and faces use free-list recycling; capacity is proportional to live hull size. - // edgeCapacity: peak is ~twice live edges plus cone edges; floor 48. - s.edgeCapacity = 24 * s.M - 48; - if ( s.edgeCapacity < 48 ) - { - s.edgeCapacity = 48; - } + // edgeCapacity: peak is ~twice live edges plus cone edges. Minimum 48. + s.edgeCapacity = b3MaxInt( 48, 24 * s.M - 48 ); - // faceCapacity: peak intermediate state live faces (<=2*M-4) plus full cone (<=3*M-6); floor 16. - s.faceCapacity = 5 * s.M - 10; - if ( s.faceCapacity < 16 ) - { - s.faceCapacity = 16; - } + // faceCapacity: peak intermediate state live faces (<=2*M-4) plus full cone (<=3*M-6). Minimum 16. + s.faceCapacity = b3MaxInt( 16, 5 * s.M - 10 ); - // Horizon/cone bounded by current half-edge count; mergedFaces by face count. - s.horizonCapacity = 3 * s.M - 6; - if ( s.horizonCapacity < 6 ) - { - s.horizonCapacity = 6; - } + // Horizon/cone bounded by current half-edge count. Merged faces by face count. + s.horizonCapacity = b3MaxInt( 6, 3 * s.M - 6 ); s.coneCapacity = s.horizonCapacity; - s.mergedFacesCapacity = 2 * s.M - 4; - if ( s.mergedFacesCapacity < 4 ) - { - s.mergedFacesCapacity = 4; - } + s.mergedFacesCapacity = b3MaxInt( 4, 2 * s.M - 4 ); // Horizon DFS depth is bounded by the number of live faces (Euler: <=2*M-4). - s.horizonStackCapacity = 2 * s.M - 4; - if ( s.horizonStackCapacity < 4 ) - { - s.horizonStackCapacity = 4; - } + s.horizonStackCapacity = b3MaxInt( 4, 2 * s.M - 4 ); size_t offset = 0; @@ -1607,6 +1608,24 @@ static b3HullHalfEdge* b3GetHullEdgesWrite( b3HullData* hull ) return (b3HullHalfEdge*)( (intptr_t)hull + hull->edgeOffset ); } +static float* b3GetHullSoaVerticesWrite( b3HullData* hull ) +{ + if ( hull->soaVertexOffset == 0 ) + { + return NULL; + } + return (float*)( (intptr_t)hull + hull->soaVertexOffset ); +} + +static float* b3GetHullSoaNormalsWrite( b3HullData* hull ) +{ + if ( hull->soaNormalOffset == 0 ) + { + return NULL; + } + return (float*)( (intptr_t)hull + hull->soaNormalOffset ); +} + int b3FindHullSupportVertex( const b3HullData* hull, b3Vec3 direction ) { int bestIndex = B3_NULL_INDEX; @@ -2028,7 +2047,7 @@ b3HullData* b3CreateHull( const b3Vec3* points, int pointCount, int maxVertexCou } b3Vec3 origin = points[0]; - int clampedMaxCount = b3ClampInt( maxVertexCount, 4, B3_HULL_LIMIT ); + int clampedMaxCount = b3ClampInt( maxVertexCount, 4, B3_MAX_HULL_VERTICES ); // Single allocation for all working memory. b3HullWorkSizes sizes = b3ComputeHullWorkSizes( pointCount, clampedMaxCount ); @@ -2040,40 +2059,41 @@ b3HullData* b3CreateHull( const b3Vec3* points, int pointCount, int maxVertexCou b3Vec3* shiftedPoints = (b3Vec3*)( work + sizes.offsetShiftedPoints ); bool ok = b3HullBuilder_Construct( &builder, points, pointCount, clampedMaxCount, origin, shiftedPoints ); - if ( !ok ) + if ( ok == false ) { b3Free( work, sizes.totalBytes ); return NULL; } - if ( builder.finalVertexCount >= B3_HULL_LIMIT ) + if ( builder.finalVertexCount > B3_MAX_HULL_VERTICES ) { - b3Log( "hull final vertex count of %d exceeds limit of %d", builder.finalVertexCount, B3_HULL_LIMIT ); + b3Log( "hull final vertex count of %d exceeds limit of %d", builder.finalVertexCount, B3_MAX_HULL_VERTICES ); b3Free( work, sizes.totalBytes ); return NULL; } - if ( builder.finalFaceCount >= B3_HULL_LIMIT ) + if ( builder.finalFaceCount > B3_MAX_HULL_FACES ) { - b3Log( "hull final face count of %d exceeds limit of %d", builder.finalFaceCount, B3_HULL_LIMIT ); + b3Log( "hull final face count of %d exceeds limit of %d", builder.finalFaceCount, B3_MAX_HULL_FACES ); b3Free( work, sizes.totalBytes ); return NULL; } - if ( builder.finalHalfEdgeCount >= B3_HULL_LIMIT ) + int maxHalfEdgeCount = 2 * B3_MAX_HULL_EDGES; + if ( builder.finalHalfEdgeCount > maxHalfEdgeCount ) { - b3Log( "hull final half edge count of %d exceeds limit of %d", builder.finalHalfEdgeCount, B3_HULL_LIMIT ); + b3Log( "hull final half edge count of %d exceeds limit of %d", builder.finalHalfEdgeCount, maxHalfEdgeCount ); b3Free( work, sizes.totalBytes ); return NULL; } - // Walk lists into temp arrays bounded by B3_HULL_LIMIT, stamping finalIndex on each node so + // Walk lists into temp arrays bounded by B3_HULL_MAX_COUNT, stamping finalIndex on each node so // the resolution pass below is O(E + F) instead of O(E^2 + F^2). - const b3QHVertex* tempVertices[B3_HULL_LIMIT]; + const b3QHVertex* tempVertices[B3_HULL_MAX_COUNT]; int vertexCount = 0; for ( b3QHListNode* node = builder.vertexList.link.next; node != &builder.vertexList.link; node = node->next ) { - B3_ASSERT( vertexCount <= B3_HULL_LIMIT - 1 ); + B3_ASSERT( vertexCount < B3_HULL_MAX_COUNT ); b3QHVertex* vertex = (b3QHVertex*)node; vertex->finalIndex = vertexCount; @@ -2081,15 +2101,14 @@ b3HullData* b3CreateHull( const b3Vec3* points, int pointCount, int maxVertexCou } // Collect edges in twin-paired order (i, i+1) by stamping each pair as we discover it. - // Replaces b3SortEdges' O(E^2) twin pairing. - const b3QHFace* tempFaces[B3_HULL_LIMIT]; - const b3QHHalfEdge* tempEdges[B3_HULL_LIMIT]; + const b3QHFace* tempFaces[B3_HULL_MAX_COUNT]; + const b3QHHalfEdge* tempEdges[B3_HULL_MAX_COUNT]; int faceCount = 0; int edgeCount = 0; for ( b3QHListNode* faceNode = builder.faceList.link.next; faceNode != &builder.faceList.link; faceNode = faceNode->next ) { - B3_ASSERT( faceCount <= B3_HULL_LIMIT - 1 ); + B3_ASSERT( faceCount < B3_HULL_MAX_COUNT ); b3QHFace* face = (b3QHFace*)faceNode; face->finalIndex = faceCount; @@ -2100,7 +2119,7 @@ b3HullData* b3CreateHull( const b3Vec3* points, int pointCount, int maxVertexCou { if ( edge->finalIndex < 0 ) { - B3_ASSERT( edgeCount + 1 <= B3_HULL_LIMIT - 1 ); + B3_ASSERT( edgeCount + 1 < B3_HULL_MAX_COUNT ); edge->finalIndex = edgeCount; tempEdges[edgeCount++] = edge; @@ -2112,6 +2131,9 @@ b3HullData* b3CreateHull( const b3Vec3* points, int pointCount, int maxVertexCou while ( edge != face->edge ); } + int soaVertexCount = ( vertexCount + 3 ) & ~3; + int soaNormalCount = ( faceCount + 3 ) & ~3; + // Allocate the hull. Arrays hang off the end. size_t byteCount = b3AlignUp8( sizeof( b3HullData ) ); int vertexOffset = (int)byteCount; @@ -2120,10 +2142,14 @@ b3HullData* b3CreateHull( const b3Vec3* points, int pointCount, int maxVertexCou byteCount += b3AlignUp8( vertexCount * (int)sizeof( b3Vec3 ) ); int edgeOffset = (int)byteCount; byteCount += b3AlignUp8( edgeCount * (int)sizeof( b3HullHalfEdge ) ); - int faceOffset = (int)byteCount; - byteCount += b3AlignUp8( faceCount * (int)sizeof( b3HullFace ) ); int planeOffset = (int)byteCount; byteCount += b3AlignUp8( faceCount * (int)sizeof( b3Plane ) ); + int faceOffset = (int)byteCount; + byteCount += b3AlignUp8( faceCount * (int)sizeof( b3HullFace ) ); + int soaVertexOffset = (int)byteCount; + byteCount += b3AlignUp8( 3 * soaVertexCount * (int)sizeof( float ) ); + int soaNormalOffset = (int)byteCount; + byteCount += b3AlignUp8( 3 * soaNormalCount * (int)sizeof( float ) ); b3HullData* hull = b3Alloc( byteCount ); memset( hull, 0, byteCount ); @@ -2132,8 +2158,10 @@ b3HullData* b3CreateHull( const b3Vec3* points, int pointCount, int maxVertexCou hull->vertexOffset = vertexOffset; hull->pointOffset = pointOffset; hull->edgeOffset = edgeOffset; - hull->faceOffset = faceOffset; hull->planeOffset = planeOffset; + hull->faceOffset = faceOffset; + hull->soaVertexOffset = soaVertexOffset; + hull->soaNormalOffset = soaNormalOffset; hull->vertexCount = vertexCount; hull->edgeCount = edgeCount; @@ -2146,11 +2174,29 @@ b3HullData* b3CreateHull( const b3Vec3* points, int pointCount, int maxVertexCou b3HullFace* faces = (b3HullFace*)( (intptr_t)hull + hull->faceOffset ); b3Vec3* finalPoints = b3GetHullPointsWrite( hull ); b3Plane* planes = b3GetHullPlanesWrite( hull ); + float* soaVertices = b3GetHullSoaVerticesWrite( hull ); + float* soaNormals = b3GetHullSoaNormalsWrite( hull ); + + float* vx = soaVertices; + float* vy = vx + soaVertexCount; + float* vz = vy + soaVertexCount; for ( int index = 0; index < vertexCount; ++index ) { vertices[index].edge = 0; - finalPoints[index] = tempVertices[index]->position; + b3Vec3 p = tempVertices[index]->position; + finalPoints[index] = p; + + vx[index] = p.x; + vy[index] = p.y; + vz[index] = p.z; + } + + for ( int index = vertexCount; index < soaVertexCount; ++index ) + { + vx[index] = vx[0]; + vy[index] = vy[0]; + vz[index] = vz[0]; } for ( int index = 0; index < edgeCount; ++index ) @@ -2169,6 +2215,10 @@ b3HullData* b3CreateHull( const b3Vec3* points, int pointCount, int maxVertexCou vertices[edge->origin->finalIndex].edge = (uint8_t)index; } + float* nx = soaNormals; + float* ny = nx + soaNormalCount; + float* nz = ny + soaNormalCount; + for ( int index = 0; index < faceCount; ++index ) { const b3QHFace* face = tempFaces[index]; @@ -2176,6 +2226,18 @@ b3HullData* b3CreateHull( const b3Vec3* points, int pointCount, int maxVertexCou faces[index].edge = (uint8_t)face->edge->finalIndex; planes[index] = face->plane; + + b3Vec3 n = face->plane.normal; + nx[index] = n.x; + ny[index] = n.y; + nz[index] = n.z; + } + + for ( int index = faceCount; index < soaNormalCount; ++index ) + { + nx[index] = 0.0f; + ny[index] = 0.0f; + nz[index] = 0.0f; } // All builder pointers are dead from here on. @@ -2238,9 +2300,10 @@ bool b3CompareHullData( const b3HullData* hull1, const b3HullData* hull2 ) // Hull identity covers every byte, so the structs carry explicit padding. These lock // the layout, re-audit padding if a size changes. -_Static_assert( sizeof( b3HullData ) == 136, "unexpected hull data size" ); -_Static_assert( sizeof( b3BoxHull ) == 440, "unexpected box hull size" ); +_Static_assert( sizeof( b3HullData ) == 144, "unexpected hull data size" ); +_Static_assert( sizeof( b3BoxHull ) == 648, "unexpected box hull size" ); +// Implement b3HullMap. #define NAME b3HullMap #define KEY_TY const b3HullData* #define VAL_TY int @@ -2269,7 +2332,7 @@ b3HullData* b3CloneAndTransformHull( const b3HullData* original, b3Transform tra return NULL; } - b3HullData* hull = (b3HullData*)b3Alloc( original->byteCount ); + b3HullData* hull = b3Alloc( original->byteCount ); memcpy( hull, original, original->byteCount ); b3Vec3 safeScale = b3SafeScale( scale ); @@ -2286,9 +2349,9 @@ b3HullData* b3CloneAndTransformHull( const b3HullData* original, b3Transform tra { const b3HullFace* face = faces + i; - uint8_t startEdgeIndex = face->edge; - uint8_t currentEdgeIndex = startEdgeIndex; - uint8_t prevEdgeIndex = UINT8_MAX; + int startEdgeIndex = face->edge; + int currentEdgeIndex = startEdgeIndex; + int prevEdgeIndex = B3_NULL_INDEX; do { @@ -2304,7 +2367,7 @@ b3HullData* b3CloneAndTransformHull( const b3HullData* original, b3Transform tra } while ( currentEdgeIndex != startEdgeIndex ); - B3_ASSERT( prevEdgeIndex != UINT8_MAX ); + B3_ASSERT( prevEdgeIndex != B3_NULL_INDEX ); currentEdgeIndex = startEdgeIndex; @@ -2312,7 +2375,7 @@ b3HullData* b3CloneAndTransformHull( const b3HullData* original, b3Transform tra { b3HullHalfEdge* edge = edges + currentEdgeIndex; uint8_t nextIndex = edge->next; - edge->next = prevEdgeIndex; + edge->next = (uint8_t)prevEdgeIndex; if ( currentEdgeIndex < edge->twin ) { @@ -2339,12 +2402,32 @@ b3HullData* b3CloneAndTransformHull( const b3HullData* original, b3Transform tra b3Matrix3 matrix = b3MakeMatrixFromQuat( transform.q ); b3Vec3* points = b3GetHullPointsWrite( hull ); + int soaVertexCount = ( vertexCount + 3 ) & ~3; + float* vx = b3GetHullSoaVerticesWrite( hull ); + float* vy = vx + soaVertexCount; + float* vz = vy + soaVertexCount; + for ( int i = 0; i < vertexCount; ++i ) { - points[i] = b3Add( b3MulMV( matrix, b3Mul( safeScale, points[i] ) ), transform.p ); + b3Vec3 p = b3Add( b3MulMV( matrix, b3Mul( safeScale, points[i] ) ), transform.p ); + points[i] = p; + vx[i] = p.x; + vy[i] = p.y; + vz[i] = p.z; + } + + for ( int i = vertexCount; i < soaVertexCount; ++i ) + { + vx[i] = vx[0]; + vy[i] = vy[0]; + vz[i] = vz[0]; } b3Plane* planes = b3GetHullPlanesWrite( hull ); + int soaNormalCount = ( faceCount + 3 ) & ~3; + float* nx = b3GetHullSoaNormalsWrite( hull ); + float* ny = nx + soaNormalCount; + float* nz = ny + soaNormalCount; for ( int i = 0; i < faceCount; ++i ) { @@ -2390,6 +2473,17 @@ b3HullData* b3CloneAndTransformHull( const b3HullData* original, b3Transform tra normal = b3MulSV( 1.0f / area, normal ); planes[i] = b3MakePlaneFromNormalAndPoint( normal, centroid ); + + nx[i] = normal.x; + ny[i] = normal.y; + nz[i] = normal.z; + } + + for ( int i = faceCount; i < soaNormalCount; ++i ) + { + nx[i] = 0.0f; + ny[i] = 0.0f; + nz[i] = 0.0f; } b3UpdateHullBounds( hull ); @@ -2641,8 +2735,10 @@ static const b3BoxHull s_boxHull = { .vertexOffset = offsetof( b3BoxHull, boxVertices ), .pointOffset = offsetof( b3BoxHull, boxPoints ), .edgeOffset = offsetof( b3BoxHull, boxEdges ), - .faceOffset = offsetof( b3BoxHull, boxFaces ), .planeOffset = offsetof( b3BoxHull, boxPlanes ), + .faceOffset = offsetof( b3BoxHull, boxFaces ), + .soaVertexOffset = offsetof( b3BoxHull, vx ), + .soaNormalOffset = offsetof( b3BoxHull, nx ), }, .boxVertices = { @@ -2677,6 +2773,8 @@ static const b3BoxHull s_boxHull = { b3BoxHull b3MakeTransformedBoxHull( float hx, float hy, float hz, b3Transform transform ) { + B3_ASSERT( b3IsValidTransform( transform ) ); + b3BoxHull boxHull = s_boxHull; float minH = 0.2f * B3_LINEAR_SLOP; @@ -2710,6 +2808,61 @@ b3BoxHull b3MakeTransformedBoxHull( float hx, float hy, float hz, b3Transform tr boxHull.boxPoints[6] = b3TransformPoint( transform, (b3Vec3){ -h.x, -h.y, -h.z } ); boxHull.boxPoints[7] = b3TransformPoint( transform, (b3Vec3){ h.x, -h.y, -h.z } ); + // SOA + boxHull.vx[0] = boxHull.boxPoints[0].x; + boxHull.vx[1] = boxHull.boxPoints[1].x; + boxHull.vx[2] = boxHull.boxPoints[2].x; + boxHull.vx[3] = boxHull.boxPoints[3].x; + boxHull.vx[4] = boxHull.boxPoints[4].x; + boxHull.vx[5] = boxHull.boxPoints[5].x; + boxHull.vx[6] = boxHull.boxPoints[6].x; + boxHull.vx[7] = boxHull.boxPoints[7].x; + + boxHull.vy[0] = boxHull.boxPoints[0].y; + boxHull.vy[1] = boxHull.boxPoints[1].y; + boxHull.vy[2] = boxHull.boxPoints[2].y; + boxHull.vy[3] = boxHull.boxPoints[3].y; + boxHull.vy[4] = boxHull.boxPoints[4].y; + boxHull.vy[5] = boxHull.boxPoints[5].y; + boxHull.vy[6] = boxHull.boxPoints[6].y; + boxHull.vy[7] = boxHull.boxPoints[7].y; + + boxHull.vz[0] = boxHull.boxPoints[0].z; + boxHull.vz[1] = boxHull.boxPoints[1].z; + boxHull.vz[2] = boxHull.boxPoints[2].z; + boxHull.vz[3] = boxHull.boxPoints[3].z; + boxHull.vz[4] = boxHull.boxPoints[4].z; + boxHull.vz[5] = boxHull.boxPoints[5].z; + boxHull.vz[6] = boxHull.boxPoints[6].z; + boxHull.vz[7] = boxHull.boxPoints[7].z; + + boxHull.nx[0] = boxHull.boxPlanes[0].normal.x; + boxHull.nx[1] = boxHull.boxPlanes[1].normal.x; + boxHull.nx[2] = boxHull.boxPlanes[2].normal.x; + boxHull.nx[3] = boxHull.boxPlanes[3].normal.x; + boxHull.nx[4] = boxHull.boxPlanes[4].normal.x; + boxHull.nx[5] = boxHull.boxPlanes[5].normal.x; + boxHull.nx[6] = 0.0f; + boxHull.nx[7] = 0.0f; + + boxHull.ny[0] = boxHull.boxPlanes[0].normal.y; + boxHull.ny[1] = boxHull.boxPlanes[1].normal.y; + boxHull.ny[2] = boxHull.boxPlanes[2].normal.y; + boxHull.ny[3] = boxHull.boxPlanes[3].normal.y; + boxHull.ny[4] = boxHull.boxPlanes[4].normal.y; + boxHull.ny[5] = boxHull.boxPlanes[5].normal.y; + boxHull.ny[6] = 0.0f; + boxHull.ny[7] = 0.0f; + + boxHull.nz[0] = boxHull.boxPlanes[0].normal.z; + boxHull.nz[1] = boxHull.boxPlanes[1].normal.z; + boxHull.nz[2] = boxHull.boxPlanes[2].normal.z; + boxHull.nz[3] = boxHull.boxPlanes[3].normal.z; + boxHull.nz[4] = boxHull.boxPlanes[4].normal.z; + boxHull.nz[5] = boxHull.boxPlanes[5].normal.z; + boxHull.nz[6] = 0.0f; + boxHull.nz[7] = 0.0f; + boxHull.base.hash = 0; boxHull.base.hash = b3NonZeroHash( b3Hash( B3_HASH_INIT, (uint8_t*)&boxHull, sizeof( b3BoxHull ) ) ); @@ -2784,6 +2937,8 @@ void b3ScaleBox( b3Vec3* halfWidths, b3Transform* transform, b3Vec3 postScale, f // todo use new hull scaling technique b3BoxHull b3MakeScaledBoxHull( b3Vec3 halfWidths, b3Transform transform, b3Vec3 postScale ) { + B3_ASSERT( b3IsValidTransform( transform ) ); + b3Vec3 h = halfWidths; b3Transform xf = transform; b3ScaleBox( &h, &xf, postScale, 4.0f * B3_LINEAR_SLOP ); diff --git a/vendor/box3d/src/src/hull_map.h b/vendor/box3d/src/src/hull.h similarity index 77% rename from vendor/box3d/src/src/hull_map.h rename to vendor/box3d/src/src/hull.h index 5b50c56eb..a1077bb52 100644 --- a/vendor/box3d/src/src/hull_map.h +++ b/vendor/box3d/src/src/hull.h @@ -12,8 +12,8 @@ uint64_t b3HashHullData( const b3HullData* hull ); bool b3CompareHullData( const b3HullData* hull1, const b3HullData* hull2 ); -// Map keyed by hull content. The world hull database stores a reference count, -// compound baking stores a byte offset. Implementation lives in hull.c. +// Map keyed by hull content. The world hull database uses the value as a reference count, +// while compound baking stores uses the value as a byte offset. Implementation is in hull.c. #define NAME b3HullMap #define KEY_TY const b3HullData* #define VAL_TY int diff --git a/vendor/box3d/src/src/island.c b/vendor/box3d/src/src/island.c index 625a1b345..6a1790923 100644 --- a/vendor/box3d/src/src/island.c +++ b/vendor/box3d/src/src/island.c @@ -498,7 +498,7 @@ void b3SplitIsland( b3World* world, int baseId ) } } - // Early return — island is still fully connected, no split needed. + // Island is still fully connected, no split needed. if ( componentCount == 1 ) { baseIsland->constraintRemoveCount = 0; diff --git a/vendor/box3d/src/src/manifold.c b/vendor/box3d/src/src/manifold.c index 40e60ad57..f15db6c03 100644 --- a/vendor/box3d/src/src/manifold.c +++ b/vendor/box3d/src/src/manifold.c @@ -10,58 +10,6 @@ #include "box3d/math_functions.h" -// p1 : origin on edge 1 -// e1 : edge 1 -// c1 : shape 1 centroid -// p2 : origin on edge 2 -// e2 : edge 2 -// c2 : shape 2 centroid -float b3EdgeEdgeSeparation( b3Vec3 p1, b3Vec3 e1, b3Vec3 c1, b3Vec3 p2, b3Vec3 e2, b3Vec3 c2 ) -{ - // Build search direction - b3Vec3 u = b3Cross( e1, e2 ); - float length = b3Length( u ); - - // Skip near parallel edges: |e1 x e1| = sin(alpha) * |e1| * |e2| - const float kTolerance = 0.005f; - if ( length < kTolerance * sqrtf( b3LengthSquared( e1 ) * b3LengthSquared( e2 ) ) ) - { - return -FLT_MAX; - } - - if ( length * length < 1000.0f * FLT_MIN ) - { - return -FLT_MAX; - } - - b3Vec3 n = b3MulSV( 1.0f / length, u ); - - // Make sure normal points away from the first shape - // For a triangle, it is possible that N is aligned with the triangle normal and the sign - // value can be close to zero and flicker between small negative and positive values, leading to - // an incorrect separation value. So we assume the other hull has some volume and pick the most - // significant sign value to orient N. - float sign1 = b3Dot( n, b3Sub( p1, c1 ) ); - float sign2 = b3Dot( n, b3Sub( p2, c2 ) ); - if ( b3AbsFloat( sign1 ) > b3AbsFloat( sign2 ) ) - { - if ( sign1 < 0.0f ) - { - n = b3Neg( n ); - } - } - else - { - if ( sign2 > 0.0f ) - { - n = b3Neg( n ); - } - } - - // s = Dot(n, p2) - d = Dot(n, p2) - Dot(n, p1) = Dot(n, p2 - p1) - return b3Dot( n, b3Sub( p2, p1 ) ); -} - // This was extended to make the wedge shape get the correct incident face. // Instead of looking directly for the most anti-parallel face, we first find the closest vertex (passed in). // Then we look for all edges coming out of that vertex and look for the edge that is @@ -69,6 +17,8 @@ float b3EdgeEdgeSeparation( b3Vec3 p1, b3Vec3 e1, b3Vec3 c1, b3Vec3 p2, b3Vec3 e // Then from that edge, we select the adjacent face that is most anti-parallel to the reference normal. int b3FindIncidentFace( const b3HullData* hull, b3Vec3 refNormal, int vertexIndex ) { + B3_ASSERT( 0 <= vertexIndex && vertexIndex < hull->vertexCount ); + const b3HullVertex* vertices = b3GetHullVertices( hull ); const b3HullHalfEdge* edges = b3GetHullEdges( hull ); const b3Plane* planes = b3GetHullPlanes( hull ); diff --git a/vendor/box3d/src/src/manifold.h b/vendor/box3d/src/src/manifold.h index 9e529ab97..fca3cde0b 100644 --- a/vendor/box3d/src/src/manifold.h +++ b/vendor/box3d/src/src/manifold.h @@ -8,19 +8,22 @@ #define B3_MAX_CLIP_POINTS 64 -typedef struct b3FaceQuery -{ - float separation; - int faceIndex; - int vertexIndex; -} b3FaceQuery; - -typedef struct b3EdgeQuery +typedef struct b3SeparatingAxis { + b3Vec3 normal; float separation; int indexA; int indexB; -} b3EdgeQuery; + b3SeparatingFeature type; +} b3SeparatingAxis; + +typedef struct b3AxisQuery +{ + b3SeparatingAxis faceA; + b3SeparatingAxis faceB; + b3SeparatingAxis edge; + b3SeparatingFeature separatedFeature; +} b3AxisQuery; typedef struct b3ClipVertex { @@ -35,7 +38,6 @@ typedef enum b3FeatureOwner b3_featureShapeB = 1 } b3FeatureOwner; -float b3EdgeEdgeSeparation( b3Vec3 p1, b3Vec3 e1, b3Vec3 c1, b3Vec3 p2, b3Vec3 e2, b3Vec3 c2 ); int b3FindIncidentFace( const b3HullData* hull, b3Vec3 refNormal, int vertexIndex ); b3FeaturePair b3MakeFeaturePair( b3FeatureOwner owner1, int index1, b3FeatureOwner owner2, int index2 ); @@ -43,6 +45,8 @@ b3FeaturePair b3FlipPair( b3FeaturePair pair ); int b3ClipPolygon( b3ClipVertex* out, b3ClipVertex* polygon, int count, b3Plane clipPlane, int edge, b3Plane refPlane ); +b3AxisQuery b3ComputeSeparatingAxis( const b3HullData* hullA, const b3HullData* hullB, b3Transform xfB, bool earlyReturn ); + #if B3_ENABLE_VALIDATION bool b3ValidatePolygon( b3ClipVertex* polygon, int count ); #endif @@ -55,3 +59,27 @@ static inline uint32_t b3MakeFeatureId( b3FeaturePair pair ) return ( (uint32_t)pair.owner1 << 24 ) | ( (uint32_t)pair.index1 << 16 ) | ( (uint32_t)pair.owner2 << 8 ) | (uint32_t)pair.index2; } + +static inline b3SeparatingAxis b3GetBestAxis( const b3AxisQuery* query ) +{ + B3_VALIDATE( query->faceA.type == b3_faceAxisA ); + B3_VALIDATE( query->edge.type == b3_edgePairAxis ); + B3_VALIDATE( query->faceB.type == b3_faceAxisB ); + + if ( query->faceA.separation > query->faceB.separation ) + { + if ( query->edge.separation > query->faceA.separation ) + { + return query->edge; + } + + return query->faceA; + } + + if ( query->edge.separation > query->faceB.separation ) + { + return query->edge; + } + + return query->faceB; +} diff --git a/vendor/box3d/src/src/math_internal.h b/vendor/box3d/src/src/math_internal.h index 4d152c5fc..d975c65dc 100644 --- a/vendor/box3d/src/src/math_internal.h +++ b/vendor/box3d/src/src/math_internal.h @@ -61,6 +61,7 @@ b3Matrix3 b3BoxInertia( float mass, b3Vec3 min, b3Vec3 max ); int b3GetProxySupport( const b3ShapeProxy* proxy, b3Vec3 axis ); int b3GetPointSupport( const b3Vec3* points, int count, b3Vec3 axis ); +// Align up to 8 byte alignment. static inline size_t b3AlignUp8( size_t x ) { return ( x + 7u ) & ~(size_t)7u; diff --git a/vendor/box3d/src/src/mesh.c b/vendor/box3d/src/src/mesh.c index c03489955..acfbda0e1 100644 --- a/vendor/box3d/src/src/mesh.c +++ b/vendor/box3d/src/src/mesh.c @@ -928,7 +928,8 @@ static int b3BuildRecursive( b3Array( b3MeshNode ) * nodes, int count, b3Primiti node->data.asNode.childOffset = rightIndex - index; node->lowerBound = aabb.lowerBound; node->upperBound = aabb.upperBound; - // triangleOffset is leaf-only, but lives outside the union — zero it so mesh->hash is deterministic + + // Zero so mesh->hash is deterministic node->triangleOffset = 0; return index; @@ -1321,14 +1322,17 @@ b3MeshData* b3CreateWaveMesh( int xCount, int zCount, float cellWidth, float amp float omegaX = 2.0f * B3_PI * columnFrequency * cellWidth; float x = -0.5f * xWidth; + b3CosSin cs = { 0 }; for ( int ix = 0; ix <= xCount; ++ix ) { - float rowHeight = sinf( omegaX * ix ); + cs = b3ComputeCosSin( omegaX * ix ); + float rowHeight = cs.sine; float z = -0.5f * zWidth; for ( int iz = 0; iz <= zCount; ++iz ) { - float columnHeight = sinf( omegaZ * iz ); + cs = b3ComputeCosSin( omegaZ * iz ); + float columnHeight = cs.sine; float y = amplitude * rowHeight * columnHeight; vertices.data[index] = (b3Vec3){ x, y, z }; @@ -1482,7 +1486,7 @@ b3MeshData* b3CreateBoxMesh( b3Vec3 center, b3Vec3 extent, bool identifyEdges ) return b3CreateMesh( &def, NULL, 0 ); } -b3MeshData* b3CreateHollowBoxMesh(b3Vec3 center, b3Vec3 extent) +b3MeshData* b3CreateHollowBoxMesh( b3Vec3 center, b3Vec3 extent ) { float x = extent.x; float y = extent.y; diff --git a/vendor/box3d/src/src/mesh_contact.c b/vendor/box3d/src/src/mesh_contact.c index a7163477a..60158e4b9 100644 --- a/vendor/box3d/src/src/mesh_contact.c +++ b/vendor/box3d/src/src/mesh_contact.c @@ -608,7 +608,7 @@ bool b3ComputeMeshManifolds( b3World* world, int workerIndex, b3Contact* contact switch ( shapeB->type ) { case b3_capsuleShape: - b3CollideCapsuleAndTriangle( manifold, pointCapacity, &shapeB->capsule, vertices, &cache->simplexCache ); + b3CollideTriangleAndCapsule( manifold, pointCapacity, vertices, &shapeB->capsule, &cache->simplexCache ); break; case b3_hullShape: @@ -619,14 +619,14 @@ bool b3ComputeMeshManifolds( b3World* world, int workerIndex, b3Contact* contact cache->satCache = (b3SATCache){ 0 }; } - b3CollideHullAndTriangle( manifold, pointCapacity, hullB, vertices[0], vertices[1], vertices[2], triangle.flags, + b3CollideTriangleAndHull( manifold, pointCapacity, vertices[0], vertices[1], vertices[2], triangle.flags, hullB, &cache->satCache, enableSpeculative ); context->satCallCount += 1; context->satCacheHitCount += cache->satCache.hit; break; case b3_sphereShape: - b3CollideSphereAndTriangle( manifold, pointCapacity, &shapeB->sphere, vertices ); + b3CollideTriangleAndSphere( manifold, pointCapacity, vertices, &shapeB->sphere ); break; default: diff --git a/vendor/box3d/src/src/parallel_for.c b/vendor/box3d/src/src/parallel_for.c index 07d1c3fdc..c16ac18f0 100644 --- a/vendor/box3d/src/src/parallel_for.c +++ b/vendor/box3d/src/src/parallel_for.c @@ -77,7 +77,8 @@ void b3ParallelFor( b3World* world, b3ParallelForCallback* callback, int itemCou // Target multiple blocks per worker to reduce thread stalls. // block size grows once items exceed maxBlockCount * minRange // so the block count stays bounded and per-block sync overhead stays low. - int blocksPerWorker = 4; + // Benchmarking shows 32 is optimal for the convex pile benchmark and others. + int blocksPerWorker = 32; int maxBlockCount = blocksPerWorker * workerCount; int blockSize; diff --git a/vendor/box3d/src/src/physics_world.c b/vendor/box3d/src/src/physics_world.c index f0dc19e2d..4309e085b 100644 --- a/vendor/box3d/src/src/physics_world.c +++ b/vendor/box3d/src/src/physics_world.c @@ -11,7 +11,7 @@ #include "contact.h" #include "core.h" #include "ctz.h" -#include "hull_map.h" +#include "hull.h" #include "island.h" #include "joint.h" #include "parallel_for.h" @@ -41,18 +41,21 @@ const b3HullData* b3AddHullToDatabase( b3World* world, const b3HullData* src ) { b3HullMap* database = world->hullDatabase; - // Compare by content so an unowned query hull finds the shared copy. + // Compare by content to de-duplicate. Not trusting the hash. b3HullMap_itr itr = b3HullMap_get( database, src ); if ( b3HullMap_is_end( itr ) == false ) { + // Bump reference count. itr.data->val += 1; return itr.data->key; } - b3HullData* owned = b3CloneHull( src ); - B3_ASSERT( owned != NULL ); - b3HullMap_insert( database, owned, 1 ); - return owned; + b3HullData* clone = b3CloneHull( src ); + B3_ASSERT( clone != NULL ); + + // Start with reference count of 1. + b3HullMap_insert( database, clone, 1 ); + return clone; } const b3HullData* b3AddOwnedHullToDatabase( b3World* world, b3HullData* owned ) @@ -1426,20 +1429,20 @@ void b3World_Draw( b3WorldId worldId, b3DebugDraw* draw, uint64_t maskBits ) const char* name = b3FindName( &world->names, body->nameId ); if ( name != NULL ) { - draw->DrawStringFcn( p, name, b3_colorOrange, draw->context ); + draw->DrawStringFcn( p, name, b3_colorWhite, draw->context ); } } if ( draw->drawMass && body->type == b3_dynamicBody ) { - b3Vec3 offset = { 0.1f, 0.1f, 0.1f }; + b3Vec3 offset = { 0.05f, 0.05f, 0.05f }; b3WorldTransform transform = { bodySim->center, bodySim->transform.q }; draw->DrawTransformFcn( transform, draw->context ); b3Pos p = b3TransformWorldPoint( transform, offset ); char buffer[32]; - snprintf( buffer, 32, " %.2f", body->mass ); + snprintf( buffer, 32, "%.2f", body->mass ); draw->DrawStringFcn( p, buffer, b3_colorWhite, draw->context ); } @@ -1523,8 +1526,10 @@ void b3World_Draw( b3WorldId worldId, b3DebugDraw* draw, uint64_t maskBits ) b3Vec3 normal = manifold->normal; // Average the anchors not the world points so the friction center stays exact far from the origin - b3Pos contactCenter = draw->drawAnchorA == 1 ? bodySimA->center : bodySimB->center; - b3Vec3 anchorSum = b3Vec3_zero; + b3Pos contactCenter = draw->drawAnchorA ? bodySimA->center : bodySimB->center; + b3Vec3 frictionAnchor = b3Vec3_zero; + float totalWeight = 0.0f; + float invTau = 1.0f / B3_SPECULATIVE_DISTANCE; const b3ManifoldPoint* points = manifold->points; for ( int pointIndex = 0; pointIndex < manifold->pointCount; ++pointIndex ) @@ -1533,10 +1538,13 @@ void b3World_Draw( b3WorldId worldId, b3DebugDraw* draw, uint64_t maskBits ) char buffer[32]; - b3Vec3 anchor = draw->drawAnchorA == 1 ? mp->anchorA : mp->anchorB; + b3Vec3 anchor = draw->drawAnchorA ? mp->anchorA : mp->anchorB; b3Pos p = b3OffsetPos( contactCenter, anchor ); - anchorSum = b3Add( anchorSum, anchor ); + // See similar friction anchor weights in b3PrepareContacts_Mesh. + float weight = b3ClampFloat( 2.0f - mp->separation * invTau, B3_MIN_FRICTION_WEIGHT, 1.0f ); + frictionAnchor = b3MulAdd( frictionAnchor, weight, anchor ); + totalWeight += weight; if ( draw->drawContactNormals ) { @@ -1599,9 +1607,8 @@ void b3World_Draw( b3WorldId worldId, b3DebugDraw* draw, uint64_t maskBits ) { // Hack inv_dt for single step debugging float inv_dt = world->inv_dt > 0.0f ? world->inv_dt : 60.0f; - - b3Vec3 avgAnchor = b3MulSV( 1.0f / manifold->pointCount, anchorSum ); - b3Pos p1 = b3OffsetPos( contactCenter, avgAnchor ); + frictionAnchor = b3MulSV( 1.0f / totalWeight, frictionAnchor ); + b3Pos p1 = b3OffsetPos( contactCenter, frictionAnchor ); b3Vec3 frictionForce = b3MulSV( 0.5f * inv_dt, manifold->frictionImpulse ); b3Pos p2 = b3OffsetPos( p1, b3MulSV( draw->forceScale, frictionForce ) ); draw->DrawSegmentFcn( p1, p2, frictionColor, draw->context ); diff --git a/vendor/box3d/src/src/physics_world.h b/vendor/box3d/src/src/physics_world.h index 12f169561..8bd3df865 100644 --- a/vendor/box3d/src/src/physics_world.h +++ b/vendor/box3d/src/src/physics_world.h @@ -178,7 +178,7 @@ typedef struct b3World b3Array( b3Shape ) shapes; // Reference counted store of shared hull data keyed by content. Shapes hold a - // pointer to the owned copy here. Opaque to avoid leaking the verstable map + // pointer to the hull stored in the db. Type erased to avoid leaking the verstable map // type into this header. void* hullDatabase; diff --git a/vendor/box3d/src/src/recording.c b/vendor/box3d/src/src/recording.c index 4d5c5995d..341ddbf0b 100644 --- a/vendor/box3d/src/src/recording.c +++ b/vendor/box3d/src/src/recording.c @@ -799,23 +799,15 @@ uint64_t b3Hash64Blob( const uint8_t* bytes, int n ) static uint32_t b3RegistryPush( b3GeometryRegistry* reg, b3GeometryHashMap* map, b3GeometryHashMap_itr itr, bool hashPresent, b3GeometryKind kind, uint64_t contentHash, uint8_t* bytes, int byteCount ) { - if ( reg->count >= reg->capacity ) - { - int newCap = reg->capacity < 8 ? 8 : reg->capacity * 2; - reg->entries = (b3GeometryEntry*)b3GrowAlloc( reg->entries, reg->capacity * (int)sizeof( b3GeometryEntry ), - newCap * (int)sizeof( b3GeometryEntry ) ); - reg->capacity = newCap; - } - - uint32_t id = (uint32_t)reg->count; - b3GeometryEntry* entry = reg->entries + reg->count; + uint32_t id = (uint32_t)reg->entries.count; + b3GeometryEntry* entry = b3Array_Emplace( reg->entries ); entry->contentHash = contentHash; entry->id = id; entry->kind = kind; entry->byteCount = byteCount; - entry->bytes = bytes; // take ownership + // Take ownership. + entry->bytes = bytes; entry->hashNext = hashPresent ? (int)itr.data->val : B3_NULL_INDEX; - reg->count++; if ( hashPresent ) { @@ -832,11 +824,11 @@ static b3GeometryHashMap* b3RegistryMap( b3GeometryRegistry* reg ) { if ( reg->dedupMap == NULL ) { - b3GeometryHashMap* fresh = (b3GeometryHashMap*)b3Alloc( sizeof( b3GeometryHashMap ) ); + b3GeometryHashMap* fresh = b3Alloc( sizeof( b3GeometryHashMap ) ); b3GeometryHashMap_init( fresh ); reg->dedupMap = fresh; } - return (b3GeometryHashMap*)reg->dedupMap; + return reg->dedupMap; } uint32_t b3InternGeometry( b3GeometryRegistry* reg, b3GeometryKind kind, uint64_t contentHash, uint8_t* bytes, int byteCount ) @@ -848,12 +840,12 @@ uint32_t b3InternGeometry( b3GeometryRegistry* reg, b3GeometryKind kind, uint64_ if ( hashPresent ) { // Walk every entry sharing this hash so a collision still finds the identical blob. - for ( int idx = (int)itr.data->val; idx != B3_NULL_INDEX; idx = reg->entries[idx].hashNext ) + for ( int index = (int)itr.data->val; index != B3_NULL_INDEX; index = reg->entries.data[index].hashNext ) { - b3GeometryEntry* e = reg->entries + idx; + b3GeometryEntry* e = reg->entries.data + index; if ( e->byteCount == byteCount && memcmp( e->bytes, bytes, (size_t)byteCount ) == 0 ) { - // Duplicate: the caller transferred ownership; return existing id + // Duplicate. Free bytes because the caller transferred ownership. Return existing id. b3Free( bytes, (size_t)byteCount ); return e->id; } @@ -873,22 +865,18 @@ uint32_t b3AppendGeometry( b3GeometryRegistry* reg, b3GeometryKind kind, uint64_ void b3FreeRegistry( b3GeometryRegistry* reg ) { - for ( int i = 0; i < reg->count; ++i ) + for ( int i = 0; i < reg->entries.count; ++i ) { - b3Free( reg->entries[i].bytes, (size_t)reg->entries[i].byteCount ); - } - if ( reg->entries != NULL ) - { - b3Free( reg->entries, (size_t)( reg->capacity * (int)sizeof( b3GeometryEntry ) ) ); + b3Free( reg->entries.data[i].bytes, (size_t)reg->entries.data[i].byteCount ); } + + b3Array_Destroy( reg->entries ); + if ( reg->dedupMap != NULL ) { b3GeometryHashMap_cleanup( (b3GeometryHashMap*)reg->dedupMap ); b3Free( reg->dedupMap, sizeof( b3GeometryHashMap ) ); } - reg->entries = NULL; - reg->count = 0; - reg->capacity = 0; reg->dedupMap = NULL; } @@ -955,10 +943,10 @@ void b3RecInternTag( b3Recording* rec, uint64_t key, uint64_t id, const char* na // the tag table stops after the geometry entries and ignores the trailing tag bytes. void b3RecWriteRegistry( b3Recording* rec ) { - b3RecW_U32( &rec->buffer, (uint32_t)rec->registry.count ); - for ( int i = 0; i < rec->registry.count; ++i ) + b3RecW_U32( &rec->buffer, (uint32_t)rec->registry.entries.count ); + for ( int i = 0; i < rec->registry.entries.count; ++i ) { - b3GeometryEntry* e = rec->registry.entries + i; + b3GeometryEntry* e = rec->registry.entries.data + i; b3RecW_U8( &rec->buffer, (uint8_t)e->kind ); b3RecW_U32( &rec->buffer, (uint32_t)e->byteCount ); b3RecBufAppend( &rec->buffer, e->bytes, e->byteCount ); @@ -977,7 +965,7 @@ void b3RecWriteRegistry( b3Recording* rec ) b3Recording* b3CreateRecording( int byteCapacity ) { - b3Recording* rec = (b3Recording*)b3Alloc( sizeof( b3Recording ) ); + b3Recording* rec = b3Alloc( sizeof( b3Recording ) ); *rec = (b3Recording){ 0 }; int initCap = byteCapacity > 0 ? byteCapacity : 65536; @@ -1196,7 +1184,7 @@ b3Recording* b3LoadRecordingFromFile( const char* path ) uint32_t b3RecInternHull( b3Recording* rec, const b3HullData* hull ) { int byteCount = hull->byteCount; - uint8_t* bytes = (uint8_t*)b3Alloc( (size_t)byteCount ); + uint8_t* bytes = b3Alloc( (size_t)byteCount ); memcpy( bytes, hull, (size_t)byteCount ); uint64_t h = b3Hash64Blob( bytes, byteCount ); return b3InternGeometry( &rec->registry, b3_geometryHull, h, bytes, byteCount ); @@ -1205,7 +1193,7 @@ uint32_t b3RecInternHull( b3Recording* rec, const b3HullData* hull ) uint32_t b3RecInternMesh( b3Recording* rec, const b3MeshData* mesh ) { int byteCount = mesh->byteCount; - uint8_t* bytes = (uint8_t*)b3Alloc( (size_t)byteCount ); + uint8_t* bytes = b3Alloc( (size_t)byteCount ); memcpy( bytes, mesh, (size_t)byteCount ); uint64_t h = b3Hash64Blob( bytes, byteCount ); return b3InternGeometry( &rec->registry, b3_geometryMesh, h, bytes, byteCount ); @@ -1214,7 +1202,7 @@ uint32_t b3RecInternMesh( b3Recording* rec, const b3MeshData* mesh ) uint32_t b3RecInternHeightField( b3Recording* rec, const b3HeightFieldData* hf ) { int byteCount = hf->byteCount; - uint8_t* bytes = (uint8_t*)b3Alloc( (size_t)byteCount ); + uint8_t* bytes = b3Alloc( (size_t)byteCount ); memcpy( bytes, hf, (size_t)byteCount ); uint64_t h = b3Hash64Blob( bytes, byteCount ); return b3InternGeometry( &rec->registry, b3_geometryHeightField, h, bytes, byteCount ); @@ -1223,7 +1211,7 @@ uint32_t b3RecInternHeightField( b3Recording* rec, const b3HeightFieldData* hf ) uint32_t b3RecInternCompound( b3Recording* rec, const b3CompoundData* compound ) { int byteCount = compound->byteCount; - uint8_t* bytes = (uint8_t*)b3Alloc( (size_t)byteCount ); + uint8_t* bytes = b3Alloc( (size_t)byteCount ); memcpy( bytes, compound, (size_t)byteCount ); // Null the tree node pointer in the copy so the canonical bytes are pointer-free. // b3ConvertBytesToCompound fixes it back on load via nodeOffset. diff --git a/vendor/box3d/src/src/recording.h b/vendor/box3d/src/src/recording.h index 3ba2fd3bb..19a96c572 100644 --- a/vendor/box3d/src/src/recording.h +++ b/vendor/box3d/src/src/recording.h @@ -3,6 +3,7 @@ #pragma once +#include "container.h" #include "core.h" #include "box3d/id.h" @@ -50,10 +51,10 @@ typedef struct b3World b3World; #define B3_REC_VERSION_MAJOR 4 // Minor tracks op-stream additions that keep the 48 byte header shape. -// Minor version 3 added name cache. -#define B3_REC_VERSION_MINOR 3 +// Minor version 4 added b3Shape_SetMeshMaterial, b3Shape_SetHull, b3Shape_SetMesh +#define B3_REC_VERSION_MINOR 4 -// File header, fixed 48 bytes, little-endian. Contains the registry locator so the player +// File header, fixed 48 bytes. Contains the registry locator so the player // can load geometry before replaying any ops. typedef struct b3RecHeader { @@ -106,13 +107,13 @@ typedef struct b3GeometryEntry int hashNext; } b3GeometryEntry; +b3DeclareArray( b3GeometryEntry ); + // Growable array of geometry entries. Ids are array indices, so the array is serialized in order. // dedupMap maps content hash to entry id for O(1) dedup; it is opaque here and owned by recording.c. typedef struct b3GeometryRegistry { - b3GeometryEntry* entries; - int count; - int capacity; + b3Array( b3GeometryEntry ) entries; void* dedupMap; } b3GeometryRegistry; @@ -126,7 +127,7 @@ typedef struct b3GeometryRegistry typedef struct b3RecTag { // hash of (id, queryName) - uint64_t key; + uint64_t key; uint64_t id; char queryName[B3_MAX_QUERY_NAME_LENGTH + 1]; } b3RecTag; diff --git a/vendor/box3d/src/src/recording_ops.inl b/vendor/box3d/src/src/recording_ops.inl index b8a3625da..3f0cf4260 100644 --- a/vendor/box3d/src/src/recording_ops.inl +++ b/vendor/box3d/src/src/recording_ops.inl @@ -68,6 +68,7 @@ B3_REC_OP( 0x36, BodySetMotionLocks, RET_NONE, ARG( BODYID, body ) ARG( LOCKS, l B3_REC_OP( 0x37, BodySetBullet, RET_NONE, ARG( BODYID, body ) ARG( BOOL, flag ) ) B3_REC_OP( 0x38, BodyEnableContactRecycling, RET_NONE, ARG( BODYID, body ) ARG( BOOL, flag ) ) B3_REC_OP( 0x39, BodyEnableHitEvents, RET_NONE, ARG( BODYID, body ) ARG( BOOL, flag ) ) +B3_REC_OP( 0x3A, BodyAllowFastRotation, RET_NONE, ARG( BODYID, body ) ARG( BOOL, flag ) ) // Shape create/destroy B3_REC_OP( 0x40, CreateSphereShape, RET_SHAPEID, ARG( BODYID, body ) ARG( SHAPEDEF, def ) ARG( SPHERE, sphere ) ) @@ -90,9 +91,11 @@ B3_REC_OP( 0x57, ShapeEnablePreSolveEvents, RET_NONE, ARG( SHAPEID, shape ) ARG( B3_REC_OP( 0x58, ShapeEnableHitEvents, RET_NONE, ARG( SHAPEID, shape ) ARG( BOOL, flag ) ) B3_REC_OP( 0x59, ShapeSetSphere, RET_NONE, ARG( SHAPEID, shape ) ARG( SPHERE, sphere ) ) B3_REC_OP( 0x5A, ShapeSetCapsule, RET_NONE, ARG( SHAPEID, shape ) ARG( CAPSULE, capsule ) ) -B3_REC_OP( 0x5B, ShapeApplyWind, RET_NONE, - ARG( SHAPEID, shape ) ARG( VEC3, wind ) ARG( F32, drag ) ARG( F32, lift ) ARG( F32, maxSpeed ) ARG( BOOL, wake ) ) +B3_REC_OP( 0x5B, ShapeApplyWind, RET_NONE, ARG( SHAPEID, shape ) ARG( VEC3, wind ) ARG( F32, drag ) ARG( F32, lift ) ARG( F32, maxSpeed ) ARG( BOOL, wake ) ) B3_REC_OP( 0x5C, ShapeSetName, RET_NONE, ARG( SHAPEID, shape ) ARG( STR, name ) ) +B3_REC_OP( 0x5D, ShapeSetMeshMaterial, RET_NONE, ARG( SHAPEID, shape ) ARG( MATERIAL, material ) ARG( I32, index ) ) +B3_REC_OP( 0x5E, ShapeSetHull, RET_NONE, ARG( SHAPEID, shape ) ARG( GEOMID, geometryId ) ) +B3_REC_OP( 0x5F, ShapeSetMesh, RET_NONE, ARG( SHAPEID, shape ) ARG( GEOMID, geometryId ) ARG( VEC3, scale ) ) // Joint create and destroy B3_REC_OP( 0x90, CreateParallelJoint, RET_JOINTID, ARG( WORLDID, world ) ARG( PARALLELJOINTDEF, def ) ) diff --git a/vendor/box3d/src/src/recording_replay.c b/vendor/box3d/src/src/recording_replay.c index 584912d59..f2f578d07 100644 --- a/vendor/box3d/src/src/recording_replay.c +++ b/vendor/box3d/src/src/recording_replay.c @@ -957,6 +957,11 @@ static void b3RecDispatch_BodySetBullet( const b3RecArgs_BodySetBullet* a, b3Rec b3Body_SetBullet( b3RecMakeBodyId( rdr, a->body ), a->flag ); } +static void b3RecDispatch_BodyAllowFastRotation( const b3RecArgs_BodyAllowFastRotation* a, b3RecReader* rdr ) +{ + b3Body_AllowFastRotation( b3RecMakeBodyId( rdr, a->body ), a->flag ); +} + static void b3RecDispatch_BodyEnableContactRecycling( const b3RecArgs_BodyEnableContactRecycling* a, b3RecReader* rdr ) { b3Body_EnableContactRecycling( b3RecMakeBodyId( rdr, a->body ), a->flag ); @@ -1019,7 +1024,7 @@ static void b3RecDispatch_CreateMeshShape( const b3RecArgs_CreateMeshShape* a, b return; } b3RegistrySlot* slot = rdr->slots + id; - const b3MeshData* mesh = (const b3MeshData*)b3RecGetLiveMesh( slot ); + const b3MeshData* mesh = b3RecGetLiveMesh( slot ); b3BodyId bodyId = b3RecMakeBodyId( rdr, a->body ); b3ShapeId gotId = b3CreateMeshShape( bodyId, &a->def, mesh, a->scale ); b3RecCheckShapeId( rdr, gotId, recId ); @@ -1071,7 +1076,7 @@ static void b3RecDispatch_CreateCompoundShape( const b3RecArgs_CreateCompoundSha b3BodyId bodyId = b3RecMakeBodyId( rdr, a->body ); // b3CreateCompoundShape takes a non-const def pointer; cast away const for the scratch def b3ShapeDef shapeDef = a->def; - b3ShapeId gotId = b3CreateCompoundShape( bodyId, &shapeDef, compound ); + b3ShapeId gotId = b3CreateBakedCompoundShape( bodyId, &shapeDef, compound ); b3RecCheckShapeId( rdr, gotId, recId ); } @@ -1105,6 +1110,11 @@ static void b3RecDispatch_ShapeSetSurfaceMaterial( const b3RecArgs_ShapeSetSurfa b3Shape_SetSurfaceMaterial( b3RecMakeShapeId( rdr, a->shape ), a->material ); } +static void b3RecDispatch_ShapeSetMeshMaterial( const b3RecArgs_ShapeSetMeshMaterial* a, b3RecReader* rdr ) +{ + b3Shape_SetMeshMaterial( b3RecMakeShapeId( rdr, a->shape ), a->material, a->index ); +} + static void b3RecDispatch_ShapeSetFilter( const b3RecArgs_ShapeSetFilter* a, b3RecReader* rdr ) { b3Shape_SetFilter( b3RecMakeShapeId( rdr, a->shape ), a->filter, a->invokeContacts ); @@ -1140,6 +1150,35 @@ static void b3RecDispatch_ShapeSetCapsule( const b3RecArgs_ShapeSetCapsule* a, b b3Shape_SetCapsule( b3RecMakeShapeId( rdr, a->shape ), &a->capsule ); } +static void b3RecDispatch_ShapeSetHull( const b3RecArgs_ShapeSetHull* a, b3RecReader* rdr ) +{ + uint32_t id = a->geometryId; + if ( id >= (uint32_t)rdr->slotCount ) + { + printf( "b3ReplayFile: hull geometryId %u out of range\n", id ); + rdr->ok = false; + return; + } + b3RegistrySlot* slot = rdr->slots + id; + b3ShapeId shapeId = b3RecMakeShapeId( rdr, a->shape ); + b3Shape_SetHull( shapeId, (const b3HullData*)slot->bytes ); +} + +static void b3RecDispatch_ShapeSetMesh( const b3RecArgs_ShapeSetMesh* a, b3RecReader* rdr ) +{ + uint32_t id = a->geometryId; + if ( id >= (uint32_t)rdr->slotCount ) + { + printf( "b3ReplayFile: mesh geometryId %u out of range\n", id ); + rdr->ok = false; + return; + } + b3RegistrySlot* slot = rdr->slots + id; + b3ShapeId shapeId = b3RecMakeShapeId( rdr, a->shape ); + const b3MeshData* mesh = b3RecGetLiveMesh( slot ); + b3Shape_SetMesh( shapeId, mesh, a->scale ); +} + static void b3RecDispatch_ShapeApplyWind( const b3RecArgs_ShapeApplyWind* a, b3RecReader* rdr ) { b3Shape_ApplyWind( b3RecMakeShapeId( rdr, a->shape ), a->wind, a->drag, a->lift, a->maxSpeed, a->wake ); @@ -2576,12 +2615,12 @@ static void b3RecCaptureKeyframe( b3RecPlayer* player ) b3World* world = b3GetWorldFromId( player->rdr.replayWorldId ); b3RecBuffer buf = { 0 }; - int regCountBefore = player->keyframeRec->registry.count; + int regCountBefore = player->keyframeRec->registry.entries.count; B3_UNUSED( regCountBefore ); b3SerializeWorld( world, &buf, player->keyframeRec ); // Registry must not grow: all geometry was pre-seeded and the registry dedups exactly. - B3_ASSERT( player->keyframeRec->registry.count == regCountBefore ); + B3_ASSERT( player->keyframeRec->registry.entries.count == regCountBefore ); size_t bodyBytes = (size_t)player->bodyIdCount * sizeof( b3BodyId ); size_t newBytes = (size_t)buf.capacity + bodyBytes; diff --git a/vendor/box3d/src/src/sensor.c b/vendor/box3d/src/src/sensor.c index 0cd23a460..056708a3b 100644 --- a/vendor/box3d/src/src/sensor.c +++ b/vendor/box3d/src/src/sensor.c @@ -107,9 +107,8 @@ static bool b3SensorQueryCallback( int proxyId, uint64_t userData, void* context b3World* world = queryContext->world; b3Shape* otherShape = b3Array_Get( world->shapes, shapeId ); - // Mesh vs mesh is not supported - if ( ( otherShape->type == b3_meshShape || otherShape->type == b3_heightShape ) && - ( sensorShape->type == b3_meshShape || sensorShape->type == b3_heightShape ) ) + // Visitors must be convex. + if ( b3IsConvex( otherShape->type ) == false ) { return true; } diff --git a/vendor/box3d/src/src/shape.c b/vendor/box3d/src/src/shape.c index e4d9d9e0c..eabc1eb55 100644 --- a/vendor/box3d/src/src/shape.c +++ b/vendor/box3d/src/src/shape.c @@ -424,7 +424,7 @@ b3ShapeId b3CreateHeightFieldShape( b3BodyId bodyId, const b3ShapeDef* def, cons return shapeId; } -b3ShapeId b3CreateCompoundShape( b3BodyId bodyId, b3ShapeDef* def, const b3CompoundData* compound ) +b3ShapeId b3CreateBakedCompoundShape( b3BodyId bodyId, b3ShapeDef* def, const b3CompoundData* compound ) { b3ShapeId shapeId = b3CreateShape( bodyId, def, compound, b3_compoundShape, b3Transform_identity, b3Vec3_one, false ); if ( shapeId.index1 != 0 ) @@ -1295,6 +1295,8 @@ void b3Shape_SetMeshMaterial( b3ShapeId shapeId, b3SurfaceMaterial surfaceMateri B3_ASSERT( 0 <= index && index < shape->materialCount ); B3_ASSERT( shape->type != b3_compoundShape ); + + B3_REC( world, ShapeSetMeshMaterial, shapeId, surfaceMaterial, index ); b3GetShapeMaterials( shape )[index] = surfaceMaterial; } @@ -1613,6 +1615,14 @@ void b3Shape_SetHull( b3ShapeId shapeId, const b3HullData* hull ) return; } + if ( world->recording != NULL ) + { + // Intern the shared hull. + uint32_t geometryId = b3RecInternHull( world->recording, data ); + b3RecArgs_ShapeSetHull setArgs = { shapeId, geometryId }; + b3RecWrite_ShapeSetHull( world->recording, &setArgs ); + } + b3DestroyShapeAllocationForShapeChange( world, shape ); shape->hull = data; @@ -1640,6 +1650,13 @@ void b3Shape_SetMesh( b3ShapeId shapeId, const b3MeshData* meshData, b3Vec3 scal world->locked = true; + if ( world->recording != NULL ) + { + uint32_t geometryId = b3RecInternMesh( world->recording, meshData ); + b3RecArgs_ShapeSetMesh setArgs = { shapeId, geometryId, scale }; + b3RecWrite_ShapeSetMesh( world->recording, &setArgs ); + } + b3Shape* shape = b3GetShape( world, shapeId ); b3DestroyShapeAllocationForShapeChange( world, shape ); diff --git a/vendor/box3d/src/src/shape.h b/vendor/box3d/src/src/shape.h index 519ce8694..ed76dd41c 100644 --- a/vendor/box3d/src/src/shape.h +++ b/vendor/box3d/src/src/shape.h @@ -153,3 +153,8 @@ static inline bool b3ShouldQueryCollide( const b3Filter* shapeFilter, const b3Qu return ( shapeFilter->categoryBits & queryFilter->maskBits ) != 0 && ( shapeFilter->maskBits & queryFilter->categoryBits ) != 0; } + +static inline bool b3IsConvex( b3ShapeType type ) +{ + return type == b3_sphereShape || type == b3_capsuleShape || type == b3_hullShape; +} diff --git a/vendor/box3d/src/src/simd.h b/vendor/box3d/src/src/simd.h index ac7e9e8b5..6e313a0a4 100644 --- a/vendor/box3d/src/src/simd.h +++ b/vendor/box3d/src/src/simd.h @@ -7,10 +7,35 @@ #include -#if defined( B3_SIMD_SSE2 ) +#if defined( B3_SIMD_NEON ) + +#include + +// wide float holds 4 numbers +typedef float32x4_t b3FloatW; + +#elif defined( B3_SIMD_SSE2 ) #include +// wide float holds 4 numbers +typedef __m128 b3FloatW; + +#else + +#include +#include + +// scalar math +typedef struct b3FloatW +{ + float x, y, z, w; +} b3FloatW; + +#endif + +#if defined( B3_SIMD_SSE2 ) + // wide float holds 4 numbers typedef __m128 b3V32; @@ -355,3 +380,544 @@ static inline bool b3TestBoundsRayOverlap( b3V32 nodeMin, b3V32 nodeMax, b3V32 r bool b3TestBoundsTriangleOverlap( b3V32 nodeCenter, b3V32 nodeExtent, b3V32 vertex1, b3V32 vertex2, b3V32 vertex3 ); float b3IntersectRayTriangle( b3V32 rayStart, b3V32 rayDelta, b3V32 vertex1, b3V32 vertex2, b3V32 vertex3 ); + +#if defined( B3_SIMD_NEON ) + +static inline b3FloatW b3ZeroW( void ) +{ + return vdupq_n_f32( 0.0f ); +} + +static inline b3FloatW b3SplatW( float scalar ) +{ + return vdupq_n_f32( scalar ); +} + +static inline b3FloatW b3SetW( float a, float b, float c, float d ) +{ + float32_t array[4] = { a, b, c, d }; + return vld1q_f32( array ); +} + +static inline b3FloatW b3LoadW( const float* data ) +{ + return vld1q_f32( data ); +} + +static inline void b3StoreW( float* data, b3FloatW a ) +{ + vst1q_f32( data, a ); +} + +static inline b3FloatW b3NegW( b3FloatW a ) +{ + return vnegq_f32( a ); +} + +static inline b3FloatW b3AddW( b3FloatW a, b3FloatW b ) +{ + return vaddq_f32( a, b ); +} + +static inline b3FloatW b3SubW( b3FloatW a, b3FloatW b ) +{ + return vsubq_f32( a, b ); +} + +static inline b3FloatW b3MulW( b3FloatW a, b3FloatW b ) +{ + return vmulq_f32( a, b ); +} + +static inline b3FloatW b3DivW( b3FloatW a, b3FloatW b ) +{ + return vdivq_f32( a, b ); +} + +static inline b3FloatW b3SqrtW( b3FloatW a ) +{ + return vsqrtq_f32( a ); +} + +// Cannot use real FMA because it doesn't match the non-SIMD path +static inline b3FloatW b3MulAddW( b3FloatW a, b3FloatW b, b3FloatW c ) +{ + return vaddq_f32( a, vmulq_f32( b, c ) ); +} + +static inline b3FloatW b3MinW( b3FloatW a, b3FloatW b ) +{ + return vminq_f32( a, b ); +} + +static inline b3FloatW b3MaxW( b3FloatW a, b3FloatW b ) +{ + return vmaxq_f32( a, b ); +} + +// clamp a to [-b, b] +static inline b3FloatW b3SymClampW( b3FloatW a, b3FloatW b ) +{ + b3FloatW nb = b3NegW( b ); + b3FloatW c = b3MaxW( nb, a ); + return b3MinW( c, b ); +} + +static inline b3FloatW b3AndW( b3FloatW a, b3FloatW b ) +{ + return vreinterpretq_f32_u32( vandq_u32( vreinterpretq_u32_f32( a ), vreinterpretq_u32_f32( b ) ) ); +} + +static inline b3FloatW b3OrW( b3FloatW a, b3FloatW b ) +{ + return vreinterpretq_f32_u32( vorrq_u32( vreinterpretq_u32_f32( a ), vreinterpretq_u32_f32( b ) ) ); +} + +static inline b3FloatW b3GreaterThanW( b3FloatW a, b3FloatW b ) +{ + return vreinterpretq_f32_u32( vcgtq_f32( a, b ) ); +} + +static inline b3FloatW b3LessThanW( b3FloatW a, b3FloatW b ) +{ + return vreinterpretq_f32_u32( vcltq_f32( a, b ) ); +} + +static inline b3FloatW b3EqualsW( b3FloatW a, b3FloatW b ) +{ + return vreinterpretq_f32_u32( vceqq_f32( a, b ) ); +} + +static inline bool b3AllZeroW( b3FloatW a ) +{ + // Create a zero vector for comparison + b3FloatW zero = vdupq_n_f32( 0.0f ); + + // Compare the input vector with zero + uint32x4_t cmp_result = vceqq_f32( a, zero ); + +// Check if all comparison results are non-zero using vminvq +#ifdef __ARM_FEATURE_SVE + // ARM v8.2+ has horizontal minimum instruction + return vminvq_u32( cmp_result ) != 0; +#else + // For older ARM architectures, we need to manually check all lanes + return vgetq_lane_u32( cmp_result, 0 ) != 0 && vgetq_lane_u32( cmp_result, 1 ) != 0 && vgetq_lane_u32( cmp_result, 2 ) != 0 && + vgetq_lane_u32( cmp_result, 3 ) != 0; +#endif +} + +// _mm_movemask_ps equivalent, compatible with ARM v7. +static inline bool b3AnyTrueW( b3FloatW mask ) +{ + uint32x4_t m = vreinterpretq_u32_f32( mask ); + uint32x2_t p = vorr_u32( vget_low_u32( m ), vget_high_u32( m ) ); + return ( vget_lane_u32( p, 0 ) | vget_lane_u32( p, 1 ) ) != 0; +} + +// component-wise returns mask ? b : a +static inline b3FloatW b3BlendW( b3FloatW a, b3FloatW b, b3FloatW mask ) +{ + uint32x4_t mask32 = vreinterpretq_u32_f32( mask ); + return vbslq_f32( mask32, b, a ); +} + +static inline b3FloatW b3Dot3W( b3FloatW ax, b3FloatW ay, b3FloatW az, b3FloatW bx, b3FloatW by, b3FloatW bz ) +{ + return vaddq_f32( vmulq_f32( ax, bx ), vaddq_f32( vmulq_f32( ay, by ), vmulq_f32( az, bz ) ) ); +} + +static inline b3FloatW b3EmbedIndexW( b3FloatW value, int baseIndex, int bitCount ) +{ + uint32_t mask = ( 1u << bitCount ) - 1; + const int32_t lanes[4] = { 0, 1, 2, 3 }; + int32x4_t index = vaddq_s32( vdupq_n_s32( baseIndex ), vld1q_s32( lanes ) ); + uint32x4_t clearLow = vdupq_n_u32( ~mask ); + uint32x4_t bits = vorrq_u32( vandq_u32( vreinterpretq_u32_f32( value ), clearLow ), vreinterpretq_u32_s32( index ) ); + return vreinterpretq_f32_u32( bits ); +} + +static inline int b3MinIndexW( b3FloatW a, int bitCount ) +{ + float32x2_t m = vmin_f32( vget_low_f32( a ), vget_high_f32( a ) ); + m = vpmin_f32( m, m ); + uint32_t bits = vget_lane_u32( vreinterpret_u32_f32( m ), 0 ); + return (int)( bits & ( ( 1u << bitCount ) - 1 ) ); +} + +#elif defined( B3_SIMD_SSE2 ) + +static inline b3FloatW b3ZeroW( void ) +{ + return _mm_setzero_ps(); +} + +static inline b3FloatW b3SplatW( float scalar ) +{ + return _mm_set1_ps( scalar ); +} + +static inline b3FloatW b3SetW( float a, float b, float c, float d ) +{ + return _mm_setr_ps( a, b, c, d ); +} + +static inline b3FloatW b3LoadW( const float* data ) +{ + return _mm_loadu_ps( data ); +} + +static inline void b3StoreW( float* data, b3FloatW a ) +{ + _mm_storeu_ps( data, a ); +} + +static inline b3FloatW b3NegW( b3FloatW a ) +{ + // Create a mask with the sign bit set for each element + __m128 mask = _mm_set1_ps( -0.0f ); + + // XOR the input with the mask to negate each element + return _mm_xor_ps( a, mask ); +} + +static inline b3FloatW b3AddW( b3FloatW a, b3FloatW b ) +{ + return _mm_add_ps( a, b ); +} + +static inline b3FloatW b3SubW( b3FloatW a, b3FloatW b ) +{ + return _mm_sub_ps( a, b ); +} + +static inline b3FloatW b3MulW( b3FloatW a, b3FloatW b ) +{ + return _mm_mul_ps( a, b ); +} + +static inline b3FloatW b3DivW( b3FloatW a, b3FloatW b ) +{ + return _mm_div_ps( a, b ); +} + +static inline b3FloatW b3SqrtW( b3FloatW a ) +{ + return _mm_sqrt_ps( a ); +} + +// a + b * c +static inline b3FloatW b3MulAddW( b3FloatW a, b3FloatW b, b3FloatW c ) +{ + return _mm_add_ps( a, _mm_mul_ps( b, c ) ); +} + +static inline b3FloatW b3MinW( b3FloatW a, b3FloatW b ) +{ + return _mm_min_ps( a, b ); +} + +static inline b3FloatW b3MaxW( b3FloatW a, b3FloatW b ) +{ + return _mm_max_ps( a, b ); +} + +// Horizontal min over a 4-lane vector (result broadcast to all lanes). +static inline b3FloatW b3HorizontalMinW( b3FloatW v ) +{ + v = _mm_min_ps( v, _mm_shuffle_ps( v, v, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + return _mm_min_ps( v, _mm_shuffle_ps( v, v, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); +} + +// clamp a to [-b, b] +static inline b3FloatW b3SymClampW( b3FloatW a, b3FloatW b ) +{ + b3FloatW nb = b3NegW( b ); + b3FloatW c = b3MaxW( nb, a ); + return b3MinW( c, b ); +} + +static inline b3FloatW b3AndW( b3FloatW a, b3FloatW b ) +{ + return _mm_and_ps( a, b ); +} + +static inline b3FloatW b3OrW( b3FloatW a, b3FloatW b ) +{ + return _mm_or_ps( a, b ); +} + +static inline b3FloatW b3GreaterThanW( b3FloatW a, b3FloatW b ) +{ + return _mm_cmpgt_ps( a, b ); +} + +static inline b3FloatW b3LessThanW( b3FloatW a, b3FloatW b ) +{ + return _mm_cmplt_ps( a, b ); +} + +static inline b3FloatW b3EqualsW( b3FloatW a, b3FloatW b ) +{ + return _mm_cmpeq_ps( a, b ); +} + +static inline bool b3AllZeroW( b3FloatW a ) +{ + // Compare each element with zero + b3FloatW zero = _mm_setzero_ps(); + b3FloatW cmp = _mm_cmpeq_ps( a, zero ); + + // Create a mask from the comparison results + int mask = _mm_movemask_ps( cmp ); + + // If all elements are zero, the mask will be 0xF (1111 in binary) + return mask == 0xF; +} + +static inline bool b3AnyTrueW( b3FloatW mask ) +{ + return _mm_movemask_ps( mask ) != 0; +} + +// component-wise returns mask ? b : a +static inline b3FloatW b3BlendW( b3FloatW a, b3FloatW b, b3FloatW mask ) +{ + return _mm_or_ps( _mm_and_ps( mask, b ), _mm_andnot_ps( mask, a ) ); +} + +static inline b3FloatW b3Dot3W( b3FloatW ax, b3FloatW ay, b3FloatW az, b3FloatW bx, b3FloatW by, b3FloatW bz ) +{ + return _mm_add_ps( _mm_mul_ps( ax, bx ), _mm_add_ps( _mm_mul_ps( ay, by ), _mm_mul_ps( az, bz ) ) ); +} + +// Replace the low bitCount mantissa bits of each lane with baseIndex + lane. The value must be +// positive so the embedded index sorts with the value, and ties fall to the lower index. +static inline b3FloatW b3EmbedIndexW( b3FloatW value, int baseIndex, int bitCount ) +{ + int mask = ( 1 << bitCount ) - 1; + __m128i index = _mm_add_epi32( _mm_set1_epi32( baseIndex ), _mm_setr_epi32( 0, 1, 2, 3 ) ); + __m128 clearLow = _mm_castsi128_ps( _mm_set1_epi32( ~mask ) ); + return _mm_or_ps( _mm_and_ps( value, clearLow ), _mm_castsi128_ps( index ) ); +} + +// Recovers the index embedded by b3EmbedIndexW from the lane holding the minimum. +static inline int b3MinIndexW( b3FloatW a, int bitCount ) +{ + a = _mm_min_ps( a, _mm_shuffle_ps( a, a, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + a = _mm_min_ps( a, _mm_shuffle_ps( a, a, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + return _mm_cvtsi128_si32( _mm_castps_si128( a ) ) & ( ( 1 << bitCount ) - 1 ); +} + +#else + +static inline b3FloatW b3ZeroW( void ) +{ + return (b3FloatW){ 0.0f, 0.0f, 0.0f, 0.0f }; +} + +static inline b3FloatW b3SplatW( float scalar ) +{ + return (b3FloatW){ scalar, scalar, scalar, scalar }; +} + +static inline b3FloatW b3SetW( float a, float b, float c, float d ) +{ + return (b3FloatW){ a, b, c, d }; +} + +static inline b3FloatW b3LoadW( const float* data ) +{ + return (b3FloatW){ data[0], data[1], data[2], data[3] }; +} + +static inline void b3StoreW( float* data, b3FloatW a ) +{ + data[0] = a.x; + data[1] = a.y; + data[2] = a.z; + data[3] = a.w; +} + +static inline b3FloatW b3NegW( b3FloatW a ) +{ + return (b3FloatW){ -a.x, -a.y, -a.z, -a.w }; +} + +static inline b3FloatW b3AddW( b3FloatW a, b3FloatW b ) +{ + return (b3FloatW){ a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w }; +} + +static inline b3FloatW b3SubW( b3FloatW a, b3FloatW b ) +{ + return (b3FloatW){ a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w }; +} + +static inline b3FloatW b3MulW( b3FloatW a, b3FloatW b ) +{ + return (b3FloatW){ a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w }; +} + +static inline b3FloatW b3DivW( b3FloatW a, b3FloatW b ) +{ + return (b3FloatW){ a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w }; +} + +static inline b3FloatW b3SqrtW( b3FloatW a ) +{ + return (b3FloatW){ sqrtf( a.x ), sqrtf( a.y ), sqrtf( a.z ), sqrtf( a.w ) }; +} + +static inline b3FloatW b3MulAddW( b3FloatW a, b3FloatW b, b3FloatW c ) +{ + return (b3FloatW){ a.x + b.x * c.x, a.y + b.y * c.y, a.z + b.z * c.z, a.w + b.w * c.w }; +} + +static inline b3FloatW b3MinW( b3FloatW a, b3FloatW b ) +{ + b3FloatW r; + r.x = a.x <= b.x ? a.x : b.x; + r.y = a.y <= b.y ? a.y : b.y; + r.z = a.z <= b.z ? a.z : b.z; + r.w = a.w <= b.w ? a.w : b.w; + return r; +} + +static inline b3FloatW b3MaxW( b3FloatW a, b3FloatW b ) +{ + b3FloatW r; + r.x = a.x >= b.x ? a.x : b.x; + r.y = a.y >= b.y ? a.y : b.y; + r.z = a.z >= b.z ? a.z : b.z; + r.w = a.w >= b.w ? a.w : b.w; + return r; +} + +// clamp a to [-b, b] +static inline b3FloatW b3SymClampW( b3FloatW a, b3FloatW b ) +{ + b3FloatW r; + r.x = a.x <= b.x ? a.x : b.x; + r.y = a.y <= b.y ? a.y : b.y; + r.z = a.z <= b.z ? a.z : b.z; + r.w = a.w <= b.w ? a.w : b.w; + r.x = r.x <= -b.x ? -b.x : r.x; + r.y = r.y <= -b.y ? -b.y : r.y; + r.z = r.z <= -b.z ? -b.z : r.z; + r.w = r.w <= -b.w ? -b.w : r.w; + return r; +} + +// Logical operations on the scalar path are 0/1 float values. Not bit-wise like SIMD. + +static inline b3FloatW b3AndW( b3FloatW a, b3FloatW b ) +{ + b3FloatW r; + r.x = a.x != 0.0f && b.x != 0.0f ? 1.0f : 0.0f; + r.y = a.y != 0.0f && b.y != 0.0f ? 1.0f : 0.0f; + r.z = a.z != 0.0f && b.z != 0.0f ? 1.0f : 0.0f; + r.w = a.w != 0.0f && b.w != 0.0f ? 1.0f : 0.0f; + return r; +} + +static inline b3FloatW b3OrW( b3FloatW a, b3FloatW b ) +{ + b3FloatW r; + r.x = a.x != 0.0f || b.x != 0.0f ? 1.0f : 0.0f; + r.y = a.y != 0.0f || b.y != 0.0f ? 1.0f : 0.0f; + r.z = a.z != 0.0f || b.z != 0.0f ? 1.0f : 0.0f; + r.w = a.w != 0.0f || b.w != 0.0f ? 1.0f : 0.0f; + return r; +} + +static inline b3FloatW b3GreaterThanW( b3FloatW a, b3FloatW b ) +{ + b3FloatW r; + r.x = a.x > b.x ? 1.0f : 0.0f; + r.y = a.y > b.y ? 1.0f : 0.0f; + r.z = a.z > b.z ? 1.0f : 0.0f; + r.w = a.w > b.w ? 1.0f : 0.0f; + return r; +} + +static inline b3FloatW b3LessThanW( b3FloatW a, b3FloatW b ) +{ + b3FloatW r; + r.x = a.x < b.x ? 1.0f : 0.0f; + r.y = a.y < b.y ? 1.0f : 0.0f; + r.z = a.z < b.z ? 1.0f : 0.0f; + r.w = a.w < b.w ? 1.0f : 0.0f; + return r; +} + +static inline b3FloatW b3EqualsW( b3FloatW a, b3FloatW b ) +{ + b3FloatW r; + r.x = a.x == b.x ? 1.0f : 0.0f; + r.y = a.y == b.y ? 1.0f : 0.0f; + r.z = a.z == b.z ? 1.0f : 0.0f; + r.w = a.w == b.w ? 1.0f : 0.0f; + return r; +} + +static inline bool b3AllZeroW( b3FloatW a ) +{ + return a.x == 0.0f && a.y == 0.0f && a.z == 0.0f && a.w == 0.0f; +} + +static inline bool b3AnyTrueW( b3FloatW mask ) +{ + return mask.x != 0.0f || mask.y != 0.0f || mask.z != 0.0f || mask.w != 0.0f; +} + +// component-wise returns mask ? b : a +static inline b3FloatW b3BlendW( b3FloatW a, b3FloatW b, b3FloatW mask ) +{ + b3FloatW r; + r.x = mask.x != 0.0f ? b.x : a.x; + r.y = mask.y != 0.0f ? b.y : a.y; + r.z = mask.z != 0.0f ? b.z : a.z; + r.w = mask.w != 0.0f ? b.w : a.w; + return r; +} + +static inline b3FloatW b3Dot3W( b3FloatW ax, b3FloatW ay, b3FloatW az, b3FloatW bx, b3FloatW by, b3FloatW bz ) +{ + b3FloatW r; + r.x = ax.x * bx.x + ( ay.x * by.x + az.x * bz.x ); + r.y = ax.y * bx.y + ( ay.y * by.y + az.y * bz.y ); + r.z = ax.z * bx.z + ( ay.z * by.z + az.z * bz.z ); + r.w = ax.w * bx.w + ( ay.w * by.w + az.w * bz.w ); + return r; +} + +static inline b3FloatW b3EmbedIndexW( b3FloatW value, int baseIndex, int bitCount ) +{ + uint32_t mask = ( 1u << bitCount ) - 1; + float lanes[4] = { value.x, value.y, value.z, value.w }; + + for ( int i = 0; i < 4; ++i ) + { + uint32_t bits; + memcpy( &bits, lanes + i, sizeof( bits ) ); + bits = ( bits & ~mask ) | (uint32_t)( baseIndex + i ); + memcpy( lanes + i, &bits, sizeof( bits ) ); + } + + return (b3FloatW){ lanes[0], lanes[1], lanes[2], lanes[3] }; +} + +static inline int b3MinIndexW( b3FloatW a, int bitCount ) +{ + float m = a.x; + m = a.y < m ? a.y : m; + m = a.z < m ? a.z : m; + m = a.w < m ? a.w : m; + + uint32_t bits; + memcpy( &bits, &m, sizeof( bits ) ); + return (int)( bits & ( ( 1u << bitCount ) - 1 ) ); +} + +#endif diff --git a/vendor/box3d/src/src/solver.c b/vendor/box3d/src/src/solver.c index a2b1be977..d8aace052 100644 --- a/vendor/box3d/src/src/solver.c +++ b/vendor/box3d/src/src/solver.c @@ -24,6 +24,8 @@ #include #include +_Static_assert( B3_RESTITUTION_ITERATIONS >= 1, "must be 1 or more" ); + // these are useful for solver testing #define ITERATIONS 1 #define RELAX_ITERATIONS 1 @@ -118,33 +120,33 @@ static void b3IntegrateVelocitiesTask( b3SolverBlock block, b3StepContext* conte b3Vec3 omega2 = omega1; // Symmetric inertia tensor: 6 unique entries (column-major) - const float i00 = inertiaLocal.cx.x; - const float i01 = inertiaLocal.cy.x; - const float i02 = inertiaLocal.cz.x; - const float i11 = inertiaLocal.cy.y; - const float i12 = inertiaLocal.cz.y; - const float i22 = inertiaLocal.cz.z; + float i00 = inertiaLocal.cx.x; + float i01 = inertiaLocal.cy.x; + float i02 = inertiaLocal.cz.x; + float i11 = inertiaLocal.cy.y; + float i12 = inertiaLocal.cz.y; + float i22 = inertiaLocal.cz.z; - for ( int gyroIteration = 0; gyroIteration < 1; ++gyroIteration ) + for ( int gyroIteration = 0; gyroIteration < B3_GYROSCOPIC_ITERATIONS; ++gyroIteration ) { - const float w1 = omega2.x; - const float w2 = omega2.y; - const float w3 = omega2.z; + float w1 = omega2.x; + float w2 = omega2.y; + float w3 = omega2.z; // Iw = I * omega2 (shared between residual and Jacobian) - const float Iw1 = i00 * w1 + i01 * w2 + i02 * w3; - const float Iw2 = i01 * w1 + i11 * w2 + i12 * w3; - const float Iw3 = i02 * w1 + i12 * w2 + i22 * w3; + float Iw1 = i00 * w1 + i01 * w2 + i02 * w3; + float Iw2 = i01 * w1 + i11 * w2 + i12 * w3; + float Iw3 = i02 * w1 + i12 * w2 + i22 * w3; - // Residual: b = I*(omega2 - omega1) + h * (omega2 × I*omega2) - const b3Vec3 dw = b3Sub( omega2, omega1 ); + // Residual: b = I * (omega2 - omega1) + h * cross(omega2, I * omega2) + b3Vec3 dw = b3Sub( omega2, omega1 ); b3Vec3 b = { i00 * dw.x + i01 * dw.y + i02 * dw.z + h * ( w2 * Iw3 - w3 * Iw2 ), i01 * dw.x + i11 * dw.y + i12 * dw.z + h * ( w3 * Iw1 - w1 * Iw3 ), i02 * dw.x + i12 * dw.y + i22 * dw.z + h * ( w1 * Iw2 - w2 * Iw1 ), }; - // Jacobian J = I + h * (skew(omega2) * I - skew(I*omega2)) + // Jacobian J = I + h * (skew(omega2) * I - skew(I * omega2)) // Jacobian derived by Erin Catto, Ph.D. Do not attempt to do this without a Ph.D. // Doubled inertia terms above fold into Iw, e.g. row 2 col 1: i00*w3 - i02*w1 - Iw3. b3Matrix3 J = { @@ -1313,6 +1315,7 @@ static void b3SolverTask( void* taskContext ) stageIndex += 1 + activeColorCount + ITERATIONS * activeColorCount + 1 + RELAX_ITERATIONS * activeColorCount; // Restitution + for ( int iteration = 0; iteration < B3_RESTITUTION_ITERATIONS; ++iteration ) { b3ApplyRestitution_Overflow( context ); @@ -1324,7 +1327,7 @@ static void b3SolverTask( void* taskContext ) b3ExecuteMainStage( stages + iterStageIndex, context, syncBits ); iterStageIndex += 1; } - // graphSyncIndex += 1; + graphSyncIndex += 1; stageIndex += activeColorCount; } @@ -1690,7 +1693,7 @@ void b3Solve( b3World* world, b3StepContext* stepContext ) // b3_stageRelax stageCount += RELAX_ITERATIONS * activeColorCount; // b3_stageRestitution - stageCount += activeColorCount; + stageCount += B3_RESTITUTION_ITERATIONS * activeColorCount; // b3_stageStoreWideImpulses stageCount += 1; // b3_stageStoreImpulses @@ -1775,8 +1778,8 @@ void b3Solve( b3World* world, b3StepContext* stepContext ) stage = b3InitColorStages( stage, b3_stageRelax, RELAX_ITERATIONS, activeColorCount, graphColorBlocks, graphBlockCounts, activeColorIndices ); // Note: joint blocks mixed in, could have joint limit restitution - stage = b3InitColorStages( stage, b3_stageRestitution, 1, activeColorCount, graphColorBlocks, graphBlockCounts, - activeColorIndices ); + stage = b3InitColorStages( stage, b3_stageRestitution, B3_RESTITUTION_ITERATIONS, activeColorCount, graphColorBlocks, + graphBlockCounts, activeColorIndices ); stage = b3InitStage( stage, b3_stageStoreWideImpulses, convexBlocks, convexPrepareDim.count, UINT8_MAX ); stage = b3InitStage( stage, b3_stageStoreImpulses, meshBlocks, meshPrepareDim.count, UINT8_MAX ); diff --git a/vendor/box3d/src/src/timer.c b/vendor/box3d/src/src/timer.c index 826b1495c..f779e3bbb 100644 --- a/vendor/box3d/src/src/timer.c +++ b/vendor/box3d/src/src/timer.c @@ -23,7 +23,8 @@ #define WIN32_LEAN_AND_MEAN 1 #endif -#include +// Lower-case windows.h intentionally for cross compiling on mingw. +#include #include static double s_invFrequency = 0.0; @@ -517,7 +518,7 @@ typedef struct b3Thread char name[NAME_LENGTH]; } b3Thread; -// macOS pthread_setname_np takes only the name — it always names the calling thread. +// macOS pthread_setname_np takes only the name, it always names the calling thread. static void b3SetCurrentThreadName( const char* name ) { if ( name == NULL || name[0] == 0 ) diff --git a/vendor/box3d/src/src/triangle_manifold.c b/vendor/box3d/src/src/triangle_manifold.c index b87973add..3de7fee23 100644 --- a/vendor/box3d/src/src/triangle_manifold.c +++ b/vendor/box3d/src/src/triangle_manifold.c @@ -18,7 +18,6 @@ typedef struct b3TriangleData { b3Vec3 v1, v2, v3; b3Vec3 e1, e2, e3; - b3Vec3 center; b3Plane plane; int flags; } b3TriangleData; @@ -51,7 +50,7 @@ static b3TriangleFeature b3GetTriangleFeature( const b3SimplexCache* cache ) return s_triangleFeatures[mask]; } -void b3CollideSphereAndTriangle( b3LocalManifold* manifold, int capacity, const b3Sphere* sphereA, const b3Vec3* triangleB ) +void b3CollideTriangleAndSphere( b3LocalManifold* manifold, int capacity, const b3Vec3* triangleA, const b3Sphere* sphereB ) { manifold->pointCount = 0; @@ -60,8 +59,8 @@ void b3CollideSphereAndTriangle( b3LocalManifold* manifold, int capacity, const return; } - b3Vec3 center = sphereA->center; - b3Vec3 v1 = triangleB[0], v2 = triangleB[1], v3 = triangleB[2]; + b3Vec3 center = sphereB->center; + b3Vec3 v1 = triangleA[0], v2 = triangleA[1], v3 = triangleA[2]; b3Plane plane = b3MakePlaneFromPoints( v1, v2, v3 ); float offset = b3PlaneSeparation( plane, center ); @@ -71,19 +70,23 @@ void b3CollideSphereAndTriangle( b3LocalManifold* manifold, int capacity, const return; } + float radius = sphereB->radius; + // Closest point on triangle to sphere center b3TrianglePoint closest = b3ClosestPointOnTriangle( v1, v2, v3, center ); // Test separating axis float squaredDistance = b3DistanceSquared( closest.point, center ); float speculativeDistance = B3_SPECULATIVE_DISTANCE; - float maxDistance = sphereA->radius + speculativeDistance; + float maxDistance = radius + speculativeDistance; if ( squaredDistance > maxDistance * maxDistance ) { return; } float distance = sqrtf( squaredDistance ); + + // Normal points from triangle to sphere b3Vec3 normal; if ( distance * distance > 1000.0f * FLT_MIN ) { @@ -95,7 +98,8 @@ void b3CollideSphereAndTriangle( b3LocalManifold* manifold, int capacity, const } // contact point mid-way - b3Vec3 contactPoint = b3MulSV( 0.5f, b3Add( b3Sub( center, b3MulSV( sphereA->radius, normal ) ), closest.point ) ); + // p = 0.5 * (c + q) - 0.5 * r * n + b3Vec3 contactPoint = b3MulSV( 0.5f, b3Add( b3Sub( center, b3MulSV( radius, normal ) ), closest.point ) ); manifold->normal = normal; manifold->pointCount = 1; @@ -104,7 +108,7 @@ void b3CollideSphereAndTriangle( b3LocalManifold* manifold, int capacity, const b3LocalManifoldPoint* mp = manifold->points + 0; mp->point = contactPoint; - mp->separation = distance - sphereA->radius; + mp->separation = distance - radius; mp->pair = b3FeaturePair_single; } @@ -157,56 +161,85 @@ static bool b3ClipSegmentToTriangleFace( b3ClipVertex segment[2], const b3Vec3* return true; } -static b3FaceQuery b3QueryTriangleFaceAndCapsule( b3Plane plane, const b3Capsule* capsule ) +static b3SeparatingAxis b3QueryTriangleFaceAndCapsule( b3Plane plane, const b3Capsule* capsule ) { float separation1 = b3PlaneSeparation( plane, capsule->center1 ); float separation2 = b3PlaneSeparation( plane, capsule->center2 ); if ( separation1 < separation2 ) { - return (b3FaceQuery){ + return (b3SeparatingAxis){ + .normal = plane.normal, .separation = separation1, - .faceIndex = 0, - .vertexIndex = 0, + .indexA = 0, + .indexB = 0, }; } - return (b3FaceQuery){ + return (b3SeparatingAxis){ + .normal = plane.normal, .separation = separation2, - .faceIndex = 0, - .vertexIndex = 1, + .indexA = 0, + .indexB = 1, }; } -static b3EdgeQuery b3QueryTriangleAndCapsuleEdges( const b3Vec3* vertices, const b3Capsule* capsule ) +static b3SeparatingAxis b3QueryTriangleAndCapsuleEdges( const b3Vec3* vertices, b3Plane plane, const b3Capsule* capsule ) { // Work in the local space of the capsule b3Vec3 p1 = capsule->center1; b3Vec3 p2 = capsule->center2; b3Vec3 capsuleEdge = b3Sub( p2, p1 ); - b3Vec3 capsuleCenter = b3Lerp( p1, p2, 0.5f ); - - b3Vec3 triangleCenter = b3MulSV( 1.0f / 3.0f, b3Add( vertices[0], b3Add( vertices[1], vertices[2] ) ) ); - // Find axis of minimum penetration + b3Vec3 maxNormal = b3Vec3_zero; float maxSeparation = -FLT_MAX; - int maxIndex1 = UINT8_MAX; - int maxIndex2 = UINT8_MAX; + int maxIndex1 = B3_NULL_INDEX; + int maxIndex2 = B3_NULL_INDEX; + float squaredTolerance = 0.005f * 0.005f; int edgeIndex = 2; b3Vec3 v1 = vertices[2]; for ( int index = 0; index < 3; ++index ) { b3Vec3 v2 = vertices[index]; - b3Vec3 triangleEdge = b3Sub( v2, v1 ); + b3Vec3 sideNormal = b3Normalize( b3Cross( triangleEdge, plane.normal ) ); + + // Pretend the triangle edge embeds a zero area face with a side normal. This + // provides a way to find an edge-edge normal that points outward from + // the triangle. + float a = b3Dot( capsuleEdge, plane.normal ); + float b = b3Dot( capsuleEdge, sideNormal ); + + // Is the capsule edge parallel to the triangle edge? If so, face contact can handle it. + if ( a * a + b * b < squaredTolerance * b3LengthSquared( capsuleEdge ) ) + { + continue; + } + + // Similar to hull vs hull (b3QueryEdgeDirections) + b3Vec3 axis; + if ( a * b <= 0.0f ) + { + float t = b / ( b - a ); + axis = b3Lerp( sideNormal, plane.normal, t ); + } + else + { + float t = b / ( a + b ); + axis = b3Lerp( sideNormal, b3Neg( plane.normal ), t ); + } + + B3_VALIDATE( b3LengthSquared( axis ) > 1000.0f * FLT_MIN ); + axis = b3Normalize( axis ); + float separation = b3Dot( axis, b3Sub( p1, v1 ) ); - float separation = b3EdgeEdgeSeparation( p1, capsuleEdge, capsuleCenter, v1, triangleEdge, triangleCenter ); if ( separation > maxSeparation ) { // Note: We don't exit early if we find a separating axis here since we want to // find the best one for caching and account for the convex radius later. + maxNormal = axis; maxSeparation = separation; maxIndex1 = edgeIndex; maxIndex2 = 0; @@ -217,10 +250,11 @@ static b3EdgeQuery b3QueryTriangleAndCapsuleEdges( const b3Vec3* vertices, const } // Save result - return (b3EdgeQuery){ + return (b3SeparatingAxis){ + .normal = maxNormal, .separation = maxSeparation, - .indexA = (uint8_t)maxIndex1, - .indexB = (uint8_t)maxIndex2, + .indexA = maxIndex1, + .indexB = maxIndex2, }; } @@ -272,8 +306,8 @@ static void b3BuildTriangleAndCapsuleFaceContact( b3LocalManifold* manifold, con pt->pair = segment[1].pair; } -static void b3BuildTriangleAndCapsuleEdgeContact( b3LocalManifold* manifold, const b3Vec3* triangle, const b3Capsule* capsule, - b3EdgeQuery query ) +static void b3BuildTriangleAndCapsuleEdgeContact( b3LocalManifold* manifold, const b3Vec3* triangle, b3Plane plane, + const b3Capsule* capsule, b3SeparatingAxis query ) { B3_ASSERT( 0 <= query.indexA && query.indexA < 3 ); @@ -283,20 +317,27 @@ static void b3BuildTriangleAndCapsuleEdgeContact( b3LocalManifold* manifold, con const b3Vec3* vs = triangle; - b3Vec3 triangleCenter = b3MulSV( 1.0f / 3.0f, b3Add( vs[0], b3Add( vs[1], vs[2] ) ) ); b3Vec3 v1 = vs[query.indexA]; b3Vec3 v2 = vs[( query.indexA + 1 ) % 3]; b3Vec3 triangleEdge = b3Sub( v2, v1 ); - b3Vec3 normal = b3Cross( capsuleEdge, triangleEdge ); - normal = b3Normalize( normal ); + b3Vec3 sideNormal = b3Normalize( b3Cross( triangleEdge, plane.normal ) ); - // Normal should point away from triangle center - if ( b3Dot( normal, b3Sub( v1, triangleCenter ) ) < 0.0f ) + // Pretend the triangle edge embeds a zero area face with a side normal. This + // provides a way to find an edge-edge normal that points outward from + // the triangle. + float a = b3Dot( capsuleEdge, plane.normal ); + float b = b3Dot( capsuleEdge, sideNormal ); + + // Is the capsule edge parallel to the triangle edge? If so, face contact can handle it. + float squaredTolerance = 0.005f * 0.005f; + if ( a * a + b * b < squaredTolerance * b3LengthSquared( capsuleEdge ) ) { - normal = b3Neg( normal ); + return; } + // Similar to hull vs hull (b3QueryEdgeDirections) + b3Vec3 normal = query.normal; b3SegmentDistanceResult result = b3LineDistance( v1, triangleEdge, p1, capsuleEdge ); if ( result.fraction1 < 0.0f || 1.0f < result.fraction1 || result.fraction2 < 0.0f || 1.0f < result.fraction2 ) @@ -306,8 +347,7 @@ static void b3BuildTriangleAndCapsuleEdgeContact( b3LocalManifold* manifold, con } b3Vec3 point = b3Lerp( b3MulSub( result.point1, capsule->radius, normal ), result.point2, 0.5f ); - - float separation = b3Dot( normal, b3Sub( result.point2, result.point1 ) ); + float separation = b3Dot( normal, b3Sub( p1, v1 ) ); B3_VALIDATE( b3AbsFloat( separation - query.separation ) < B3_LINEAR_SLOP ); manifold->normal = normal; @@ -322,7 +362,7 @@ static void b3BuildTriangleAndCapsuleEdgeContact( b3LocalManifold* manifold, con pt->pair = b3MakeFeaturePair( b3_featureShapeA, query.indexA, b3_featureShapeB, query.indexB ); } -void b3CollideCapsuleAndTriangle( b3LocalManifold* manifold, int capacity, const b3Capsule* capsuleA, const b3Vec3* triangleB, +void b3CollideTriangleAndCapsule( b3LocalManifold* manifold, int capacity, const b3Vec3* triangleA, const b3Capsule* capsuleB, b3SimplexCache* cache ) { manifold->pointCount = 0; @@ -332,9 +372,9 @@ void b3CollideCapsuleAndTriangle( b3LocalManifold* manifold, int capacity, const return; } - b3Vec3 v1 = triangleB[0], v2 = triangleB[1], v3 = triangleB[2]; + b3Vec3 v1 = triangleA[0], v2 = triangleA[1], v3 = triangleA[2]; b3Plane plane = b3MakePlaneFromPoints( v1, v2, v3 ); - b3Vec3 capsuleCenter = b3Lerp( capsuleA->center1, capsuleA->center2, 0.5f ); + b3Vec3 capsuleCenter = b3Lerp( capsuleB->center1, capsuleB->center2, 0.5f ); float offset = b3PlaneSeparation( plane, capsuleCenter ); if ( offset < 0.0f ) @@ -344,15 +384,15 @@ void b3CollideCapsuleAndTriangle( b3LocalManifold* manifold, int capacity, const } b3DistanceInput distanceInput; - distanceInput.proxyA = (b3ShapeProxy){ triangleB, 3, 0.0f }; - distanceInput.proxyB = (b3ShapeProxy){ &capsuleA->center1, 2, 0.0f }; + distanceInput.proxyA = (b3ShapeProxy){ triangleA, 3, 0.0f }; + distanceInput.proxyB = (b3ShapeProxy){ &capsuleB->center1, 2, 0.0f }; distanceInput.transform = b3Transform_identity; distanceInput.useRadii = false; b3DistanceOutput distanceOutput = b3ShapeDistance( &distanceInput, cache, NULL, 0 ); - - float radius = capsuleA->radius; - if ( distanceOutput.distance > radius + B3_SPECULATIVE_DISTANCE ) + float speculativeDistance = B3_SPECULATIVE_DISTANCE; + float radius = capsuleB->radius; + if ( distanceOutput.distance > radius + speculativeDistance ) { // Shapes are separated, persist the cache return; @@ -370,14 +410,14 @@ void b3CollideCapsuleAndTriangle( b3LocalManifold* manifold, int capacity, const { // Clip capsule segment against side planes of reference face b3ClipVertex segment[2]; - segment[0].position = capsuleA->center1; + segment[0].position = capsuleB->center1; segment[0].separation = 0.0f; segment[0].pair = b3MakeFeaturePair( b3_featureShapeA, 0, b3_featureShapeA, 0 ); - segment[1].position = capsuleA->center2; + segment[1].position = capsuleB->center2; segment[1].separation = 0.0f; segment[1].pair = b3MakeFeaturePair( b3_featureShapeA, 1, b3_featureShapeA, 1 ); - bool havePoints = b3ClipSegmentToTriangleFace( segment, triangleB, plane ); + bool havePoints = b3ClipSegmentToTriangleFace( segment, triangleA, plane ); if ( havePoints == true ) { @@ -388,6 +428,7 @@ void b3CollideCapsuleAndTriangle( b3LocalManifold* manifold, int capacity, const b3Vec3 point1 = b3MulSub( segment[0].position, 0.5f * ( radius + distance1 ), normal ); b3Vec3 point2 = b3MulSub( segment[1].position, 0.5f * ( radius + distance2 ), normal ); + // Normal points from triangle to capsule manifold->normal = normal; manifold->feature = b3_featureTriangleFace; manifold->pointCount = 2; @@ -406,9 +447,10 @@ void b3CollideCapsuleAndTriangle( b3LocalManifold* manifold, int capacity, const } } - // Create contact from closest points + // Create contact from closest points. b3Vec3 point = b3MulSV( 0.5f, b3Add( b3Sub( distanceOutput.pointA, b3MulSV( radius, delta ) ), distanceOutput.pointB ) ); + // Normal points from triangle to capsule. manifold->normal = delta; manifold->pointCount = 1; manifold->feature = b3GetTriangleFeature( cache ); @@ -423,38 +465,38 @@ void b3CollideCapsuleAndTriangle( b3LocalManifold* manifold, int capacity, const // Deep penetration - b3FaceQuery faceQuery = b3QueryTriangleFaceAndCapsule( plane, capsuleA ); + b3SeparatingAxis faceQuery = b3QueryTriangleFaceAndCapsule( plane, capsuleB ); if ( faceQuery.separation > radius ) { - // Shapes are separated + // Shapes are separated. Should be impossible for a reasonable capsule radius. return; } - b3EdgeQuery edgeQuery = b3QueryTriangleAndCapsuleEdges( triangleB, capsuleA ); + b3SeparatingAxis edgeQuery = b3QueryTriangleAndCapsuleEdges( triangleA, plane, capsuleB ); if ( edgeQuery.separation > radius ) { - // Shapes are separated + // Shapes are separated. Should be impossible for a reasonable capsule radius. return; } // Create face contact float faceSeparation = faceQuery.separation - radius; - b3BuildTriangleAndCapsuleFaceContact( manifold, triangleB, plane, capsuleA ); + b3BuildTriangleAndCapsuleFaceContact( manifold, triangleA, plane, capsuleB ); + B3_VALIDATE( manifold->pointCount == 0 || manifold->pointCount == 2 ); if ( manifold->pointCount == 2 ) { + // This becomes the clipped separation. faceSeparation = b3MinFloat( manifold->points[0].separation, manifold->points[1].separation ); } - B3_VALIDATE( faceSeparation <= 0.0f ); // Face contact can be empty if it does not realize the axis of minimum penetration. // Create edge contact if face contact fails or edge contact is significantly better! - const float kRelEdgeTolerance = 0.50f; - const float kAbsTolerance = 1.0f * B3_LINEAR_SLOP; + float linearSlop = B3_LINEAR_SLOP; float edgeSeparation = edgeQuery.separation - radius; - if ( manifold->pointCount == 0 || edgeSeparation > kRelEdgeTolerance * faceSeparation + kAbsTolerance ) + if ( manifold->pointCount == 0 || edgeSeparation > faceSeparation + linearSlop ) { // Edge contact - b3BuildTriangleAndCapsuleEdgeContact( manifold, triangleB, capsuleA, edgeQuery ); + b3BuildTriangleAndCapsuleEdgeContact( manifold, triangleA, plane, capsuleB, edgeQuery ); } } @@ -479,31 +521,35 @@ static inline int b3GetTriangleSupport( b3Vec3* points, b3Vec3 direction ) return index; } -static b3FaceQuery b3QueryTriangleFace( const b3TriangleData* triangle, const b3HullData* hull ) +static b3SeparatingAxis b3QueryTriangleFace( const b3TriangleData* triangle, const b3HullData* hull ) { const b3Vec3* hullPoints = b3GetHullPoints( hull ); b3Plane plane = triangle->plane; - int vertexIndex = b3FindHullSupportVertex( hull, b3Neg( plane.normal ) ); + b3Vec3 normal = b3Neg( plane.normal ); + int vertexIndex = b3FindHullSupportVertex( hull, normal ); b3Vec3 support = hullPoints[vertexIndex]; float separation = b3PlaneSeparation( plane, support ); - return (b3FaceQuery){ + return (b3SeparatingAxis){ + .normal = plane.normal, .separation = separation, - .faceIndex = 0, - .vertexIndex = (uint8_t)vertexIndex, + .indexA = 0, + .indexB = vertexIndex, + .type = b3_faceAxisA, }; } -static b3FaceQuery b3QueryHullFace( const b3TriangleData* triangle, const b3HullData* hull ) +static b3SeparatingAxis b3QueryHullFace( const b3TriangleData* triangle, const b3HullData* hull ) { const b3Plane* hullPlanes = b3GetHullPlanes( hull ); int faceCount = hull->faceCount; b3Vec3 trianglePoints[] = { triangle->v1, triangle->v2, triangle->v3 }; - int maxFaceIndex = -1; - int maxVertexIndex = -1; - float maxFaceSeparation = -FLT_MAX; + b3Vec3 maxNormal = b3Vec3_zero; + float maxFaceSeparation = -INFINITY; + int maxFaceIndex = B3_NULL_INDEX; + int maxVertexIndex = B3_NULL_INDEX; for ( int faceIndex = 0; faceIndex < faceCount; ++faceIndex ) { @@ -514,25 +560,32 @@ static b3FaceQuery b3QueryHullFace( const b3TriangleData* triangle, const b3Hull float separation = b3PlaneSeparation( plane, support ); if ( separation > maxFaceSeparation ) { + maxNormal = plane.normal; + maxFaceSeparation = separation; maxFaceIndex = faceIndex; maxVertexIndex = vertexIndex; - maxFaceSeparation = separation; } } - return (b3FaceQuery){ + // Normal points from triangle to hull + return (b3SeparatingAxis){ + .normal = b3Neg( maxNormal ), .separation = maxFaceSeparation, - .faceIndex = maxFaceIndex, - .vertexIndex = maxVertexIndex, + .indexA = maxVertexIndex, + .indexB = maxFaceIndex, + .type = b3_faceAxisB, }; } -static b3EdgeQuery b3TestEdgePairs( const b3TriangleData* triangle, const b3HullData* hull ) +// A: hull, B: triangle +static b3SeparatingAxis b3QueryTriangleAndHullEdges( const b3TriangleData* triangle, const b3HullData* hull ) { - b3EdgeQuery result = { - .separation = -FLT_MAX, + b3SeparatingAxis result = { + .normal = b3Vec3_zero, + .separation = -INFINITY, .indexA = B3_NULL_INDEX, .indexB = B3_NULL_INDEX, + .type = b3_edgePairAxis, }; b3Vec3 trianglePoints[] = { triangle->v1, triangle->v2, triangle->v3 }; @@ -552,6 +605,7 @@ static b3EdgeQuery b3TestEdgePairs( const b3TriangleData* triangle, const b3Hull const b3Vec3* hullPoints = b3GetHullPoints( hull ); const b3Plane* hullPlanes = b3GetHullPlanes( hull ); int edgeCount = hull->edgeCount; + float squaredTolerance = 0.005f * 0.005f; for ( int i = 0; i < edgeCount; i += 2 ) { @@ -577,14 +631,28 @@ static b3EdgeQuery b3TestEdgePairs( const b3TriangleData* triangle, const b3Hull continue; } - b3Vec3 triPoint = trianglePoints[j]; - float separation = b3EdgeEdgeSeparation( triPoint, triEdge, triangle->center, hullPoint, hullEdge, hull->center ); + // Avoid nearly parallel edges that may lead to invalid separation values at the noise floor. + if ( b3MaxFloat( cab * cab, dab * dab ) < squaredTolerance * b3LengthSquared( triEdge ) ) + { + continue; + } + + // Similar to hull vs hull (b3QueryEdgeDirections) + // dot(hullNormal1 + t * (hullNormal2 - hullNormal1), triEdge) = 0 + // Normal points out of hull by construction. + float t = cab / ( cab - dab ); + b3Vec3 axis = b3Lerp( hullNormal1, hullNormal2, t ); + B3_VALIDATE( b3LengthSquared( axis ) > 1000.0f * FLT_MIN ); + axis = b3Normalize( axis ); + float separation = b3Dot( axis, b3Sub( trianglePoints[j], hullPoint ) ); // if ( separation > result.separation && ( edgeFlags[j] & triangleFlags ) == 0 ) if ( separation > result.separation ) { // Note: We don't exit early if we find a separating axis here since we want to // find the best one for caching. + // Flip normal to point from triangle to hull. + result.normal = b3Neg( axis ); result.separation = separation; result.indexA = j; result.indexB = i; @@ -596,8 +664,12 @@ static b3EdgeQuery b3TestEdgePairs( const b3TriangleData* triangle, const b3Hull } static float b3CollideHullFace( b3LocalManifold* manifold, int pointCapacity, const b3TriangleData* triangle, - const b3HullData* hull, b3FaceQuery query, b3SATCache* cache, bool enableSpeculative ) + const b3HullData* hull, b3SeparatingAxis query, b3SATCache* cache, bool enableSpeculative ) { + B3_VALIDATE( query.type == b3_faceAxisB ); + B3_VALIDATE( 0 <= query.indexA && query.indexA < 3 ); + B3_VALIDATE( 0 <= query.indexB && query.indexB < hull->faceCount ); + manifold->pointCount = 0; const b3HullFace* hullFaces = b3GetHullFaces( hull ); @@ -606,8 +678,7 @@ static float b3CollideHullFace( b3LocalManifold* manifold, int pointCapacity, co const b3Vec3* hullPoints = b3GetHullPoints( hull ); // Reference hull face - int refFace = query.faceIndex; - b3Plane refPlane = hullPlanes[refFace]; + b3Plane refPlane = hullPlanes[query.indexB]; // Build clip polygon from triangle face (the incident face) b3ClipVertex buffer1[B3_MAX_CLIP_POINTS], buffer2[B3_MAX_CLIP_POINTS]; @@ -630,7 +701,7 @@ static float b3CollideHullFace( b3LocalManifold* manifold, int pointCapacity, co b3ClipVertex* input = buffer1; b3ClipVertex* output = buffer2; - const b3HullFace* face = hullFaces + refFace; + const b3HullFace* face = hullFaces + query.indexB; int edgeIndex = face->edge; do @@ -702,14 +773,17 @@ static float b3CollideHullFace( b3LocalManifold* manifold, int pointCapacity, co // Save cache cache->separation = minSeparation; cache->type = b3_faceAxisB; - cache->indexA = (uint8_t)query.vertexIndex; - cache->indexB = (uint8_t)query.faceIndex; + cache->indexA = (uint8_t)query.indexA; + cache->indexB = (uint8_t)query.indexB; return minSeparation; } static float b3CollideTriangleFace( b3LocalManifold* manifold, int pointCapacity, const b3TriangleData* triangle, - const b3HullData* hull, b3FaceQuery query, b3SATCache* cache, bool enableSpeculative ) + const b3HullData* hull, b3SeparatingAxis query, b3SATCache* cache, bool enableSpeculative ) { + B3_VALIDATE( query.type == b3_faceAxisA ); + B3_VALIDATE( query.indexA == 0 ); + B3_VALIDATE( 0 <= query.indexB && query.indexB < hull->vertexCount ); B3_VALIDATE( manifold->pointCount == 0 ); const b3HullFace* hullFaces = b3GetHullFaces( hull ); @@ -717,10 +791,9 @@ static float b3CollideTriangleFace( b3LocalManifold* manifold, int pointCapacity const b3Vec3* hullPoints = b3GetHullPoints( hull ); // Find incident face - B3_ASSERT( query.faceIndex == 0 ); b3Plane refPlane = triangle->plane; - int incFace = b3FindIncidentFace( hull, refPlane.normal, query.vertexIndex ); + int incFace = b3FindIncidentFace( hull, refPlane.normal, query.indexB ); // Build clip polygon from incident face b3ClipVertex buffer1[2 * B3_MAX_CLIP_POINTS], buffer2[2 * B3_MAX_CLIP_POINTS]; @@ -816,18 +889,19 @@ static float b3CollideTriangleFace( b3LocalManifold* manifold, int pointCapacity // Save cache cache->separation = minSeparation; cache->type = b3_faceAxisA; - cache->indexA = (uint8_t)query.faceIndex; - cache->indexB = (uint8_t)query.vertexIndex; + cache->indexA = (uint8_t)query.indexA; + cache->indexB = (uint8_t)query.indexB; return minSeparation; } -static void b3CollideHullAndTriangleEdges( b3LocalManifold* manifold, int capacity, b3Vec3 trianglePoint, b3Vec3 triangleEdge, - b3Vec3 triangleCenter, const b3HullData* hull, b3EdgeQuery query, b3SATCache* cache ) +static void b3CollideTriangleAndHullEdges( b3LocalManifold* manifold, int capacity, b3Vec3 trianglePoint, b3Vec3 triangleEdge, + const b3HullData* hull, b3SeparatingAxis query, b3SATCache* cache ) { + B3_VALIDATE( query.type == b3_edgePairAxis ); + B3_VALIDATE( 0 <= query.indexA && query.indexA < 3 ); + B3_VALIDATE( 0 <= query.indexB && query.indexB < hull->edgeCount ); B3_VALIDATE( query.separation <= 2.0f * B3_SPECULATIVE_DISTANCE ); - B3_ASSERT( query.indexA < 3 ); - b3Vec3 cA = triangleCenter; b3Vec3 pA = trianglePoint; b3Vec3 eA = triangleEdge; @@ -839,32 +913,6 @@ static void b3CollideHullAndTriangleEdges( b3LocalManifold* manifold, int capaci b3Vec3 qB = pointsB[twinB->origin]; b3Vec3 eB = b3Sub( qB, pB ); - b3Vec3 normal = b3Cross( eA, eB ); - normal = b3Normalize( normal ); - - // Ensure normal points outward from triangle center - float outwardA = b3Dot( normal, b3Sub( pA, cA ) ); - - // Ensure normal points towards hull center - float outwardB = b3Dot( normal, b3Sub( hull->center, pB ) ); - - // Use the largest magnitude. The triangle outward value - // may be unreliable as some angles. - if ( b3AbsFloat( outwardA ) > b3AbsFloat( outwardB ) ) - { - if ( outwardA < 0.0f ) - { - normal = b3Neg( normal ); - } - } - else - { - if ( outwardB < 0.0f ) - { - normal = b3Neg( normal ); - } - } - // Get the closest points between the infinite edge lines b3SegmentDistanceResult result = b3LineDistance( pA, eA, pB, eB ); @@ -879,7 +927,7 @@ static void b3CollideHullAndTriangleEdges( b3LocalManifold* manifold, int capaci } // This can slide off the end from caching - float separation = b3Dot( normal, b3Sub( result.point2, result.point1 ) ); + float separation = b3Dot( query.normal, b3Sub( pB, pA ) ); B3_VALIDATE( b3AbsFloat( separation - query.separation ) < B3_LINEAR_SLOP ); b3Vec3 point = b3MulSV( 0.5f, b3Add( result.point1, result.point2 ) ); @@ -895,30 +943,19 @@ static void b3CollideHullAndTriangleEdges( b3LocalManifold* manifold, int capaci cache->indexA = (uint8_t)query.indexA; cache->indexB = (uint8_t)query.indexB; - manifold->normal = normal; + manifold->normal = query.normal; manifold->pointCount = 1; b3TriangleFeature edgesFeatures[] = { b3_featureEdge1, b3_featureEdge2, b3_featureEdge3 }; manifold->feature = edgesFeatures[query.indexA]; } -// See "Collision Detection of Convex Polyhedra Based on Duality Transformation" -// Simplified for triangle versus hull -static inline bool b3IsTriangleMinkowskiFace( b3Vec3 triNormal, b3Vec3 triEdge, b3Vec3 hullNormal1, b3Vec3 hullNormal2, - b3Vec3 hullEdge ) -{ - float cab = b3Dot( hullNormal1, triEdge ); - float dab = b3Dot( hullNormal2, triEdge ); - float bcd = b3Dot( triNormal, hullEdge ); - return cab * dab < 0.0f && cab * bcd > 0.0f; -} - b3AtomicInt b3_triangleConvexCalls; b3AtomicInt b3_triangleCacheHits; -// Computes the manifold in the local space of the hull -void b3CollideHullAndTriangle( b3LocalManifold* manifold, int capacity, const b3HullData* hullA, b3Vec3 v1, b3Vec3 v2, b3Vec3 v3, - int triangleFlags, b3SATCache* cache, bool enableSpeculative ) +// Triangle is in the local space of the hull for efficiency. +void b3CollideTriangleAndHull( b3LocalManifold* manifold, int capacity, b3Vec3 v1, b3Vec3 v2, b3Vec3 v3, int triangleFlags, + const b3HullData* hullB, b3SATCache* cache, bool enableSpeculative ) { manifold->pointCount = 0; manifold->feature = b3_featureNone; @@ -931,7 +968,7 @@ void b3CollideHullAndTriangle( b3LocalManifold* manifold, int capacity, const b3 b3Plane trianglePlane = b3MakePlaneFromPoints( v1, v2, v3 ); float linearSlop = B3_LINEAR_SLOP; - float offset = b3PlaneSeparation( trianglePlane, hullA->center ); + float offset = b3PlaneSeparation( trianglePlane, hullB->center ); if ( cache->type == b3_backsideAxis ) { // Use hysteresis to avoid jitter on wavy meshes @@ -951,7 +988,6 @@ void b3CollideHullAndTriangle( b3LocalManifold* manifold, int capacity, const b3 return; } - b3Vec3 triangleCenter = b3MulSV( 1.0f / 3.0f, b3Add( v1, b3Add( v2, v3 ) ) ); b3Vec3 trianglePoints[] = { v1, v2, v3 }; b3Vec3 triangleEdges[] = { b3Sub( v2, v1 ), b3Sub( v3, v2 ), b3Sub( v1, v3 ) }; @@ -962,14 +998,13 @@ void b3CollideHullAndTriangle( b3LocalManifold* manifold, int capacity, const b3 .e1 = triangleEdges[0], .e2 = triangleEdges[1], .e3 = triangleEdges[2], - .center = triangleCenter, .plane = trianglePlane, .flags = triangleFlags, }; - const b3HullHalfEdge* edges = b3GetHullEdges( hullA ); - const b3Plane* hullPlanes = b3GetHullPlanes( hullA ); - const b3Vec3* hullPoints = b3GetHullPoints( hullA ); + const b3HullHalfEdge* edges = b3GetHullEdges( hullB ); + const b3Plane* hullPlanes = b3GetHullPlanes( hullB ); + const b3Vec3* hullPoints = b3GetHullPoints( hullB ); float speculativeDistance = enableSpeculative ? B3_SPECULATIVE_DISTANCE : 0.0f; cache->hit = 1; @@ -981,7 +1016,7 @@ void b3CollideHullAndTriangle( b3LocalManifold* manifold, int capacity, const b3 { B3_ASSERT( cache->indexA == 0 ); - int vertexIndex = b3FindHullSupportVertex( hullA, b3Neg( trianglePlane.normal ) ); + int vertexIndex = b3FindHullSupportVertex( hullB, b3Neg( trianglePlane.normal ) ); b3Vec3 support = hullPoints[vertexIndex]; float separation = b3PlaneSeparation( trianglePlane, support ); if ( separation > speculativeDistance ) @@ -990,15 +1025,17 @@ void b3CollideHullAndTriangle( b3LocalManifold* manifold, int capacity, const b3 return; } - b3FaceQuery faceQuery; + b3SeparatingAxis faceQuery; + faceQuery.normal = trianglePlane.normal; faceQuery.separation = separation; - faceQuery.faceIndex = cache->indexA; - faceQuery.vertexIndex = vertexIndex; + faceQuery.indexA = cache->indexA; + faceQuery.indexB = vertexIndex; + faceQuery.type = b3_faceAxisA; // Read cache but don't modify it b3SATCache localCache = *cache; float clippedSeparation = - b3CollideTriangleFace( manifold, capacity, &triangle, hullA, faceQuery, &localCache, enableSpeculative ); + b3CollideTriangleFace( manifold, capacity, &triangle, hullB, faceQuery, &localCache, enableSpeculative ); if ( manifold->pointCount > 0 && b3AbsFloat( cache->separation - clippedSeparation ) < linearSlop ) { @@ -1014,7 +1051,7 @@ void b3CollideHullAndTriangle( b3LocalManifold* manifold, int capacity, const b3 case b3_faceAxisB: { - B3_ASSERT( cache->indexB < hullA->faceCount ); + B3_ASSERT( cache->indexB < hullB->faceCount ); b3Plane plane = hullPlanes[cache->indexB]; @@ -1049,15 +1086,17 @@ void b3CollideHullAndTriangle( b3LocalManifold* manifold, int capacity, const b3 if ( isDeep == false ) { // Try to rebuild contact from last features - b3FaceQuery faceQuery; + b3SeparatingAxis faceQuery; + faceQuery.normal = b3Neg( plane.normal ); faceQuery.separation = separation; - faceQuery.faceIndex = cache->indexB; - faceQuery.vertexIndex = vertexIndex; + faceQuery.indexA = vertexIndex; + faceQuery.indexB = cache->indexB; + faceQuery.type = b3_faceAxisB; // Read cache but don't modify it b3SATCache localCache = *cache; float clippedSeparation = - b3CollideHullFace( manifold, capacity, &triangle, hullA, faceQuery, &localCache, enableSpeculative ); + b3CollideHullFace( manifold, capacity, &triangle, hullB, faceQuery, &localCache, enableSpeculative ); // Cache reuse is only successful if it creates contact points and the clipped separation didn't change much. if ( manifold->pointCount > 0 && b3AbsFloat( cache->separation - clippedSeparation ) < linearSlop ) @@ -1081,7 +1120,7 @@ void b3CollideHullAndTriangle( b3LocalManifold* manifold, int capacity, const b3 b3Vec3 triPoint = trianglePoints[indexA]; b3Vec3 triEdge = triangleEdges[indexA]; - B3_ASSERT( cache->indexB < hullA->edgeCount - 1 ); + B3_ASSERT( cache->indexB < hullB->edgeCount - 1 ); int indexB = cache->indexB; const b3HullHalfEdge* edge2 = edges + indexB; @@ -1093,36 +1132,55 @@ void b3CollideHullAndTriangle( b3LocalManifold* manifold, int capacity, const b3 b3Vec3 hullNormal1 = hullPlanes[edge2->face].normal; b3Vec3 hullNormal2 = hullPlanes[twin2->face].normal; - // Confirm the edge pair is still a Minkowski face - bool isMinkowski = b3IsTriangleMinkowskiFace( trianglePlane.normal, triEdge, hullNormal1, hullNormal2, hullEdge ); - if ( isMinkowski ) + // Confirm the edge pair is still a Minkowski face. + // See "Collision Detection of Convex Polyhedra Based on Duality Transformation" + // Simplified for triangle versus hull. + float cab = b3Dot( hullNormal1, triEdge ); + float dab = b3Dot( hullNormal2, triEdge ); + float bcd = b3Dot( trianglePlane.normal, hullEdge ); + + if ( cab * dab < 0.0f && cab * bcd > 0.0f ) { - // Transform reference center of the first hull into local space of the second hull - float separation = b3EdgeEdgeSeparation( triPoint, triEdge, triangleCenter, hullPoint, hullEdge, hullA->center ); - if ( separation > speculativeDistance ) + float squaredTolerance = 0.005f * 0.005f; + + // Avoid nearly parallel edges that may lead to invalid separation values at the noise floor. + if ( b3MaxFloat( cab * cab, dab * dab ) >= squaredTolerance * b3LengthSquared( triEdge ) ) { - // Cache hit, shapes are separated - return; - } - - if ( b3AbsFloat( cache->separation - separation ) < linearSlop ) - { - // Try to rebuild contact from last features - b3EdgeQuery edgeQuery; - edgeQuery.indexA = indexA; - edgeQuery.indexB = indexB; - edgeQuery.separation = separation; - - // Read cache but don't modify it - b3SATCache localCache = *cache; - b3CollideHullAndTriangleEdges( manifold, capacity, triPoint, triEdge, triangleCenter, hullA, edgeQuery, - &localCache ); - - if ( manifold->pointCount > 0 ) + // Similar to hull vs hull (b3QueryEdgeDirections) + // dot(hullNormal1 + t * (hullNormal2 - hullNormal1), triEdge) = 0 + // Normal points out of hull by construction. + float t = cab / ( cab - dab ); + b3Vec3 axis = b3Lerp( hullNormal1, hullNormal2, t ); + B3_VALIDATE( b3LengthSquared( axis ) > 1000.0f * FLT_MIN ); + axis = b3Normalize( axis ); + float separation = b3Dot( axis, b3Sub( triPoint, hullPoint ) ); + if ( separation > speculativeDistance ) { - // Cache hit, contact point generated + // Cache hit, shapes are separated return; } + + if ( b3AbsFloat( cache->separation - separation ) < linearSlop ) + { + // Try to rebuild contact from last features + // Flip normal to point from triangle to hull + b3SeparatingAxis edgeQuery; + edgeQuery.normal = b3Neg( axis ); + edgeQuery.indexA = indexA; + edgeQuery.indexB = indexB; + edgeQuery.separation = separation; + edgeQuery.type = b3_edgePairAxis; + + // Read cache but don't modify it + b3SATCache localCache = *cache; + b3CollideTriangleAndHullEdges( manifold, capacity, triPoint, triEdge, hullB, edgeQuery, &localCache ); + + if ( manifold->pointCount > 0 ) + { + // Cache hit, contact point generated + return; + } + } } } @@ -1134,29 +1192,28 @@ void b3CollideHullAndTriangle( b3LocalManifold* manifold, int capacity, const b3 // This case is for testing case b3_manualFaceAxisA: { - b3FaceQuery faceQueryA = b3QueryTriangleFace( &triangle, hullA ); - b3CollideTriangleFace( manifold, capacity, &triangle, hullA, faceQueryA, cache, enableSpeculative ); + b3SeparatingAxis query = b3QueryTriangleFace( &triangle, hullB ); + b3CollideTriangleFace( manifold, capacity, &triangle, hullB, query, cache, enableSpeculative ); return; } // This case is for testing case b3_manualFaceAxisB: { - b3FaceQuery faceQueryB = b3QueryHullFace( &triangle, hullA ); - b3CollideHullFace( manifold, capacity, &triangle, hullA, faceQueryB, cache, enableSpeculative ); + b3SeparatingAxis query = b3QueryHullFace( &triangle, hullB ); + b3CollideHullFace( manifold, capacity, &triangle, hullB, query, cache, enableSpeculative ); return; } // This case is for testing case b3_manualEdgePairAxis: { - b3EdgeQuery edgeQuery = b3TestEdgePairs( &triangle, hullA ); - if ( edgeQuery.indexA != B3_NULL_INDEX ) + b3SeparatingAxis query = b3QueryTriangleAndHullEdges( &triangle, hullB ); + if ( query.indexA != B3_NULL_INDEX ) { - b3Vec3 trianglePoint = trianglePoints[edgeQuery.indexA]; - b3Vec3 triangleEdge = triangleEdges[edgeQuery.indexA]; - b3CollideHullAndTriangleEdges( manifold, capacity, trianglePoint, triangleEdge, triangleCenter, hullA, edgeQuery, - cache ); + b3Vec3 trianglePoint = trianglePoints[query.indexA]; + b3Vec3 triangleEdge = triangleEdges[query.indexA]; + b3CollideTriangleAndHullEdges( manifold, capacity, trianglePoint, triangleEdge, hullB, query, cache ); } return; } @@ -1170,29 +1227,29 @@ void b3CollideHullAndTriangle( b3LocalManifold* manifold, int capacity, const b3 cache->hit = 0; // Find axis of minimum penetration - b3FaceQuery faceQueryA = b3QueryTriangleFace( &triangle, hullA ); + b3SeparatingAxis faceQueryA = b3QueryTriangleFace( &triangle, hullB ); if ( faceQueryA.separation > speculativeDistance ) { // Separating axis found cache->separation = faceQueryA.separation; cache->type = b3_faceAxisA; - cache->indexA = 0; - cache->indexB = UINT8_MAX; + cache->indexA = (uint8_t)faceQueryA.indexA; + cache->indexB = (uint8_t)faceQueryA.indexB; return; } - b3FaceQuery faceQueryB = b3QueryHullFace( &triangle, hullA ); + b3SeparatingAxis faceQueryB = b3QueryHullFace( &triangle, hullB ); if ( faceQueryB.separation > speculativeDistance ) { // Separating axis found cache->separation = faceQueryB.separation; cache->type = b3_faceAxisB; - cache->indexA = UINT8_MAX; - cache->indexB = (uint8_t)faceQueryB.faceIndex; + cache->indexA = (uint8_t)faceQueryB.indexA; + cache->indexB = (uint8_t)faceQueryB.indexB; return; } - b3EdgeQuery edgeQuery = b3TestEdgePairs( &triangle, hullA ); + b3SeparatingAxis edgeQuery = b3QueryTriangleAndHullEdges( &triangle, hullB ); if ( edgeQuery.separation > speculativeDistance ) { // Separating axis found @@ -1203,21 +1260,18 @@ void b3CollideHullAndTriangle( b3LocalManifold* manifold, int capacity, const b3 return; } - float clippedFaceSeparation; + float clipSeparation; // Don't admit a hull face significantly opposed to the triangle face. // Need a tolerance to avoid ghost collisions. - // todo hull query skips faces that point along the triangle normal - b3Vec3 hullNormal = hullPlanes[faceQueryB.faceIndex].normal; - bool pushingDown = b3Dot( hullNormal, trianglePlane.normal ) > 0.25f; - if ( faceQueryB.separation > faceQueryA.separation + linearSlop && pushingDown == false ) + bool pushingDown = b3Dot( faceQueryB.normal, trianglePlane.normal ) < -0.25f; + if ( faceQueryB.separation >= faceQueryA.separation && pushingDown == false ) { - clippedFaceSeparation = b3CollideHullFace( manifold, capacity, &triangle, hullA, faceQueryB, cache, enableSpeculative ); + clipSeparation = b3CollideHullFace( manifold, capacity, &triangle, hullB, faceQueryB, cache, enableSpeculative ); } else { - clippedFaceSeparation = - b3CollideTriangleFace( manifold, capacity, &triangle, hullA, faceQueryA, cache, enableSpeculative ); + clipSeparation = b3CollideTriangleFace( manifold, capacity, &triangle, hullB, faceQueryA, cache, enableSpeculative ); } // Does an edge axis exist? @@ -1228,14 +1282,13 @@ void b3CollideHullAndTriangle( b3LocalManifold* manifold, int capacity, const b3 float maxFaceSeparation = b3MaxFloat( faceQueryA.separation, faceQueryB.separation ); if ( ( manifold->pointCount == 0 && edgeQuery.separation > maxFaceSeparation ) || - ( manifold->pointCount == 1 && edgeQuery.separation > clippedFaceSeparation + linearSlop ) ) + ( manifold->pointCount == 1 && edgeQuery.separation > clipSeparation + linearSlop ) ) { B3_ASSERT( 0 <= edgeQuery.indexA && edgeQuery.indexA < 3 ); b3Vec3 trianglePoint = trianglePoints[edgeQuery.indexA]; b3Vec3 triangleEdge = triangleEdges[edgeQuery.indexA]; manifold->pointCount = 0; - b3CollideHullAndTriangleEdges( manifold, capacity, trianglePoint, triangleEdge, triangleCenter, hullA, edgeQuery, - cache ); + b3CollideTriangleAndHullEdges( manifold, capacity, trianglePoint, triangleEdge, hullB, edgeQuery, cache ); } } @@ -1250,7 +1303,7 @@ void b3CollideHullAndTriangle( b3LocalManifold* manifold, int capacity, const b3 .count = 3, .radius = 0.0f, }; - input.proxyB = (b3ShapeProxy){ .points = hullPoints, .count = hullA->vertexCount, .radius = 0.0f }; + input.proxyB = (b3ShapeProxy){ .points = hullPoints, .count = hullB->vertexCount, .radius = 0.0f }; input.transform = b3Transform_identity; input.useRadii = false; @@ -1270,5 +1323,8 @@ void b3CollideHullAndTriangle( b3LocalManifold* manifold, int capacity, const b3 // This feature pair not accurate but maybe it doesn't matter manifold->points[0].pair = b3FeaturePair_single; } + + // No way to cache this scenario + *cache = (b3SATCache){ 0 }; } } diff --git a/vendor/box3d/src/src/types.c b/vendor/box3d/src/src/types.c index b199cd15d..7e2cd071e 100644 --- a/vendor/box3d/src/src/types.c +++ b/vendor/box3d/src/src/types.c @@ -80,10 +80,9 @@ b3ShapeDef b3DefaultShapeDef( void ) return def; } -static bool b3EmptyDrawShape( void* userShape, b3WorldTransform transform, b3HexColor color, void* context ) +static void b3EmptyDrawShape( void* userShape, b3WorldTransform transform, b3HexColor color, void* context ) { B3_UNUSED( userShape, transform, color, context ); - return false; } static void b3EmptyDrawSegment( b3Pos p1, b3Pos p2, b3HexColor color, void* context )