#include "shape.h"
#include "body.h"
#include "broad_phase.h"
#include "contact.h"
#include "physics_world.h"
#include "recording.h"
#include "sensor.h"
#include "aabb.h"
#include "compound.h"
#include "box3d/box3d.h"
#include <inttypes.h>
#include <stdio.h>
static b3Shape* b3GetShape( b3World* world, b3ShapeId shapeId )
{
int id = shapeId.index1 - 1;
b3Shape* shape = b3Array_Get( world->shapes, id );
B3_ASSERT( shape->id == id && shape->generation == shapeId.generation );
return shape;
}
static float b3ComputeShapeMargin( b3Shape* shape )
{
float margin = 0.0f;
switch ( shape->type )
{
case b3_sphereShape:
{
margin = shape->sphere.radius;
}
break;
case b3_capsuleShape:
{
margin = 0.5f * b3Distance( shape->capsule.center2, shape->capsule.center1 ) + shape->capsule.radius;
}
break;
case b3_hullShape:
{
const b3HullData* hull = shape->hull;
const b3Vec3* points = b3GetHullPoints( hull );
float maxExtentSqr = 0.0f;
int count = hull->vertexCount;
for ( int i = 0; i < count; ++i )
{
float distSqr = b3DistanceSquared( points[i], hull->center );
maxExtentSqr = b3MaxFloat( maxExtentSqr, distSqr );
}
margin = sqrtf( maxExtentSqr );
}
break;
case b3_meshShape:
case b3_heightShape:
case b3_compoundShape:
{
return B3_MAX_AABB_MARGIN;
}
default:
B3_VALIDATE( false );
return B3_MAX_AABB_MARGIN;
}
return b3MinFloat( B3_MAX_AABB_MARGIN, B3_AABB_MARGIN_FRACTION * margin );
}
static void b3UpdateShapeAABBs( b3Shape* shape, b3WorldTransform transform, b3BodyType proxyType )
{
const float speculativeDistance = B3_SPECULATIVE_DISTANCE;
const float aabbMargin = shape->aabbMargin;
b3AABB aabb = b3ComputeFatShapeAABB( shape, transform, speculativeDistance );
shape->aabb = aabb;
float margin = proxyType == b3_staticBody ? speculativeDistance : aabbMargin;
b3AABB fatAABB;
fatAABB.lowerBound.x = aabb.lowerBound.x - margin;
fatAABB.lowerBound.y = aabb.lowerBound.y - margin;
fatAABB.lowerBound.z = aabb.lowerBound.z - margin;
fatAABB.upperBound.x = aabb.upperBound.x + margin;
fatAABB.upperBound.y = aabb.upperBound.y + margin;
fatAABB.upperBound.z = aabb.upperBound.z + margin;
shape->fatAABB = fatAABB;
}
static b3Shape* b3CreateShapeInternal( b3World* world, b3Body* body, b3WorldTransform bodyTransform, const b3ShapeDef* def,
const void* geometry, b3ShapeType shapeType, b3Transform shapeTransform, b3Vec3 scale,
bool haveShapeTransform )
{
int shapeId = b3AllocId( &world->shapeIdPool );
if ( shapeId == world->shapes.count )
{
b3Array_Push( world->shapes, (b3Shape){ 0 } );
}
else
{
B3_ASSERT( world->shapes.data[shapeId].id == B3_NULL_INDEX );
}
b3Shape* shape = b3Array_Get( world->shapes, shapeId );
switch ( shapeType )
{
case b3_capsuleShape:
shape->capsule = *(b3Capsule*)geometry;
break;
case b3_compoundShape:
B3_ASSERT( body->type == b3_staticBody );
B3_ASSERT( def->isSensor == false );
shape->compound = (b3CompoundData*)geometry;
break;
case b3_sphereShape:
shape->sphere = *(b3Sphere*)geometry;
break;
case b3_hullShape:
if ( haveShapeTransform )
{
b3HullData* baked = b3CloneAndTransformHull( (b3HullData*)geometry, shapeTransform, scale );
if ( baked == NULL )
{
b3FreeId( &world->shapeIdPool, shapeId );
shape->id = B3_NULL_INDEX;
return NULL;
}
shape->hull = b3AddOwnedHullToDatabase( world, baked );
}
else
{
shape->hull = b3AddHullToDatabase( world, (const b3HullData*)geometry );
}
break;
case b3_meshShape:
{
shape->mesh.data = (b3MeshData*)geometry;
shape->mesh.scale = b3SafeScale( scale );
}
break;
case b3_heightShape:
shape->heightField = (b3HeightFieldData*)geometry;
break;
default:
B3_ASSERT( false );
break;
}
shape->id = shapeId;
shape->bodyId = body->id;
shape->type = shapeType;
shape->density = def->density;
shape->explosionScale = def->explosionScale;
shape->filter = def->filter;
shape->userData = def->userData;
shape->userShape = NULL;
shape->enlargedAABB = false;
shape->enableSensorEvents = def->enableSensorEvents;
shape->enableContactEvents = def->enableContactEvents;
shape->enableCustomFiltering = def->enableCustomFiltering;
shape->enableHitEvents = def->enableHitEvents;
shape->enablePreSolveEvents = def->enablePreSolveEvents;
shape->proxyKey = B3_NULL_INDEX;
shape->localCentroid = b3GetShapeCentroid( shape );
shape->aabbMargin = b3ComputeShapeMargin( shape );
shape->aabb = (b3AABB){ b3Vec3_zero, b3Vec3_zero };
shape->fatAABB = (b3AABB){ b3Vec3_zero, b3Vec3_zero };
shape->generation += 1;
if ( shape->type == b3_compoundShape )
{
int materialCount = shape->compound->materialCount;
shape->materialCount = materialCount;
shape->materials = b3Alloc( materialCount * sizeof( b3SurfaceMaterial ) );
memcpy( shape->materials, b3GetCompoundMaterials( shape->compound ), materialCount * sizeof( b3SurfaceMaterial ) );
}
else if ( def->materialCount > 1 && def->materials != NULL )
{
shape->materialCount = def->materialCount;
shape->materials = b3Alloc( def->materialCount * sizeof( b3SurfaceMaterial ) );
memcpy( shape->materials, def->materials, def->materialCount * sizeof( b3SurfaceMaterial ) );
}
else
{
shape->material = ( def->materialCount == 1 && def->materials != NULL ) ? def->materials[0] : def->baseMaterial;
shape->materialCount = 1;
shape->materials = NULL;
}
if ( body->setIndex != b3_disabledSet )
{
b3BodyType proxyType = body->type;
bool forcePairCreation = def->invokeContactCreation && shape->type != b3_compoundShape;
b3CreateShapeProxy( shape, &world->broadPhase, proxyType, bodyTransform, forcePairCreation );
}
if ( body->headShapeId != B3_NULL_INDEX )
{
b3Shape* headShape = b3Array_Get( world->shapes, body->headShapeId );
headShape->prevShapeId = shapeId;
}
shape->prevShapeId = B3_NULL_INDEX;
shape->nextShapeId = body->headShapeId;
body->headShapeId = shapeId;
body->shapeCount += 1;
if ( def->isSensor )
{
shape->sensorIndex = world->sensors.count;
b3Sensor* sensor = b3Array_Emplace( world->sensors );
b3Array_CreateN( sensor->hits, 4 );
b3Array_CreateN( sensor->overlaps1, 16 );
b3Array_CreateN( sensor->overlaps2, 16 );
sensor->shapeId = shapeId;
}
else
{
shape->sensorIndex = B3_NULL_INDEX;
}
b3ValidateSolverSets( world );
return shape;
}
static b3ShapeId b3CreateShape( b3BodyId bodyId, const b3ShapeDef* def, const void* geometry, b3ShapeType shapeType,
b3Transform transform, b3Vec3 scale, bool haveTransform )
{
B3_CHECK_DEF( def );
B3_ASSERT( b3IsValidFloat( def->density ) && def->density >= 0.0f );
B3_ASSERT( b3IsValidFloat( def->baseMaterial.friction ) && def->baseMaterial.friction >= 0.0f );
B3_ASSERT( b3IsValidFloat( def->baseMaterial.restitution ) && def->baseMaterial.restitution >= 0.0f );
b3World* world = b3GetUnlockedWorld( bodyId.world0 );
if ( world == NULL )
{
return (b3ShapeId){ 0 };
}
if ( world->shapes.count == B3_MAX_SHAPES && world->shapeIdPool.freeArray.count == 0 )
{
B3_ASSERT( false );
return b3_nullShapeId;
}
b3Body* body = b3GetBodyFullId( world, bodyId );
if ( body->type != b3_staticBody && ( shapeType == b3_compoundShape || shapeType == b3_heightShape ) )
{
return b3_nullShapeId;
}
world->locked = true;
b3WorldTransform bodyTransform = b3GetBodyTransformQuick( world, body );
b3Shape* shape =
b3CreateShapeInternal( world, body, bodyTransform, def, geometry, shapeType, transform, scale, haveTransform );
if ( shape == NULL )
{
world->locked = false;
return b3_nullShapeId;
}
if ( def->updateBodyMass == true )
{
b3UpdateBodyMassData( world, body );
}
b3ValidateSolverSets( world );
b3ShapeId id = { shape->id + 1, bodyId.world0, shape->generation };
world->locked = false;
return id;
}
b3ShapeId b3CreateSphereShape( b3BodyId bodyId, const b3ShapeDef* def, const b3Sphere* sphere )
{
b3ShapeId shapeId = b3CreateShape( bodyId, def, sphere, b3_sphereShape, b3Transform_identity, b3Vec3_one, false );
if ( shapeId.index1 != 0 )
{
b3World* world = b3GetUnlockedWorld( bodyId.world0 );
if ( world != NULL )
{
B3_REC_CREATE( world, CreateSphereShape, shapeId, bodyId, *def, *sphere );
}
}
return shapeId;
}
b3ShapeId b3CreateCapsuleShape( b3BodyId bodyId, const b3ShapeDef* def, const b3Capsule* capsule )
{
float lengthSqr = b3DistanceSquared( capsule->center1, capsule->center2 );
b3ShapeId shapeId;
if ( lengthSqr <= B3_LINEAR_SLOP * B3_LINEAR_SLOP )
{
b3Sphere sphere = { b3Lerp( capsule->center1, capsule->center2, 0.5f ), capsule->radius };
shapeId = b3CreateShape( bodyId, def, &sphere, b3_sphereShape, b3Transform_identity, b3Vec3_one, false );
}
else
{
shapeId = b3CreateShape( bodyId, def, capsule, b3_capsuleShape, b3Transform_identity, b3Vec3_one, false );
}
if ( shapeId.index1 != 0 )
{
b3World* world = b3GetUnlockedWorld( bodyId.world0 );
if ( world != NULL )
{
B3_REC_CREATE( world, CreateCapsuleShape, shapeId, bodyId, *def, *capsule );
}
}
return shapeId;
}
b3ShapeId b3CreateHullShape( b3BodyId bodyId, const b3ShapeDef* def, const b3HullData* hull )
{
B3_VALIDATE( b3IsValidHull( hull ) );
B3_VALIDATE( hull->hash != 0 );
b3ShapeId shapeId = b3CreateShape( bodyId, def, hull, b3_hullShape, b3Transform_identity, b3Vec3_one, false );
if ( shapeId.index1 != 0 )
{
b3World* world = b3GetUnlockedWorld( bodyId.world0 );
if ( world != NULL && world->recording != NULL )
{
uint32_t geometryId = b3RecInternHull( world->recording, hull );
b3RecArgs_CreateHullShape createArgs = { bodyId, *def, geometryId };
b3RecWriteRet_CreateHullShape( world->recording, &createArgs, shapeId );
}
}
return shapeId;
}
b3ShapeId b3CreateTransformedHullShape( b3BodyId bodyId, const b3ShapeDef* def, const b3HullData* hull, b3Transform transform,
b3Vec3 scale )
{
B3_VALIDATE( b3IsValidHull( hull ) );
b3ShapeId shapeId = b3CreateShape( bodyId, def, hull, b3_hullShape, transform, scale, true );
if ( shapeId.index1 != 0 )
{
b3World* world = b3GetUnlockedWorld( bodyId.world0 );
if ( world != NULL && world->recording != NULL )
{
b3Shape* shape = b3Array_Get( world->shapes, shapeId.index1 - 1 );
uint32_t geometryId = b3RecInternHull( world->recording, shape->hull );
b3RecArgs_CreateHullShape createArgs = { bodyId, *def, geometryId };
b3RecWriteRet_CreateHullShape( world->recording, &createArgs, shapeId );
}
}
return shapeId;
}
b3ShapeId b3CreateMeshShape( b3BodyId bodyId, const b3ShapeDef* def, const b3MeshData* mesh, b3Vec3 scale )
{
B3_VALIDATE( b3IsValidMesh( mesh ) );
B3_VALIDATE( mesh->hash != 0 );
b3ShapeId shapeId = b3CreateShape( bodyId, def, mesh, b3_meshShape, b3Transform_identity, scale, true );
if ( shapeId.index1 != 0 )
{
b3World* world = b3GetUnlockedWorld( bodyId.world0 );
if ( world != NULL && world->recording != NULL )
{
uint32_t geometryId = b3RecInternMesh( world->recording, mesh );
b3RecArgs_CreateMeshShape createArgs = { bodyId, *def, geometryId, scale };
b3RecWriteRet_CreateMeshShape( world->recording, &createArgs, shapeId );
}
}
return shapeId;
}
b3ShapeId b3CreateHeightFieldShape( b3BodyId bodyId, const b3ShapeDef* def, const b3HeightFieldData* heightField )
{
B3_VALIDATE( heightField->hash != 0 );
b3ShapeId shapeId = b3CreateShape( bodyId, def, heightField, b3_heightShape, b3Transform_identity, b3Vec3_one, false );
if ( shapeId.index1 != 0 )
{
b3World* world = b3GetUnlockedWorld( bodyId.world0 );
if ( world != NULL && world->recording != NULL )
{
uint32_t geometryId = b3RecInternHeightField( world->recording, heightField );
b3RecArgs_CreateHeightFieldShape createArgs = { bodyId, *def, geometryId };
b3RecWriteRet_CreateHeightFieldShape( world->recording, &createArgs, shapeId );
}
}
return shapeId;
}
b3ShapeId b3CreateCompoundShape( b3BodyId bodyId, b3ShapeDef* def, const b3CompoundData* compound )
{
b3ShapeId shapeId = b3CreateShape( bodyId, def, compound, b3_compoundShape, b3Transform_identity, b3Vec3_one, false );
if ( shapeId.index1 != 0 )
{
b3World* world = b3GetUnlockedWorld( bodyId.world0 );
if ( world != NULL && world->recording != NULL )
{
uint32_t geometryId = b3RecInternCompound( world->recording, compound );
b3RecArgs_CreateCompoundShape createArgs = { bodyId, *def, geometryId };
b3RecWriteRet_CreateCompoundShape( world->recording, &createArgs, shapeId );
}
}
return shapeId;
}
static void b3DestroyShapeInternal( b3World* world, b3Shape* shape, b3Body* body, bool wakeBodies )
{
int shapeId = shape->id;
if ( shape->prevShapeId != B3_NULL_INDEX )
{
b3Shape* prevShape = b3Array_Get( world->shapes, shape->prevShapeId );
prevShape->nextShapeId = shape->nextShapeId;
}
if ( shape->nextShapeId != B3_NULL_INDEX )
{
b3Shape* nextShape = b3Array_Get( world->shapes, shape->nextShapeId );
nextShape->prevShapeId = shape->prevShapeId;
}
if ( shapeId == body->headShapeId )
{
body->headShapeId = shape->nextShapeId;
}
body->shapeCount -= 1;
b3DestroyShapeProxy( shape, &world->broadPhase );
int contactKey = body->headContactKey;
while ( contactKey != B3_NULL_INDEX )
{
int contactId = contactKey >> 1;
int edgeIndex = contactKey & 1;
b3Contact* contact = b3Array_Get( world->contacts, contactId );
contactKey = contact->edges[edgeIndex].nextKey;
if ( contact->shapeIdA == shapeId || contact->shapeIdB == shapeId )
{
b3DestroyContact( world, contact, wakeBodies );
}
}
if ( shape->sensorIndex != B3_NULL_INDEX )
{
b3Sensor* sensor = b3Array_Get( world->sensors, shape->sensorIndex );
for ( int i = 0; i < sensor->overlaps2.count; ++i )
{
b3Visitor* ref = sensor->overlaps2.data + i;
b3SensorEndTouchEvent event = {
.sensorShapeId =
{
.index1 = shapeId + 1,
.world0 = world->worldId,
.generation = shape->generation,
},
.visitorShapeId =
{
.index1 = ref->shapeId + 1,
.world0 = world->worldId,
.generation = ref->generation,
},
};
b3Array_Push( world->sensorEndEvents[world->endEventArrayIndex], event );
}
b3Array_Destroy( sensor->hits );
b3Array_Destroy( sensor->overlaps1 );
b3Array_Destroy( sensor->overlaps2 );
int movedIndex = b3Array_RemoveSwap( world->sensors, shape->sensorIndex );
if ( movedIndex != B3_NULL_INDEX )
{
b3Sensor* movedSensor = b3Array_Get( world->sensors, shape->sensorIndex );
b3Shape* otherSensorShape = b3Array_Get( world->shapes, movedSensor->shapeId );
otherSensorShape->sensorIndex = shape->sensorIndex;
}
}
b3DestroyShapeAllocations( world, shape );
b3FreeId( &world->shapeIdPool, shapeId );
shape->id = B3_NULL_INDEX;
b3ValidateSolverSets( world );
}
void b3DestroyShape( b3ShapeId shapeId, bool updateBodyMass )
{
b3World* world = b3GetUnlockedWorld( shapeId.world0 );
if ( world == NULL )
{
return;
}
B3_REC( world, DestroyShape, shapeId, updateBodyMass );
world->locked = true;
b3Shape* shape = b3GetShape( world, shapeId );
bool wakeBodies = true;
b3Body* body = b3Array_Get( world->bodies, shape->bodyId );
b3DestroyShapeInternal( world, shape, body, wakeBodies );
if ( updateBodyMass == true )
{
b3UpdateBodyMassData( world, body );
}
world->locked = false;
}
b3AABB b3ComputeShapeAABB( const b3Shape* shape, b3Transform transform )
{
switch ( shape->type )
{
case b3_capsuleShape:
return b3ComputeCapsuleAABB( &shape->capsule, transform );
case b3_compoundShape:
return b3ComputeCompoundAABB( shape->compound, transform );
case b3_heightShape:
return b3ComputeHeightFieldAABB( shape->heightField, transform );
case b3_hullShape:
return b3ComputeHullAABB( shape->hull, transform );
case b3_meshShape:
return b3ComputeMeshAABB( shape->mesh.data, transform, shape->mesh.scale );
case b3_sphereShape:
return b3ComputeSphereAABB( &shape->sphere, transform );
default:
{
B3_ASSERT( false );
b3AABB empty = { transform.p, transform.p };
return empty;
}
}
}
b3AABB b3ComputeFatShapeAABB( const b3Shape* shape, b3WorldTransform transform, float extra )
{
b3Vec3 r = { extra, extra, extra };
#if defined( BOX3D_DOUBLE_PRECISION )
b3Transform rotation = { b3Vec3_zero, transform.q };
b3AABB localBox = b3ComputeShapeAABB( shape, rotation );
localBox.lowerBound = b3Sub( localBox.lowerBound, r );
localBox.upperBound = b3Add( localBox.upperBound, r );
return b3OffsetAABB( localBox, transform.p );
#else
b3AABB aabb = b3ComputeShapeAABB( shape, transform );
aabb.lowerBound = b3Sub( aabb.lowerBound, r );
aabb.upperBound = b3Add( aabb.upperBound, r );
return aabb;
#endif
}
b3AABB b3ComputeSweptShapeAABB( const b3Shape* shape, const b3Sweep* sweep, float time )
{
B3_ASSERT( 0.0f <= time && time <= 1.0f );
b3Transform xf1 = { b3Sub( sweep->c1, b3RotateVector( sweep->q1, sweep->localCenter ) ), sweep->q1 };
b3Transform xf2 = b3GetSweepTransform( sweep, time );
switch ( shape->type )
{
case b3_capsuleShape:
return b3ComputeSweptCapsuleAABB( &shape->capsule, xf1, xf2 );
case b3_hullShape:
return b3ComputeSweptHullAABB( shape->hull, xf1, xf2 );
case b3_sphereShape:
return b3ComputeSweptSphereAABB( &shape->sphere, xf1, xf2 );
default:
B3_ASSERT( false );
return (b3AABB){ xf1.p, xf1.p };
}
}
b3Vec3 b3GetShapeCentroid( const b3Shape* shape )
{
switch ( shape->type )
{
case b3_capsuleShape:
return b3Lerp( shape->capsule.center1, shape->capsule.center2, 0.5f );
case b3_compoundShape:
{
b3AABB aabb = b3ComputeCompoundAABB( shape->compound, b3Transform_identity );
return b3AABB_Center( aabb );
}
case b3_sphereShape:
return shape->sphere.center;
case b3_hullShape:
return shape->hull->center;
case b3_meshShape:
{
b3AABB aabb = b3ComputeMeshAABB( shape->mesh.data, b3Transform_identity, shape->mesh.scale );
return b3AABB_Center( aabb );
}
case b3_heightShape:
{
b3AABB aabb = b3ComputeHeightFieldAABB( shape->heightField, b3Transform_identity );
return b3AABB_Center( aabb );
}
default:
return b3Vec3_zero;
}
}
float b3GetShapeArea( const b3Shape* shape )
{
switch ( shape->type )
{
case b3_capsuleShape:
return 2.0f * b3Length( b3Sub( shape->capsule.center1, shape->capsule.center2 ) ) +
2.0f * B3_PI * shape->capsule.radius;
case b3_hullShape:
return shape->hull->surfaceArea;
case b3_sphereShape:
return 2.0f * B3_PI * shape->sphere.radius;
default:
return 0.0f;
}
}
float b3GetShapeProjectedArea( const b3Shape* shape, b3Vec3 planeNormal )
{
switch ( shape->type )
{
case b3_capsuleShape:
{
float radius = shape->capsule.radius;
b3Vec3 axis = b3Sub( shape->capsule.center2, shape->capsule.center1 );
float projectedLength = b3Length( b3Cross( axis, planeNormal ) );
float cylinderArea = 2.0f * radius * projectedLength;
float sphereArea = B3_PI * radius * radius;
return sphereArea + cylinderArea;
}
case b3_hullShape:
return b3ComputeHullProjectedArea( shape->hull, planeNormal );
case b3_sphereShape:
return B3_PI * shape->sphere.radius * shape->sphere.radius;
default:
return 0.0f;
}
}
b3MassData b3ComputeShapeMass( const b3Shape* shape )
{
switch ( shape->type )
{
case b3_capsuleShape:
return b3ComputeCapsuleMass( &shape->capsule, shape->density );
case b3_hullShape:
return b3ComputeHullMass( shape->hull, shape->density );
case b3_sphereShape:
return b3ComputeSphereMass( &shape->sphere, shape->density );
default:
return (b3MassData){ 0 };
}
}
b3ShapeExtent b3ComputeShapeExtent( const b3Shape* shape, b3Vec3 localCenter )
{
b3ShapeExtent extent = { 0 };
switch ( shape->type )
{
case b3_capsuleShape:
{
float radius = shape->capsule.radius;
extent.minExtent = radius;
b3Vec3 c1 = b3Sub( shape->capsule.center1, localCenter );
b3Vec3 c2 = b3Sub( shape->capsule.center2, localCenter );
b3Vec3 r = { radius, radius, radius };
extent.maxExtent = b3Add( b3Max( c1, c2 ), r );
}
break;
case b3_compoundShape:
{
b3AABB aabb = b3ComputeCompoundAABB( shape->compound, b3Transform_identity );
float r1 = b3Length( b3Sub( aabb.lowerBound, localCenter ) );
float r2 = b3Length( b3Sub( aabb.upperBound, localCenter ) );
extent.minExtent = b3MinFloat( r1, r2 );
b3Vec3 p = b3FarthestPointOnAABB( aabb, localCenter );
extent.maxExtent = b3Abs( b3Sub( p, localCenter ) );
}
break;
case b3_sphereShape:
{
float radius = shape->sphere.radius;
extent.minExtent = radius;
b3Vec3 r = { radius, radius, radius };
b3Vec3 p = b3Add( b3Sub( shape->sphere.center, localCenter ), r );
extent.maxExtent = b3Abs( b3Sub( p, localCenter ) );
}
break;
case b3_hullShape:
extent = b3ComputeHullExtent( shape->hull, localCenter );
break;
case b3_meshShape:
{
b3AABB aabb = b3ComputeMeshAABB( shape->mesh.data, b3Transform_identity, shape->mesh.scale );
float r1 = b3Length( b3Sub( aabb.lowerBound, localCenter ) );
float r2 = b3Length( b3Sub( aabb.upperBound, localCenter ) );
extent.minExtent = b3MinFloat( r1, r2 );
b3Vec3 p = b3FarthestPointOnAABB( aabb, localCenter );
extent.maxExtent = b3Abs( p );
}
break;
default:
break;
}
return extent;
}
b3CastOutput b3RayCastShape( const b3Shape* shape, b3Transform transform, const b3RayCastInput* input )
{
b3RayCastInput localInput = *input;
localInput.origin = b3InvTransformPoint( transform, input->origin );
localInput.translation = b3InvRotateVector( transform.q, input->translation );
b3CastOutput output = { 0 };
switch ( shape->type )
{
case b3_capsuleShape:
output = b3RayCastCapsule( &shape->capsule, &localInput );
break;
case b3_compoundShape:
output = b3RayCastCompound( shape->compound, &localInput );
break;
case b3_sphereShape:
output = b3RayCastSphere( &shape->sphere, &localInput );
break;
case b3_hullShape:
output = b3RayCastHull( shape->hull, &localInput );
break;
case b3_meshShape:
output = b3RayCastMesh( &shape->mesh, &localInput );
break;
case b3_heightShape:
output = b3RayCastHeightField( shape->heightField, &localInput );
break;
default:
return output;
}
output.point = b3TransformPoint( transform, output.point );
output.normal = b3RotateVector( transform.q, output.normal );
return output;
}
b3CastOutput b3ShapeCastShape( const b3Shape* shape, b3Transform transform, const b3ShapeCastInput* input )
{
b3ShapeCastInput localInput = *input;
b3Vec3 localPoints[B3_MAX_SHAPE_CAST_POINTS];
localInput.proxy.count = b3MinInt( input->proxy.count, B3_MAX_SHAPE_CAST_POINTS );
for ( int i = 0; i < localInput.proxy.count; ++i )
{
localPoints[i] = b3InvTransformPoint( transform, input->proxy.points[i] );
}
localInput.proxy.points = localPoints;
localInput.translation = b3InvRotateVector( transform.q, input->translation );
b3CastOutput output = { 0 };
switch ( shape->type )
{
case b3_capsuleShape:
output = b3ShapeCastCapsule( &shape->capsule, &localInput );
break;
case b3_compoundShape:
output = b3ShapeCastCompound( shape->compound, &localInput );
break;
case b3_heightShape:
output = b3ShapeCastHeightField( shape->heightField, &localInput );
break;
case b3_hullShape:
output = b3ShapeCastHull( shape->hull, &localInput );
break;
case b3_meshShape:
output = b3ShapeCastMesh( &shape->mesh, &localInput );
break;
case b3_sphereShape:
output = b3ShapeCastSphere( &shape->sphere, &localInput );
break;
default:
return output;
}
output.point = b3TransformPoint( transform, output.point );
output.normal = b3RotateVector( transform.q, output.normal );
return output;
}
bool b3OverlapShape( const b3Shape* shape, b3Transform transform, const b3ShapeProxy* proxy )
{
b3ShapeType type = shape->type;
switch ( type )
{
case b3_capsuleShape:
return b3OverlapCapsule( &shape->capsule, transform, proxy );
case b3_compoundShape:
return b3OverlapCompound( shape->compound, transform, proxy );
case b3_heightShape:
return b3OverlapHeightField( shape->heightField, transform, proxy );
case b3_hullShape:
return b3OverlapHull( shape->hull, transform, proxy );
case b3_meshShape:
return b3OverlapMesh( &shape->mesh, transform, proxy );
case b3_sphereShape:
return b3OverlapSphere( &shape->sphere, transform, proxy );
default:
B3_ASSERT( false );
return false;
}
#if 0#endif
}
int b3CollideMover( b3PlaneResult* planes, int planeCapacity, const b3Shape* shape, b3Transform transform,
const b3Capsule* mover )
{
if ( planeCapacity == 0 )
{
return 0;
}
b3Capsule localMover;
localMover.center1 = b3InvTransformPoint( transform, mover->center1 );
localMover.center2 = b3InvTransformPoint( transform, mover->center2 );
localMover.radius = mover->radius;
int planeCount = 0;
switch ( shape->type )
{
case b3_capsuleShape:
planeCount = b3CollideMoverAndCapsule( planes, &shape->capsule, &localMover );
break;
case b3_compoundShape:
planeCount = b3CollideMoverAndCompound( planes, planeCapacity, shape->compound, &localMover );
break;
case b3_sphereShape:
planeCount = b3CollideMoverAndSphere( planes, &shape->sphere, &localMover );
break;
case b3_hullShape:
planeCount = b3CollideMoverAndHull( planes, shape->hull, &localMover );
break;
case b3_meshShape:
planeCount = b3CollideMoverAndMesh( planes, planeCapacity, &shape->mesh, &localMover );
break;
case b3_heightShape:
planeCount = b3CollideMoverAndHeightField( planes, planeCapacity, shape->heightField, &localMover );
break;
default:
B3_ASSERT( false );
break;
}
for ( int i = 0; i < planeCount; ++i )
{
planes[i].plane.normal = b3RotateVector( transform.q, planes[i].plane.normal );
planes[i].point = b3TransformPoint( transform, planes[i].point );
}
return planeCount;
}
void b3CreateShapeProxy( b3Shape* shape, b3BroadPhase* bp, b3BodyType type, b3WorldTransform transform, bool forcePairCreation )
{
B3_ASSERT( shape->proxyKey == B3_NULL_INDEX );
b3UpdateShapeAABBs( shape, transform, type );
shape->proxyKey =
b3BroadPhase_CreateProxy( bp, type, shape->fatAABB, shape->filter.categoryBits, shape->id, forcePairCreation );
B3_ASSERT( B3_PROXY_TYPE( shape->proxyKey ) < b3_bodyTypeCount );
}
void b3DestroyShapeProxy( b3Shape* shape, b3BroadPhase* bp )
{
if ( shape->proxyKey != B3_NULL_INDEX )
{
b3BroadPhase_DestroyProxy( bp, shape->proxyKey );
shape->proxyKey = B3_NULL_INDEX;
}
}
static void b3DestroyShapeAllocationForShapeChange( b3World* world, b3Shape* shape )
{
b3ShapeType type = shape->type;
switch ( type )
{
case b3_hullShape:
b3RemoveHullFromDatabase( world, shape->hull );
shape->hull = NULL;
break;
default:
break;
}
if ( shape->userShape != NULL )
{
world->destroyDebugShape( shape->userShape, world->userDebugShapeContext );
shape->userShape = NULL;
}
}
void b3DestroyShapeAllocations( b3World* world, b3Shape* shape )
{
b3DestroyShapeAllocationForShapeChange( world, shape );
if ( shape->materials != NULL )
{
B3_ASSERT( shape->materialCount > 0 );
b3Free( shape->materials, shape->materialCount * sizeof( b3SurfaceMaterial ) );
shape->materials = NULL;
shape->materialCount = 0;
}
}
b3ShapeProxy b3MakeShapeProxy( const b3Shape* shape )
{
switch ( shape->type )
{
case b3_capsuleShape:
return (b3ShapeProxy){ &shape->capsule.center1, 2, shape->capsule.radius };
case b3_sphereShape:
return (b3ShapeProxy){ &shape->sphere.center, 1, shape->sphere.radius };
case b3_hullShape:
{
const b3HullData* hull = shape->hull;
const b3Vec3* points = b3GetHullPoints( hull );
return (b3ShapeProxy){ points, hull->vertexCount, 0.0f };
}
default:
{
B3_ASSERT( false );
return (b3ShapeProxy){ 0 };
}
}
}
b3ShapeProxy b3MakeLocalProxy( const b3ShapeProxy* proxy, b3Transform transform, b3Vec3* buffer )
{
b3Transform invTransform = b3InvertTransform( transform );
b3Matrix3 R = b3MakeMatrixFromQuat( invTransform.q );
int count = b3MinInt( proxy->count, B3_MAX_SHAPE_CAST_POINTS );
for ( int i = 0; i < count; ++i )
{
buffer[i] = b3Add( b3MulMV( R, proxy->points[i] ), invTransform.p );
}
return (b3ShapeProxy){
.points = buffer,
.count = count,
.radius = proxy->radius,
};
}
b3AABB b3ComputeProxyAABB( const b3ShapeProxy* proxy )
{
const b3Vec3* points = proxy->points;
b3AABB aabb = {
.lowerBound = points[0],
.upperBound = points[0],
};
for ( int i = 1; i < proxy->count; ++i )
{
aabb.lowerBound = b3Min( aabb.lowerBound, points[i] );
aabb.upperBound = b3Max( aabb.upperBound, points[i] );
}
b3Vec3 r = { proxy->radius, proxy->radius, proxy->radius };
aabb.lowerBound = b3Sub( aabb.lowerBound, r );
aabb.upperBound = b3Add( aabb.upperBound, r );
return aabb;
}
b3BodyId b3Shape_GetBody( b3ShapeId shapeId )
{
b3World* world = b3GetWorld( shapeId.world0 );
b3Shape* shape = b3GetShape( world, shapeId );
return b3MakeBodyId( world, shape->bodyId );
}
b3WorldId b3Shape_GetWorld( b3ShapeId shapeId )
{
b3World* world = b3GetWorld( shapeId.world0 );
return (b3WorldId){ (uint16_t)( shapeId.world0 + 1 ), world->generation };
}
void b3Shape_SetUserData( b3ShapeId shapeId, void* userData )
{
b3World* world = b3GetWorld( shapeId.world0 );
b3Shape* shape = b3GetShape( world, shapeId );
shape->userData = userData;
}
void* b3Shape_GetUserData( b3ShapeId shapeId )
{
b3World* world = b3GetWorld( shapeId.world0 );
b3Shape* shape = b3GetShape( world, shapeId );
return shape->userData;
}
bool b3Shape_IsSensor( b3ShapeId shapeId )
{
b3World* world = b3GetWorld( shapeId.world0 );
b3Shape* shape = b3GetShape( world, shapeId );
return shape->sensorIndex != B3_NULL_INDEX;
}
b3WorldCastOutput b3Shape_RayCast( b3ShapeId shapeId, b3Pos origin, b3Vec3 translation )
{
B3_ASSERT( b3IsValidPosition( origin ) );
B3_ASSERT( b3IsValidVec3( translation ) );
b3World* world = b3GetWorld( shapeId.world0 );
b3Shape* shape = b3GetShape( world, shapeId );
b3Transform transform = b3ToRelativeTransform( b3GetBodyTransform( world, shape->bodyId ), origin );
b3RayCastInput input = { b3Vec3_zero, translation, 1.0f };
b3CastOutput local = b3RayCastShape( shape, transform, &input );
b3WorldCastOutput output;
output.normal = local.normal;
output.point = b3OffsetPos( origin, local.point );
output.fraction = local.fraction;
output.iterations = local.iterations;
output.triangleIndex = local.triangleIndex;
output.childIndex = local.childIndex;
output.materialIndex = local.materialIndex;
output.hit = local.hit;
return output;
}
void b3Shape_SetDensity( b3ShapeId shapeId, float density, bool updateBodyMass )
{
B3_ASSERT( b3IsValidFloat( density ) && density >= 0.0f );
b3World* world = b3GetUnlockedWorld( shapeId.world0 );
if ( world == NULL )
{
return;
}
B3_REC( world, ShapeSetDensity, shapeId, density, updateBodyMass );
b3Shape* shape = b3GetShape( world, shapeId );
if ( density == shape->density )
{
return;
}
shape->density = density;
if ( updateBodyMass == true )
{
b3Body* body = b3Array_Get( world->bodies, shape->bodyId );
b3UpdateBodyMassData( world, body );
}
}
float b3Shape_GetDensity( b3ShapeId shapeId )
{
b3World* world = b3GetWorld( shapeId.world0 );
b3Shape* shape = b3GetShape( world, shapeId );
return shape->density;
}
void b3Shape_SetFriction( b3ShapeId shapeId, float friction )
{
B3_ASSERT( b3IsValidFloat( friction ) && friction >= 0.0f );
b3World* world = b3GetWorld( shapeId.world0 );
B3_REC( world, ShapeSetFriction, shapeId, friction );
b3Shape* shape = b3GetShape( world, shapeId );
B3_ASSERT( shape->type != b3_compoundShape );
b3GetShapeMaterials( shape )[0].friction = friction;
}
float b3Shape_GetFriction( b3ShapeId shapeId )
{
b3World* world = b3GetWorld( shapeId.world0 );
b3Shape* shape = b3GetShape( world, shapeId );
return b3GetShapeMaterials( shape )[0].friction;
}
void b3Shape_SetRestitution( b3ShapeId shapeId, float restitution )
{
B3_ASSERT( b3IsValidFloat( restitution ) && restitution >= 0.0f );
b3World* world = b3GetWorld( shapeId.world0 );
B3_REC( world, ShapeSetRestitution, shapeId, restitution );
b3Shape* shape = b3GetShape( world, shapeId );
B3_ASSERT( shape->type != b3_compoundShape );
b3GetShapeMaterials( shape )[0].restitution = restitution;
}
float b3Shape_GetRestitution( b3ShapeId shapeId )
{
b3World* world = b3GetWorld( shapeId.world0 );
b3Shape* shape = b3GetShape( world, shapeId );
return b3GetShapeMaterials( shape )[0].restitution;
}
void b3Shape_SetSurfaceMaterial( b3ShapeId shapeId, b3SurfaceMaterial surfaceMaterial )
{
B3_ASSERT( b3IsValidFloat( surfaceMaterial.friction ) && surfaceMaterial.friction >= 0.0f );
B3_ASSERT( b3IsValidFloat( surfaceMaterial.restitution ) && surfaceMaterial.restitution >= 0.0f );
B3_ASSERT( b3IsValidFloat( surfaceMaterial.rollingResistance ) && surfaceMaterial.rollingResistance >= 0.0f );
B3_ASSERT( b3IsValidVec3( surfaceMaterial.tangentVelocity ) );
b3World* world = b3GetWorld( shapeId.world0 );
B3_REC( world, ShapeSetSurfaceMaterial, shapeId, surfaceMaterial );
b3Shape* shape = b3GetShape( world, shapeId );
B3_ASSERT( shape->type != b3_compoundShape );
b3GetShapeMaterials( shape )[0] = surfaceMaterial;
}
b3SurfaceMaterial b3Shape_GetSurfaceMaterial( b3ShapeId shapeId )
{
b3World* world = b3GetWorld( shapeId.world0 );
b3Shape* shape = b3GetShape( world, shapeId );
return b3GetShapeMaterials( shape )[0];
}
int b3Shape_GetMeshMaterialCount( b3ShapeId shapeId )
{
b3World* world = b3GetWorld( shapeId.world0 );
b3Shape* shape = b3GetShape( world, shapeId );
return shape->materialCount;
}
void b3Shape_SetMeshMaterial( b3ShapeId shapeId, b3SurfaceMaterial surfaceMaterial, int index )
{
B3_ASSERT( b3IsValidFloat( surfaceMaterial.friction ) && surfaceMaterial.friction >= 0.0f );
B3_ASSERT( b3IsValidFloat( surfaceMaterial.restitution ) && surfaceMaterial.restitution >= 0.0f );
B3_ASSERT( b3IsValidFloat( surfaceMaterial.rollingResistance ) && surfaceMaterial.rollingResistance >= 0.0f );
B3_ASSERT( b3IsValidVec3( surfaceMaterial.tangentVelocity ) );
b3World* world = b3GetWorld( shapeId.world0 );
b3Shape* shape = b3GetShape( world, shapeId );
B3_ASSERT( 0 <= index && index < shape->materialCount );
B3_ASSERT( shape->type != b3_compoundShape );
b3GetShapeMaterials( shape )[index] = surfaceMaterial;
}
b3SurfaceMaterial b3Shape_GetMeshSurfaceMaterial( b3ShapeId shapeId, int index )
{
b3World* world = b3GetWorld( shapeId.world0 );
b3Shape* shape = b3GetShape( world, shapeId );
B3_ASSERT( 0 <= index && index < shape->materialCount );
return b3GetShapeMaterials( shape )[index];
}
b3Filter b3Shape_GetFilter( b3ShapeId shapeId )
{
b3World* world = b3GetWorld( shapeId.world0 );
b3Shape* shape = b3GetShape( world, shapeId );
return shape->filter;
}
static void b3ResetProxy( b3World* world, b3Shape* shape, bool wakeBodies, bool destroyProxy )
{
b3Body* body = b3Array_Get( world->bodies, shape->bodyId );
int shapeId = shape->id;
int contactKey = body->headContactKey;
while ( contactKey != B3_NULL_INDEX )
{
int contactId = contactKey >> 1;
int edgeIndex = contactKey & 1;
b3Contact* contact = b3Array_Get( world->contacts, contactId );
contactKey = contact->edges[edgeIndex].nextKey;
if ( contact->shapeIdA == shapeId || contact->shapeIdB == shapeId )
{
b3DestroyContact( world, contact, wakeBodies );
}
}
b3WorldTransform transform = b3GetBodyTransformQuick( world, body );
if ( shape->proxyKey != B3_NULL_INDEX )
{
b3BodyType proxyType = B3_PROXY_TYPE( shape->proxyKey );
b3UpdateShapeAABBs( shape, transform, proxyType );
if ( destroyProxy )
{
b3BroadPhase_DestroyProxy( &world->broadPhase, shape->proxyKey );
bool forcePairCreation = true;
shape->proxyKey = b3BroadPhase_CreateProxy( &world->broadPhase, proxyType, shape->fatAABB, shape->filter.categoryBits,
shapeId, forcePairCreation );
}
else
{
b3BroadPhase_MoveProxy( &world->broadPhase, shape->proxyKey, shape->fatAABB );
}
}
else
{
b3BodyType proxyType = body->type;
b3UpdateShapeAABBs( shape, transform, proxyType );
}
b3ValidateSolverSets( world );
}
void b3Shape_SetFilter( b3ShapeId shapeId, b3Filter filter, bool invokeContacts )
{
b3World* world = b3GetUnlockedWorld( shapeId.world0 );
if ( world == NULL )
{
return;
}
B3_REC( world, ShapeSetFilter, shapeId, filter, invokeContacts );
b3Shape* shape = b3GetShape( world, shapeId );
if ( filter.maskBits == shape->filter.maskBits && filter.categoryBits == shape->filter.categoryBits &&
filter.groupIndex == shape->filter.groupIndex )
{
return;
}
shape->filter = filter;
if ( invokeContacts )
{
world->locked = true;
bool wakeBodies = true;
bool destroyProxy = filter.categoryBits == shape->filter.categoryBits;
b3ResetProxy( world, shape, wakeBodies, destroyProxy );
world->locked = false;
}
}
void b3Shape_EnableSensorEvents( b3ShapeId shapeId, bool flag )
{
b3World* world = b3GetUnlockedWorld( shapeId.world0 );
if ( world == NULL )
{
return;
}
B3_REC( world, ShapeEnableSensorEvents, shapeId, flag );
b3Shape* shape = b3GetShape( world, shapeId );
shape->enableSensorEvents = flag;
}
bool b3Shape_AreSensorEventsEnabled( b3ShapeId shapeId )
{
b3World* world = b3GetWorld( shapeId.world0 );
b3Shape* shape = b3GetShape( world, shapeId );
return shape->enableSensorEvents;
}
void b3Shape_EnableContactEvents( b3ShapeId shapeId, bool flag )
{
b3World* world = b3GetUnlockedWorld( shapeId.world0 );
if ( world == NULL )
{
return;
}
B3_REC( world, ShapeEnableContactEvents, shapeId, flag );
b3Shape* shape = b3GetShape( world, shapeId );
shape->enableContactEvents = flag;
}
bool b3Shape_AreContactEventsEnabled( b3ShapeId shapeId )
{
b3World* world = b3GetWorld( shapeId.world0 );
b3Shape* shape = b3GetShape( world, shapeId );
return shape->enableContactEvents;
}
void b3Shape_EnablePreSolveEvents( b3ShapeId shapeId, bool flag )
{
b3World* world = b3GetUnlockedWorld( shapeId.world0 );
if ( world == NULL )
{
return;
}
B3_REC( world, ShapeEnablePreSolveEvents, shapeId, flag );
b3Shape* shape = b3GetShape( world, shapeId );
shape->enablePreSolveEvents = flag;
}
bool b3Shape_ArePreSolveEventsEnabled( b3ShapeId shapeId )
{
b3World* world = b3GetWorld( shapeId.world0 );
b3Shape* shape = b3GetShape( world, shapeId );
return shape->enablePreSolveEvents;
}
void b3Shape_EnableHitEvents( b3ShapeId shapeId, bool flag )
{
b3World* world = b3GetUnlockedWorld( shapeId.world0 );
if ( world == NULL )
{
return;
}
B3_REC( world, ShapeEnableHitEvents, shapeId, flag );
b3Shape* shape = b3GetShape( world, shapeId );
shape->enableHitEvents = flag;
}
bool b3Shape_AreHitEventsEnabled( b3ShapeId shapeId )
{
b3World* world = b3GetWorld( shapeId.world0 );
b3Shape* shape = b3GetShape( world, shapeId );
return shape->enableHitEvents;
}
b3ShapeType b3Shape_GetType( b3ShapeId shapeId )
{
b3World* world = b3GetWorld( shapeId.world0 );
b3Shape* shape = b3GetShape( world, shapeId );
return shape->type;
}
b3Sphere b3Shape_GetSphere( b3ShapeId shapeId )
{
b3World* world = b3GetWorld( shapeId.world0 );
b3Shape* shape = b3GetShape( world, shapeId );
B3_ASSERT( shape->type == b3_sphereShape );
return shape->sphere;
}
b3Capsule b3Shape_GetCapsule( b3ShapeId shapeId )
{
b3World* world = b3GetWorld( shapeId.world0 );
b3Shape* shape = b3GetShape( world, shapeId );
B3_ASSERT( shape->type == b3_capsuleShape );
return shape->capsule;
}
const b3HullData* b3Shape_GetHull( b3ShapeId shapeId )
{
b3World* world = b3GetWorld( shapeId.world0 );
b3Shape* shape = b3GetShape( world, shapeId );
B3_ASSERT( shape->type == b3_hullShape );
return shape->hull;
}
b3Mesh b3Shape_GetMesh( b3ShapeId shapeId )
{
b3World* world = b3GetWorld( shapeId.world0 );
b3Shape* shape = b3GetShape( world, shapeId );
B3_ASSERT( shape->type == b3_meshShape );
return shape->mesh;
}
const b3HeightFieldData* b3Shape_GetHeightField( b3ShapeId shapeId )
{
b3World* world = b3GetWorld( shapeId.world0 );
b3Shape* shape = b3GetShape( world, shapeId );
B3_ASSERT( shape->type == b3_heightShape );
return shape->heightField;
}
void b3Shape_SetSphere( b3ShapeId shapeId, const b3Sphere* sphere )
{
b3World* world = b3GetUnlockedWorld( shapeId.world0 );
if ( world == NULL )
{
return;
}
B3_REC( world, ShapeSetSphere, shapeId, *sphere );
world->locked = true;
b3Shape* shape = b3GetShape( world, shapeId );
b3DestroyShapeAllocationForShapeChange( world, shape );
shape->sphere = *sphere;
shape->type = b3_sphereShape;
shape->aabbMargin = b3ComputeShapeMargin( shape );
bool wakeBodies = true;
bool destroyProxy = true;
b3ResetProxy( world, shape, wakeBodies, destroyProxy );
world->locked = false;
}
void b3Shape_SetCapsule( b3ShapeId shapeId, const b3Capsule* capsule )
{
b3World* world = b3GetUnlockedWorld( shapeId.world0 );
if ( world == NULL )
{
return;
}
B3_REC( world, ShapeSetCapsule, shapeId, *capsule );
world->locked = true;
b3Shape* shape = b3GetShape( world, shapeId );
b3DestroyShapeAllocationForShapeChange( world, shape );
shape->capsule = *capsule;
shape->type = b3_capsuleShape;
shape->aabbMargin = b3ComputeShapeMargin( shape );
bool wakeBodies = true;
bool destroyProxy = true;
b3ResetProxy( world, shape, wakeBodies, destroyProxy );
world->locked = false;
}
void b3Shape_SetHull( b3ShapeId shapeId, const b3HullData* hull )
{
B3_VALIDATE( b3IsValidHull( hull ) );
B3_VALIDATE( hull->hash != 0 );
b3World* world = b3GetUnlockedWorld( shapeId.world0 );
if ( world == NULL )
{
return;
}
world->locked = true;
b3Shape* shape = b3GetShape( world, shapeId );
const b3HullData* data = b3AddHullToDatabase( world, hull );
if ( shape->type == b3_hullShape && data == shape->hull )
{
b3RemoveHullFromDatabase( world, data );
world->locked = false;
return;
}
b3DestroyShapeAllocationForShapeChange( world, shape );
shape->hull = data;
shape->type = b3_hullShape;
shape->aabbMargin = b3ComputeShapeMargin( shape );
bool wakeBodies = true;
bool destroyProxy = true;
b3ResetProxy( world, shape, wakeBodies, destroyProxy );
world->locked = false;
}
void b3Shape_SetMesh( b3ShapeId shapeId, const b3MeshData* meshData, b3Vec3 scale )
{
B3_ASSERT( b3IsValidVec3( scale ) );
B3_ASSERT( meshData != NULL && b3IsValidMesh( meshData ) );
b3World* world = b3GetUnlockedWorld( shapeId.world0 );
if ( world == NULL )
{
return;
}
world->locked = true;
b3Shape* shape = b3GetShape( world, shapeId );
b3DestroyShapeAllocationForShapeChange( world, shape );
shape->mesh.data = meshData;
shape->mesh.scale = b3SafeScale( scale );
shape->type = b3_meshShape;
shape->aabbMargin = b3ComputeShapeMargin( shape );
bool wakeBodies = true;
bool destroyProxy = true;
b3ResetProxy( world, shape, wakeBodies, destroyProxy );
world->locked = false;
}
int b3Shape_GetContactCapacity( b3ShapeId shapeId )
{
b3World* world = b3GetUnlockedWorld( shapeId.world0 );
if ( world == NULL )
{
return 0;
}
b3Shape* shape = b3GetShape( world, shapeId );
if ( shape->sensorIndex != B3_NULL_INDEX )
{
return 0;
}
b3Body* body = b3Array_Get( world->bodies, shape->bodyId );
return body->contactCount;
}
int b3Shape_GetContactData( b3ShapeId shapeId, b3ContactData* contactData, int capacity )
{
b3World* world = b3GetUnlockedWorld( shapeId.world0 );
if ( world == NULL )
{
return 0;
}
b3Shape* shape = b3GetShape( world, shapeId );
if ( shape->sensorIndex != B3_NULL_INDEX )
{
return 0;
}
b3Body* body = b3Array_Get( world->bodies, shape->bodyId );
int contactKey = body->headContactKey;
int index = 0;
while ( contactKey != B3_NULL_INDEX && index < capacity )
{
int contactId = contactKey >> 1;
int edgeIndex = contactKey & 1;
b3Contact* contact = b3Array_Get( world->contacts, contactId );
if ( ( contact->shapeIdA == shapeId.index1 - 1 || contact->shapeIdB == shapeId.index1 - 1 ) &&
( contact->flags & b3_contactTouchingFlag ) != 0 )
{
b3Shape* shapeA = world->shapes.data + contact->shapeIdA;
b3Shape* shapeB = world->shapes.data + contact->shapeIdB;
contactData[index].contactId = (b3ContactId){ contact->contactId + 1, shapeId.world0, 0, contact->generation };
contactData[index].shapeIdA = (b3ShapeId){ shapeA->id + 1, shapeId.world0, shapeA->generation };
contactData[index].shapeIdB = (b3ShapeId){ shapeB->id + 1, shapeId.world0, shapeB->generation };
contactData[index].manifolds = contact->manifolds;
contactData[index].manifoldCount = contact->manifoldCount;
index += 1;
}
contactKey = contact->edges[edgeIndex].nextKey;
}
B3_ASSERT( index <= capacity );
return index;
}
int b3Shape_GetSensorCapacity( b3ShapeId shapeId )
{
b3World* world = b3GetUnlockedWorld( shapeId.world0 );
if ( world == NULL )
{
return 0;
}
b3Shape* shape = b3GetShape( world, shapeId );
if ( shape->sensorIndex == B3_NULL_INDEX )
{
return 0;
}
b3Sensor* sensor = b3Array_Get( world->sensors, shape->sensorIndex );
return sensor->overlaps2.count;
}
int b3Shape_GetSensorData( b3ShapeId shapeId, b3ShapeId* visitorIds, int capacity )
{
b3World* world = b3GetUnlockedWorld( shapeId.world0 );
if ( world == NULL )
{
return 0;
}
b3Shape* shape = b3GetShape( world, shapeId );
if ( shape->sensorIndex == B3_NULL_INDEX )
{
return 0;
}
b3Sensor* sensor = b3Array_Get( world->sensors, shape->sensorIndex );
int count = b3MinInt( sensor->overlaps2.count, capacity );
b3Visitor* refs = sensor->overlaps2.data;
for ( int i = 0; i < count; ++i )
{
b3ShapeId visitorId = {
.index1 = refs[i].shapeId + 1,
.world0 = shapeId.world0,
.generation = refs[i].generation,
};
visitorIds[i] = visitorId;
}
return count;
}
b3AABB b3Shape_GetAABB( b3ShapeId shapeId )
{
b3World* world = b3GetWorld( shapeId.world0 );
if ( world == NULL )
{
return (b3AABB){ 0 };
}
b3Shape* shape = b3GetShape( world, shapeId );
return shape->aabb;
}
b3MassData b3Shape_ComputeMassData( b3ShapeId shapeId )
{
b3World* world = b3GetWorld( shapeId.world0 );
if ( world == NULL )
{
return (b3MassData){ 0 };
}
b3Shape* shape = b3GetShape( world, shapeId );
return b3ComputeShapeMass( shape );
}
b3Vec3 b3Shape_GetClosestPoint( b3ShapeId shapeId, b3Vec3 target )
{
b3World* world = b3GetWorld( shapeId.world0 );
if ( world == NULL )
{
return b3Vec3_zero;
}
b3Shape* shape = b3GetShape( world, shapeId );
b3Body* body = b3Array_Get( world->bodies, shape->bodyId );
b3Transform transform = b3ToRelativeTransform( b3GetBodyTransformQuick( world, body ), b3Pos_zero );
b3DistanceInput input;
input.proxyA = b3MakeShapeProxy( shape );
input.proxyB = (b3ShapeProxy){ &target, 1, 0.0f };
input.transform = b3InvMulTransforms( transform, b3Transform_identity );
input.useRadii = true;
b3SimplexCache cache = { 0 };
b3DistanceOutput output = b3ShapeDistance( &input, &cache, NULL, 0 );
return b3TransformPoint( transform, output.pointA );
}
#define B3_DEBUG_WIND 0
void b3Shape_ApplyWind( b3ShapeId shapeId, b3Vec3 wind, float drag, float lift, float maxSpeed, bool wake )
{
b3World* world = b3GetWorld( shapeId.world0 );
if ( world == NULL )
{
return;
}
B3_REC( world, ShapeApplyWind, shapeId, wind, drag, lift, maxSpeed, wake );
b3Shape* shape = b3GetShape( world, shapeId );
b3ShapeType shapeType = shape->type;
if ( shapeType != b3_sphereShape && shapeType != b3_capsuleShape && shapeType != b3_hullShape )
{
return;
}
b3Body* body = b3Array_Get( world->bodies, shape->bodyId );
if ( body->type != b3_dynamicBody )
{
return;
}
if ( body->setIndex == b3_disabledSet )
{
return;
}
if ( body->setIndex >= b3_firstSleepingSet && wake == false )
{
return;
}
b3BodySim* sim = b3GetBodySim( world, body );
if ( body->setIndex != b3_awakeSet )
{
b3WakeBodyWithLock( world, body );
}
B3_ASSERT( body->setIndex == b3_awakeSet );
b3BodyState* state = b3GetBodyState( world, body );
b3Transform transform = b3ToRelativeTransform( sim->transform, b3Pos_zero );
float lengthUnits = b3GetLengthUnitsPerMeter();
float volumeUnits = lengthUnits * lengthUnits * lengthUnits;
float airDensity = 1.2250f / ( volumeUnits );
b3Vec3 force = { 0 };
b3Vec3 torque = { 0 };
switch ( shape->type )
{
case b3_sphereShape:
{
float radius = shape->sphere.radius;
b3Vec3 centroid = shape->localCentroid;
b3Vec3 lever = b3RotateVector( transform.q, b3Sub( centroid, sim->localCenter ) );
b3Vec3 shapeVelocity = b3Add( state->linearVelocity, b3Cross( state->angularVelocity, lever ) );
b3Vec3 relativeVelocity = b3MulSub( wind, drag, shapeVelocity );
float speed;
b3Vec3 direction = b3GetLengthAndNormalize( &speed, relativeVelocity );
speed = b3MinFloat( speed, maxSpeed );
float projectedArea = B3_PI * radius * radius;
force = b3MulSV( 0.5f * airDensity * projectedArea * speed * speed, direction );
torque = b3Cross( lever, force );
}
break;
case b3_capsuleShape:
{
b3Vec3 centroid = shape->localCentroid;
b3Vec3 lever = b3RotateVector( transform.q, b3Sub( centroid, sim->localCenter ) );
b3Vec3 shapeVelocity = b3Add( state->linearVelocity, b3Cross( state->angularVelocity, lever ) );
b3Vec3 relativeVelocity = b3MulSub( wind, drag, shapeVelocity );
float speed;
b3Vec3 direction = b3GetLengthAndNormalize( &speed, relativeVelocity );
speed = b3MinFloat( speed, maxSpeed );
b3Vec3 d = b3Sub( shape->capsule.center2, shape->capsule.center1 );
d = b3RotateVector( transform.q, d );
float radius = shape->capsule.radius;
float projectedArea = B3_PI * radius * radius + 2.0f * radius * b3Length( b3Cross( d, direction ) );
b3Vec3 e = b3Normalize( d );
b3Vec3 normal = b3Sub( b3MulSV( b3Dot( direction, e ), e ), direction );
b3Vec3 liftDirection = b3Cross( b3Cross( normal, direction ), direction );
float forceMagnitude = 0.5f * airDensity * projectedArea * speed * speed;
force = b3MulSV( forceMagnitude, b3MulAdd( direction, lift, liftDirection ) );
b3Vec3 edgeLever = b3MulAdd( lever, radius, normal );
torque = b3Cross( edgeLever, force );
}
break;
case b3_hullShape:
{
b3Matrix3 matrix = b3MakeMatrixFromQuat( transform.q );
int faceCount = shape->hull->faceCount;
const b3Vec3* points = b3GetHullPoints( shape->hull );
const b3HullFace* faces = b3GetHullFaces( shape->hull );
const b3HullHalfEdge* edges = b3GetHullEdges( shape->hull );
const b3Plane* planes = b3GetHullPlanes( shape->hull );
b3Vec3 linearVelocity = state->linearVelocity;
b3Vec3 angularVelocity = state->angularVelocity;
b3Vec3 localCenterOfMass = sim->localCenter;
for ( int i = 0; i < faceCount; ++i )
{
const b3HullFace* face = faces + i;
const b3HullHalfEdge* edge1 = edges + face->edge;
const b3HullHalfEdge* edge2 = edges + edge1->next;
const b3HullHalfEdge* edge3 = edges + edge2->next;
B3_ASSERT( edge1 != edge3 );
B3_ASSERT( edge1->origin < shape->hull->vertexCount );
B3_ASSERT( edge2->origin < shape->hull->vertexCount );
b3Vec3 localPoint1 = points[edge1->origin];
b3Vec3 localPoint2 = points[edge2->origin];
b3Vec3 v1 = b3MulMV( matrix, localPoint1 );
b3Vec3 v2 = b3MulMV( matrix, localPoint2 );
b3Vec3 normal = b3MulMV( matrix, planes[i].normal );
do
{
B3_ASSERT( edge3->origin < shape->hull->vertexCount );
b3Vec3 localPoint3 = points[edge3->origin];
b3Vec3 v3 = b3MulMV( matrix, localPoint3 );
b3Vec3 localCenter = b3MulSV( 0.333333f, b3Add( localPoint1, b3Add( localPoint2, localPoint3 ) ) );
b3Vec3 lever = b3MulMV( matrix, b3Sub( localCenter, localCenterOfMass ) );
b3Vec3 centerVelocity = b3Add( linearVelocity, b3Cross( angularVelocity, lever ) );
b3Vec3 relativeVelocity = b3MulSub( wind, drag, centerVelocity );
float speed;
b3Vec3 direction = b3GetLengthAndNormalize( &speed, relativeVelocity );
if ( b3Dot( normal, direction ) < -FLT_EPSILON )
{
float projectedArea = -0.5f * b3Dot( b3Cross( b3Sub( v2, v1 ), b3Sub( v3, v1 ) ), direction );
B3_VALIDATE( projectedArea >= -FLT_EPSILON );
b3Vec3 liftDirection = b3Cross( b3Cross( normal, direction ), direction );
speed = b3MinFloat( speed, maxSpeed );
float forceMagnitude = 0.5f * airDensity * projectedArea * speed * speed;
b3Vec3 deltaForce = b3MulSV( forceMagnitude, b3MulAdd( direction, lift, liftDirection ) );
b3Vec3 deltaTorque = b3Cross( lever, deltaForce );
force = b3Add( force, deltaForce );
torque = b3Add( torque, deltaTorque );
#if B3_DEBUG_WIND
int lineIndex = world->taskContexts.data[0].lineCount;
if ( lineIndex < B3_DEBUG_LINE_CAPACITY )
{
b3DebugLine* line = world->taskContexts.data[0].lines + lineIndex;
line->p1 = b3OffsetPos( sim->transform.p, b3MulMV( matrix, localCenter ) );
line->p2 = b3OffsetPos( line->p1, deltaForce );
line->label = i;
line->color = b3_colorBlanchedAlmond;
world->taskContexts.data[0].lineCount += 1;
}
#endif
}
edge2 = edge3;
edge3 = edges + edge3->next;
v2 = v3;
localPoint2 = localPoint3;
}
while ( edge1 != edge3 );
}
}
break;
default:
break;
}
sim->force = b3Add( sim->force, force );
sim->torque = b3Add( sim->torque, torque );
}
typedef struct b3MeshImpactContext
{
b3TOIInput toiInput;
b3TOIOutput toiOutput;
b3Vec3 localCentroidB;
b3Vec3 meshLocalCentroidB1, meshLocalCentroidB2;
float fallbackRadius;
bool isSensor;
int visitCount;
} b3MeshImpactContext;
static bool b3MeshTimeOfImpactFcn( b3Vec3 a, b3Vec3 b, b3Vec3 c, int triangleIndex, void* context )
{
B3_UNUSED( triangleIndex );
b3MeshImpactContext* toiContext = context;
toiContext->visitCount += 1;
b3Vec3 c1 = toiContext->meshLocalCentroidB1;
b3Vec3 c2 = toiContext->meshLocalCentroidB2;
b3Vec3 n = b3Normalize( b3Cross( b3Sub( b, a ), b3Sub( c, a ) ) );
float offset1 = b3Dot( n, b3Sub( c1, a ) );
float offset2 = b3Dot( n, b3Sub( c2, a ) );
if ( offset1 < 0.0f )
{
return true;
}
if ( toiContext->isSensor == false && offset1 - offset2 < toiContext->fallbackRadius && offset2 > toiContext->fallbackRadius )
{
return true;
}
b3Vec3 triangle[3] = { a, b, c };
toiContext->toiInput.proxyA.points = triangle;
toiContext->toiInput.proxyA.count = 3;
b3TOIOutput output = b3TimeOfImpact( &toiContext->toiInput );
if ( 0.0f < output.fraction && output.fraction < toiContext->toiInput.maxFraction )
{
toiContext->toiOutput = output;
toiContext->toiInput.maxFraction = output.fraction;
}
else if ( 0.0f == output.fraction )
{
b3TOIInput fallbackInput = toiContext->toiInput;
fallbackInput.proxyB = (b3ShapeProxy){ &toiContext->localCentroidB, 1, toiContext->fallbackRadius + B3_LINEAR_SLOP };
output = b3TimeOfImpact( &fallbackInput );
if ( 0.0f < output.fraction && output.fraction < toiContext->toiInput.maxFraction )
{
toiContext->toiOutput = output;
toiContext->toiInput.maxFraction = output.fraction;
toiContext->toiOutput.usedFallback = true;
}
}
return true;
}
typedef struct b3CompoundImpactContext
{
b3TOIInput toiInput;
b3TOIOutput toiOutput;
b3Transform compoundTransform;
b3AABB localSweepBoundsB;
b3Vec3 localCentroidB;
float fallbackRadius;
} b3CompoundImpactContext;
static bool b3CompoundTimeOfImpactFcn( const b3CompoundData* compound, int childIndex, void* context )
{
b3CompoundImpactContext* toiContext = (b3CompoundImpactContext*)context;
b3ChildShape child = b3GetCompoundChild( compound, childIndex );
b3TOIOutput output = { 0 };
toiContext->toiInput.sweepA = b3MakeCompoundChildSweep( toiContext->compoundTransform, child.transform );
switch ( child.type )
{
case b3_capsuleShape:
{
toiContext->toiInput.proxyA.points = &child.capsule.center1;
toiContext->toiInput.proxyA.count = 2;
toiContext->toiInput.proxyA.radius = child.capsule.radius;
output = b3TimeOfImpact( &toiContext->toiInput );
}
break;
case b3_hullShape:
{
toiContext->toiInput.proxyA.points = b3GetHullPoints( child.hull );
toiContext->toiInput.proxyA.count = child.hull->vertexCount;
toiContext->toiInput.proxyA.radius = 0.0f;
output = b3TimeOfImpact( &toiContext->toiInput );
}
break;
case b3_meshShape:
{
b3MeshImpactContext meshContext = { 0 };
meshContext.toiInput = toiContext->toiInput;
meshContext.isSensor = false;
meshContext.localCentroidB = toiContext->localCentroidB;
meshContext.fallbackRadius = toiContext->fallbackRadius;
b3Transform meshWorldTransform = b3MulTransforms( toiContext->compoundTransform, child.transform );
const b3Sweep* sweepB = &toiContext->toiInput.sweepB;
b3Transform xfB1 = {
.p = b3Sub( sweepB->c1, b3RotateVector( sweepB->q1, sweepB->localCenter ) ),
.q = sweepB->q1,
};
b3Transform xfB2 = {
.p = b3Sub( sweepB->c2, b3RotateVector( sweepB->q2, sweepB->localCenter ) ),
.q = sweepB->q2,
};
meshContext.meshLocalCentroidB1 =
b3InvTransformPoint( meshWorldTransform, b3TransformPoint( xfB1, meshContext.localCentroidB ) );
meshContext.meshLocalCentroidB2 =
b3InvTransformPoint( meshWorldTransform, b3TransformPoint( xfB2, meshContext.localCentroidB ) );
b3AABB localBounds = b3AABB_Transform( b3InvertTransform( child.transform ), toiContext->localSweepBoundsB );
b3QueryMesh( &child.mesh, localBounds, b3MeshTimeOfImpactFcn, &meshContext );
output = meshContext.toiOutput;
}
break;
case b3_sphereShape:
{
toiContext->toiInput.proxyA.points = &child.sphere.center;
toiContext->toiInput.proxyA.count = 1;
toiContext->toiInput.proxyA.radius = child.sphere.radius;
output = b3TimeOfImpact( &toiContext->toiInput );
}
break;
default:
B3_ASSERT( false );
break;
}
if ( 0.0f < output.fraction && output.fraction < toiContext->toiInput.maxFraction )
{
toiContext->toiOutput = output;
toiContext->toiInput.maxFraction = output.fraction;
}
toiContext->toiInput.proxyA = (b3ShapeProxy){ 0 };
return true;
}
b3TOIOutput b3ShapeTimeOfImpact( b3Shape* shapeA, b3Shape* shapeB, b3Sweep* sweepA, b3Sweep* sweepB, float maxFraction )
{
bool isSensor = shapeA->sensorIndex != B3_NULL_INDEX;
b3ShapeType typeA = shapeA->type;
if ( typeA == b3_compoundShape )
{
b3CompoundImpactContext context = { 0 };
context.toiInput.proxyB = b3MakeShapeProxy( shapeB );
context.toiInput.sweepB = *sweepB;
context.toiInput.maxFraction = maxFraction;
context.compoundTransform = (b3Transform){
.p = sweepA->c1,
.q = sweepA->q1,
};
b3Vec3 localCentroidB = b3GetShapeCentroid( shapeB );
context.localCentroidB = localCentroidB;
b3ShapeExtent extents = b3ComputeShapeExtent( shapeB, context.localCentroidB );
context.fallbackRadius = b3MaxFloat( 0.75f * extents.minExtent, B3_SPECULATIVE_DISTANCE );
b3AABB bounds = b3ComputeSweptShapeAABB( shapeB, sweepB, maxFraction );
b3AABB localBounds = b3AABB_Transform( b3InvertTransform( context.compoundTransform ), bounds );
context.localSweepBoundsB = localBounds;
b3QueryCompound( shapeA->compound, localBounds, b3CompoundTimeOfImpactFcn, &context );
return context.toiOutput;
}
if ( typeA == b3_heightShape || typeA == b3_meshShape )
{
uint64_t ticks = b3GetTicks();
b3MeshImpactContext context = { 0 };
context.toiInput.sweepA = *sweepA;
context.toiInput.proxyA.count = 3;
context.toiInput.proxyB = b3MakeShapeProxy( shapeB );
context.toiInput.sweepB = *sweepB;
context.toiInput.maxFraction = maxFraction;
context.isSensor = isSensor;
b3Vec3 localCentroidB = b3GetShapeCentroid( shapeB );
context.localCentroidB = localCentroidB;
b3Transform xfA = {
.p = b3Sub( sweepA->c1, b3RotateVector( sweepA->q1, sweepA->localCenter ) ),
.q = sweepA->q1,
};
b3Transform xfB1 = {
.p = b3Sub( sweepB->c1, b3RotateVector( sweepB->q1, sweepB->localCenter ) ),
.q = sweepB->q1,
};
b3Transform xfB2 = {
.p = b3Sub( sweepB->c2, b3RotateVector( sweepB->q2, sweepB->localCenter ) ),
.q = sweepB->q2,
};
context.meshLocalCentroidB1 = b3InvTransformPoint( xfA, b3TransformPoint( xfB1, localCentroidB ) );
context.meshLocalCentroidB2 = b3InvTransformPoint( xfA, b3TransformPoint( xfB2, localCentroidB ) );
b3ShapeExtent extents = b3ComputeShapeExtent( shapeB, context.localCentroidB );
context.fallbackRadius = b3MaxFloat( 0.5f * extents.minExtent, B3_LINEAR_SLOP );
b3AABB bounds = b3ComputeSweptShapeAABB( shapeB, sweepB, maxFraction );
b3AABB localBounds = b3AABB_Transform( b3InvertTransform( xfA ), bounds );
if ( typeA == b3_meshShape )
{
b3QueryMesh( &shapeA->mesh, localBounds, b3MeshTimeOfImpactFcn, &context );
}
else if ( typeA == b3_heightShape )
{
b3QueryHeightField( shapeA->heightField, localBounds, b3MeshTimeOfImpactFcn, &context );
}
float ms = b3GetMilliseconds( ticks );
if ( ms > 1000.0f * b3GetStallThreshold() )
{
b3Log( "CCD stall: visited %d triangles", context.visitCount );
}
return context.toiOutput;
}
B3_ASSERT( shapeB->type != b3_compoundShape && shapeB->type != b3_meshShape && shapeB->type != b3_heightShape );
b3TOIInput input;
input.proxyA = b3MakeShapeProxy( shapeA );
input.proxyB = b3MakeShapeProxy( shapeB );
input.sweepA = *sweepA;
input.sweepB = *sweepB;
input.maxFraction = maxFraction;
b3TOIOutput output = b3TimeOfImpact( &input );
#if 0#endif
return output;
}
uint64_t b3GetShapeUserMaterialId( const b3Shape* shape, int childIndex, int triangleIndex )
{
if ( shape->materialCount == 0 )
{
return 0;
}
int materialIndex = 0;
if ( shape->type == b3_meshShape )
{
const uint8_t* indices = b3GetMeshMaterialIndices( shape->mesh.data );
if ( indices != NULL )
{
materialIndex = indices[triangleIndex];
}
}
else if ( shape->type == b3_heightShape )
{
materialIndex = b3GetHeightFieldMaterial( shape->heightField, triangleIndex );
}
else if ( shape->type == b3_compoundShape )
{
b3ChildShape child = b3GetCompoundChild( shape->compound, childIndex );
if ( child.type == b3_meshShape )
{
const uint8_t* indices = b3GetMeshMaterialIndices( child.mesh.data );
int meshMaterialIndex = indices != NULL ? indices[triangleIndex] : 0;
meshMaterialIndex = b3ClampInt( meshMaterialIndex, 0, B3_MAX_COMPOUND_MESH_MATERIALS - 1 );
materialIndex = child.materialIndices[meshMaterialIndex];
}
else
{
materialIndex = child.materialIndices[0];
}
}
materialIndex = b3ClampInt( materialIndex, 0, shape->materialCount - 1 );
return b3GetShapeMaterials( shape )[materialIndex].userMaterialId;
}
void b3DumpShape( b3World* world, int shapeIndex )
{
b3Shape* shape = b3Array_Get( world->shapes, shapeIndex );
b3Dump( " b3ShapeDef sd = b3DefaultShapeDef();\n" );
b3Dump( " sd.density = %.9g;\n", shape->density );
b3Dump( " sd.isSensor = bool(%d);\n", shape->sensorIndex != B3_NULL_INDEX );
b3Dump( " sd.filter.categoryBits = 0x%" PRIx64 ";\n", shape->filter.categoryBits );
b3Dump( " sd.filter.maskBits = 0x%" PRIx64 ";\n", shape->filter.maskBits );
b3Dump( " sd.filter.groupIndex = %d;\n", shape->filter.groupIndex );
B3_ASSERT( shape->materialCount >= 1 );
const b3SurfaceMaterial* m = b3GetShapeMaterials( shape );
b3Dump( " sd.baseMaterial.friction = %.9g;\n", m->friction );
b3Dump( " sd.baseMaterial.restitution = %.9g;\n", m->restitution );
b3Dump( " sd.baseMaterial.rollingResistance = %.9g;\n", m->rollingResistance );
switch ( shape->type )
{
case b3_capsuleShape:
{
b3Capsule* s = &shape->capsule;
b3Dump( " b3CapsuleShape shape;\n" );
b3Dump( " shape.center1 = {%.9g, %.9g, %.9g};\n", s->center1.x, s->center1.y, s->center1.z );
b3Dump( " shape.center2 = {%.9g, %.9g, %.9g};\n", s->center2.x, s->center2.y, s->center2.z );
b3Dump( " shape.radius = %.9g;\n", s->radius );
b3Dump( " b3CreateCapsuleShape(bodyId, &sd, &shape);\n" );
}
break;
case b3_compoundShape:
{
}
break;
case b3_heightShape:
{
}
break;
case b3_hullShape:
{
const b3HullData* s = shape->hull;
int vertexCount = s->vertexCount;
const b3Vec3* vs = b3GetHullPoints( s );
b3Dump( " b3Vec3 vs[%d];\n", vertexCount );
for ( int i = 0; i < vertexCount; ++i )
{
b3Dump( " vs[%d] = {%.9g, %.9g, %.9g};\n", i, vs[i].x, vs[i].y, vs[i].z );
}
b3Dump( " b3HullData* hullData = b3CreateHull(vs, %d, %d);\n", vertexCount, vertexCount );
b3Dump( " b3CreateHullShape(bodyId, &sd, hullData);\n" );
b3Dump( " b3DestroyHull(hullData);\n" );
}
break;
case b3_meshShape:
{
const b3MeshData* s = shape->mesh.data;
b3Vec3 scale = shape->mesh.scale;
int meshIndex = b3FetchAddMeshDumpIndex();
char buffer[64];
snprintf( buffer, 64, "mesh_dump_%d.b3m", meshIndex );
b3WriteBinaryFile( (void*)s, s->byteCount, buffer );
b3Dump( " int dataSize = 0;\n" );
b3Dump( " b3MeshData* data = (b3MeshData*)b3ReadBinaryFile(dumpPrefix, \"%s\", &dataSize);\n", buffer );
b3Dump( " if (data == nullptr || data->version != B3_MESH_VERSION || data->byteCount != dataSize)\n" );
b3Dump( " {\n" );
b3Dump( " return;\n" );
b3Dump( " }\n" );
b3Dump( " b3Vec3 scale = { %.9g, %.9g, %.9g };\n", scale.x, scale.y, scale.z );
b3Dump( " b3CreateMeshShape(bodyId, &sd, data, scale);\n" );
b3Dump( " m_meshes.push_back(data);\n" );
}
break;
case b3_sphereShape:
{
b3Sphere* s = &shape->sphere;
b3Dump( " b3SphereShape shape;\n" );
b3Dump( " shape.center = {%.9g, %.9g, %.9g};\n", s->center.x, s->center.y, s->center.z );
b3Dump( " shape.radius = %.9g;\n", s->radius );
b3Dump( " b3CreateSphereShape(bodyId, &sd, &shape);\n" );
}
break;
default:
return;
}
}