Update box3d

Update box3d to 3fc20f5b45

Fixes #7177
This commit is contained in:
Jeroen van Rijn
2026-08-04 14:16:51 +02:00
parent 14007d51d3
commit 11f481b4c3
50 changed files with 2994 additions and 1304 deletions

6
.gitattributes vendored
View File

@@ -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

View File

@@ -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.

View File

@@ -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)

View File

@@ -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
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

View File

@@ -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,

Binary file not shown.

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6b000b0b69028af14d6a481b6bf6ca3b70d2c3a29cd9e73eab8b96e0deffa07d
size 2524600
oid sha256:92ad74c3967667704cb7589ae6c64612a66112ea12ce4462b849f7aac4bc689f
size 2551264

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:948651aca53740a4a47a8251b68982b839fa62da91689035132be1c273cbd7da
size 1536158
oid sha256:d698fbbe655db1d1ec6f52dcc23bfed4a2c981fa88040ba449f9f83609edba88
size 3137140

View File

@@ -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 )

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 )

View File

@@ -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;

View File

@@ -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 "$<$<CONFIG:RELWITHDEBINFO>: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 "$<$<CONFIG:RELWITHDEBINFO>: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)

View File

@@ -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;

View File

@@ -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 );

View File

@@ -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 );

View File

@@ -208,7 +208,6 @@ typedef struct b3BodySim
float minExtent;
b3Vec3 maxExtent;
float maxAngularVelocity;
float linearDamping;
float angularDamping;
float gravityScale;

View File

@@ -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 ) );

View File

@@ -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;

View File

@@ -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 <arm_neon.h>
// wide float holds 4 numbers
typedef float32x4_t b3FloatW;
#elif defined( B3_SIMD_SSE2 )
#include <emmintrin.h>
// 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;
}

View File

@@ -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;

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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 );

View File

@@ -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

View File

@@ -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;

View File

@@ -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 );

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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:

View File

@@ -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;

View File

@@ -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 );

View File

@@ -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;

View File

@@ -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.

View File

@@ -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;

View File

@@ -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 ) )

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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 );

View File

@@ -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;
}

View File

@@ -7,10 +7,35 @@
#include <stdbool.h>
#if defined( B3_SIMD_SSE2 )
#if defined( B3_SIMD_NEON )
#include <arm_neon.h>
// wide float holds 4 numbers
typedef float32x4_t b3FloatW;
#elif defined( B3_SIMD_SSE2 )
#include <emmintrin.h>
// wide float holds 4 numbers
typedef __m128 b3FloatW;
#else
#include <math.h>
#include <string.h>
// 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

View File

@@ -24,6 +24,8 @@
#include <stddef.h>
#include <stdio.h>
_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 );

View File

@@ -23,7 +23,8 @@
#define WIN32_LEAN_AND_MEAN 1
#endif
#include <Windows.h>
// Lower-case windows.h intentionally for cross compiling on mingw.
#include <windows.h>
#include <limits.h>
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 )

View File

@@ -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 };
}
}

View File

@@ -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 )