#include "body.h"
#include "aabb.h"
#include "contact.h"
#include "core.h"
#include "id_pool.h"
#include "island.h"
#include "joint.h"
#include "physics_world.h"
#include "recording.h"
#include "sensor.h"
#include "shape.h"
#include "solver_set.h"
#include "box3d/box3d.h"
#include "box3d/id.h"
#include <stddef.h>
#include <string.h>
_Static_assert( B3_BODY_NAME_LENGTH >= 0, "minimum name length" );
b3Body* b3GetBodyFullId( b3World* world, b3BodyId bodyId )
{
B3_ASSERT( b3Body_IsValid( bodyId ) );
return b3Array_Get( world->bodies, bodyId.index1 - 1 );
}
b3WorldTransform b3GetBodyTransformQuick( b3World* world, b3Body* body )
{
b3SolverSet* set = b3Array_Get( world->solverSets, body->setIndex );
b3BodySim* bodySim = b3Array_Get( set->bodySims, body->localIndex );
return bodySim->transform;
}
b3WorldTransform b3GetBodyTransform( b3World* world, int bodyId )
{
b3Body* body = b3Array_Get( world->bodies, bodyId );
return b3GetBodyTransformQuick( world, body );
}
b3BodyId b3MakeBodyId( b3World* world, int bodyId )
{
b3Body* body = b3Array_Get( world->bodies, bodyId );
return (b3BodyId){ bodyId + 1, world->worldId, body->generation };
}
b3BodySim* b3GetBodySim( b3World* world, b3Body* body )
{
b3SolverSet* set = b3Array_Get( world->solverSets, body->setIndex );
b3BodySim* bodySim = b3Array_Get( set->bodySims, body->localIndex );
return bodySim;
}
b3BodyState* b3GetBodyState( b3World* world, b3Body* body )
{
if ( body->setIndex == b3_awakeSet )
{
b3SolverSet* set = b3Array_Get( world->solverSets, b3_awakeSet );
return b3Array_Get( set->bodyStates, body->localIndex );
}
return NULL;
}
static void b3SyncBodyFlags( b3World* world, b3Body* body )
{
uint32_t flags = body->flags & ~b3_bodyTransientFlags;
b3BodySim* bodySim = b3GetBodySim( world, body );
bodySim->flags = flags;
b3BodyState* bodyState = b3GetBodyState( world, body );
if ( bodyState != NULL )
{
bodyState->flags = flags;
}
}
static void b3CreateIslandForBody( b3World* world, int setIndex, b3Body* body )
{
B3_ASSERT( body->islandId == B3_NULL_INDEX );
B3_ASSERT( setIndex != b3_disabledSet );
b3Island* island = b3CreateIsland( world, setIndex );
b3Array_Push( island->bodies, body->id );
body->islandId = island->islandId;
body->islandIndex = 0;
b3ValidateIsland( world, island->islandId );
}
static void b3RemoveBodyFromIsland( b3World* world, b3Body* body )
{
if ( body->islandId == B3_NULL_INDEX )
{
B3_ASSERT( body->islandIndex == B3_NULL_INDEX );
return;
}
int islandId = body->islandId;
b3Island* island = b3Array_Get( world->islands, islandId );
{
int localIndex = body->islandIndex;
int movedBodyId = island->bodies.data[island->bodies.count - 1];
island->bodies.data[localIndex] = movedBodyId;
B3_VALIDATE( world->bodies.data[movedBodyId].islandIndex == island->bodies.count - 1 );
world->bodies.data[movedBodyId].islandIndex = localIndex;
island->bodies.count -= 1;
}
if ( island->bodies.count == 0 )
{
B3_ASSERT( island->contacts.count == 0 );
B3_ASSERT( island->joints.count == 0 );
b3DestroyIsland( world, island->islandId );
}
else
{
b3ValidateIsland( world, islandId );
}
body->islandId = B3_NULL_INDEX;
body->islandIndex = B3_NULL_INDEX;
}
static void b3DestroyBodyContacts( b3World* world, b3Body* body, bool wakeBodies )
{
int edgeKey = body->headContactKey;
while ( edgeKey != B3_NULL_INDEX )
{
int contactId = edgeKey >> 1;
int edgeIndex = edgeKey & 1;
b3Contact* contact = b3Array_Get( world->contacts, contactId );
edgeKey = contact->edges[edgeIndex].nextKey;
b3DestroyContact( world, contact, wakeBodies );
}
b3ValidateSolverSets( world );
}
b3BodyId b3CreateBody( b3WorldId worldId, const b3BodyDef* def )
{
B3_CHECK_DEF( def );
B3_ASSERT( b3IsValidPosition( def->position ) );
B3_ASSERT( b3IsValidQuat( def->rotation ) );
B3_ASSERT( b3IsValidVec3( def->linearVelocity ) );
B3_ASSERT( b3IsValidVec3( def->angularVelocity ) );
B3_ASSERT( b3IsValidFloat( def->linearDamping ) && def->linearDamping >= 0.0f );
B3_ASSERT( b3IsValidFloat( def->angularDamping ) && def->angularDamping >= 0.0f );
B3_ASSERT( b3IsValidFloat( def->sleepThreshold ) && def->sleepThreshold >= 0.0f );
B3_ASSERT( b3IsValidFloat( def->gravityScale ) );
b3World* world = b3GetUnlockedWorldFromId( worldId );
if ( world == NULL )
{
return b3_nullBodyId;
}
world->locked = true;
bool isAwake = ( def->isAwake || def->enableSleep == false ) && def->isEnabled;
int setId;
if ( def->isEnabled == false )
{
setId = b3_disabledSet;
}
else if ( def->type == b3_staticBody )
{
setId = b3_staticSet;
}
else if ( isAwake == true )
{
setId = b3_awakeSet;
}
else
{
setId = b3AllocId( &world->solverSetIdPool );
if ( setId == world->solverSets.count )
{
b3Array_Push( world->solverSets, (b3SolverSet){ 0 } );
}
else
{
B3_ASSERT( world->solverSets.data[setId].setIndex == B3_NULL_INDEX );
}
world->solverSets.data[setId].setIndex = setId;
}
B3_ASSERT( 0 <= setId && setId < world->solverSets.count );
int bodyId = b3AllocId( &world->bodyIdPool );
uint32_t lockFlags = 0;
lockFlags |= def->motionLocks.linearX ? b3_lockLinearX : 0;
lockFlags |= def->motionLocks.linearY ? b3_lockLinearY : 0;
lockFlags |= def->motionLocks.linearZ ? b3_lockLinearZ : 0;
lockFlags |= def->motionLocks.angularX ? b3_lockAngularX : 0;
lockFlags |= def->motionLocks.angularY ? b3_lockAngularY : 0;
lockFlags |= def->motionLocks.angularZ ? b3_lockAngularZ : 0;
b3SolverSet* set = b3Array_Get( world->solverSets, setId );
b3BodySim* bodySim = b3Array_Emplace( set->bodySims );
*bodySim = (b3BodySim){ 0 };
bodySim->transform.p = def->position;
bodySim->transform.q = def->rotation;
bodySim->center = def->position;
bodySim->rotation0 = bodySim->transform.q;
bodySim->center0 = bodySim->center;
bodySim->localCenter = b3Vec3_zero;
bodySim->force = b3Vec3_zero;
bodySim->torque = b3Vec3_zero;
bodySim->invMass = 0.0f;
bodySim->invInertiaLocal = b3Mat3_zero;
bodySim->minExtent = B3_HUGE;
bodySim->maxExtent = b3Vec3_zero;
bodySim->linearDamping = def->linearDamping;
bodySim->angularDamping = def->angularDamping;
bodySim->gravityScale = def->gravityScale;
bodySim->bodyId = bodyId;
bodySim->flags = lockFlags;
bodySim->flags |= def->isBullet ? b3_isBullet : 0;
bodySim->flags |= def->allowFastRotation ? b3_allowFastRotation : 0;
bodySim->flags |= def->type == b3_dynamicBody ? b3_dynamicFlag : 0;
bodySim->flags |= def->enableSleep ? b3_enableSleep : 0;
bodySim->flags |= def->enableContactRecycling ? b3_bodyEnableContactRecycling : 0;
if ( setId == b3_awakeSet )
{
b3BodyState* bodyState = b3Array_Emplace( set->bodyStates );
*bodyState = (b3BodyState){ 0 };
bodyState->linearVelocity = def->linearVelocity;
bodyState->angularVelocity = def->angularVelocity;
bodyState->deltaRotation = b3Quat_identity;
bodyState->flags = bodySim->flags;
bodySim->maxAngularVelocity = b3Length( def->angularVelocity ) + 5.0f;
}
if ( bodyId == world->bodies.count )
{
b3Array_Push( world->bodies, (b3Body){ 0 } );
}
else
{
B3_ASSERT( world->bodies.data[bodyId].id == B3_NULL_INDEX );
}
b3Body* body = b3Array_Get( world->bodies, bodyId );
if ( def->name )
{
#if defined( _MSC_VER )
strncpy_s( body->name, B3_BODY_NAME_LENGTH + 1, def->name, B3_BODY_NAME_LENGTH );
#else
strncpy( body->name, def->name, B3_BODY_NAME_LENGTH );
body->name[B3_BODY_NAME_LENGTH] = 0;
#endif
}
else
{
memset( body->name, 0, sizeof( body->name ) );
}
body->userData = def->userData;
body->setIndex = setId;
body->localIndex = set->bodySims.count - 1;
body->generation += 1;
body->headShapeId = B3_NULL_INDEX;
body->shapeCount = 0;
body->headChainId = B3_NULL_INDEX;
body->headContactKey = B3_NULL_INDEX;
body->contactCount = 0;
body->headJointKey = B3_NULL_INDEX;
body->jointCount = 0;
body->islandId = B3_NULL_INDEX;
body->islandIndex = B3_NULL_INDEX;
body->bodyMoveIndex = B3_NULL_INDEX;
body->id = bodyId;
body->sleepThreshold = def->sleepThreshold;
body->sleepTime = 0.0f;
body->mass = 0.0f;
body->inertia = b3Mat3_zero;
body->type = def->type;
body->flags = bodySim->flags;
if ( setId >= b3_awakeSet )
{
b3CreateIslandForBody( world, setId, body );
}
b3ValidateSolverSets( world );
b3BodyId id = { bodyId + 1, world->worldId, body->generation };
world->locked = false;
B3_REC_CREATE( world, CreateBody, id, worldId, *def );
return id;
}
bool b3IsBodyAwake( b3World* world, b3Body* body )
{
B3_UNUSED( world );
return body->setIndex == b3_awakeSet;
}
bool b3WakeBody( b3World* world, b3Body* body )
{
if ( body->setIndex >= b3_firstSleepingSet )
{
b3WakeSolverSet( world, body->setIndex );
b3ValidateSolverSets( world );
return true;
}
return false;
}
bool b3WakeBodyWithLock( b3World* world, b3Body* body )
{
B3_ASSERT( world->locked == false );
world->locked = true;
bool woke = b3WakeBody( world, body );
world->locked = false;
return woke;
}
void b3DestroyBody( b3BodyId bodyId )
{
b3World* world = b3GetUnlockedWorld( bodyId.world0 );
if ( world == NULL )
{
return;
}
B3_REC( world, DestroyBody, bodyId );
world->locked = true;
b3Body* body = b3GetBodyFullId( world, bodyId );
bool wakeBodies = true;
int edgeKey = body->headJointKey;
while ( edgeKey != B3_NULL_INDEX )
{
int jointId = edgeKey >> 1;
int edgeIndex = edgeKey & 1;
b3Joint* joint = b3Array_Get( world->joints, jointId );
edgeKey = joint->edges[edgeIndex].nextKey;
b3DestroyJointInternal( world, joint, wakeBodies );
}
b3DestroyBodyContacts( world, body, wakeBodies );
int shapeId = body->headShapeId;
while ( shapeId != B3_NULL_INDEX )
{
b3Shape* shape = b3Array_Get( world->shapes, shapeId );
if ( shape->sensorIndex != B3_NULL_INDEX )
{
b3DestroySensor( world, shape );
}
b3DestroyShapeProxy( shape, &world->broadPhase );
b3DestroyShapeAllocations( world, shape );
b3FreeId( &world->shapeIdPool, shapeId );
shape->id = B3_NULL_INDEX;
shapeId = shape->nextShapeId;
}
b3RemoveBodyFromIsland( world, body );
b3SolverSet* set = b3Array_Get( world->solverSets, body->setIndex );
int movedIndex = b3Array_RemoveSwap( set->bodySims, body->localIndex );
if ( movedIndex != B3_NULL_INDEX )
{
b3BodySim* movedSim = set->bodySims.data + body->localIndex;
int movedId = movedSim->bodyId;
b3Body* movedBody = b3Array_Get( world->bodies, movedId );
B3_ASSERT( movedBody->localIndex == movedIndex );
movedBody->localIndex = body->localIndex;
}
if ( body->setIndex == b3_awakeSet )
{
int result = b3Array_RemoveSwap( set->bodyStates, body->localIndex );
B3_UNUSED( result );
B3_ASSERT( result == movedIndex );
}
else if ( set->setIndex >= b3_firstSleepingSet && set->bodySims.count == 0 )
{
b3DestroySolverSet( world, set->setIndex );
}
b3FreeId( &world->bodyIdPool, body->id );
body->setIndex = B3_NULL_INDEX;
body->localIndex = B3_NULL_INDEX;
body->id = B3_NULL_INDEX;
b3ValidateSolverSets( world );
world->locked = false;
}
int b3Body_GetContactCapacity( b3BodyId bodyId )
{
b3World* world = b3GetUnlockedWorld( bodyId.world0 );
if ( world == NULL )
{
return 0;
}
b3Body* body = b3GetBodyFullId( world, bodyId );
return body->contactCount;
}
int b3Body_GetContactData( b3BodyId bodyId, b3ContactData* contactData, int capacity )
{
b3World* world = b3GetUnlockedWorld( bodyId.world0 );
if ( world == NULL )
{
return 0;
}
b3Body* body = b3GetBodyFullId( world, 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->flags & b3_contactTouchingFlag )
{
b3Shape* shapeA = b3Array_Get( world->shapes, contact->shapeIdA );
b3Shape* shapeB = b3Array_Get( world->shapes, contact->shapeIdB );
contactData[index].contactId = (b3ContactId){ contact->contactId + 1, bodyId.world0, 0, contact->generation };
contactData[index].shapeIdA = (b3ShapeId){ shapeA->id + 1, bodyId.world0, shapeA->generation };
contactData[index].shapeIdB = (b3ShapeId){ shapeB->id + 1, bodyId.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;
}
b3AABB b3Body_ComputeAABB( b3BodyId bodyId )
{
b3World* world = b3GetUnlockedWorld( bodyId.world0 );
if ( world == NULL )
{
return (b3AABB){ 0 };
}
b3Body* body = b3GetBodyFullId( world, bodyId );
if ( body->headShapeId == B3_NULL_INDEX )
{
b3WorldTransform transform = b3GetBodyTransform( world, body->id );
b3Vec3 p = b3ToVec3( transform.p );
return (b3AABB){ p, p };
}
b3Shape* shape = b3Array_Get( world->shapes, body->headShapeId );
b3AABB aabb = shape->aabb;
while ( shape->nextShapeId != B3_NULL_INDEX )
{
shape = b3Array_Get( world->shapes, shape->nextShapeId );
aabb = b3AABB_Union( aabb, shape->aabb );
}
return aabb;
}
float b3Body_GetClosestPoint( b3BodyId bodyId, b3Vec3* result, b3Vec3 target )
{
b3World* world = b3GetUnlockedWorld( bodyId.world0 );
if ( world == NULL )
{
*result = (b3Vec3){ 0 };
return 0.0f;
}
b3Body* body = b3GetBodyFullId( world, bodyId );
b3WorldTransform worldTransform = b3GetBodyTransform( world, body->id );
b3Transform transform = b3ToRelativeTransform( worldTransform, b3Pos_zero );
float closestDistance = FLT_MAX;
b3Vec3 closestPoint = transform.p;
b3DistanceInput input = { 0 };
input.proxyA = (b3ShapeProxy){ &target, 1, 0.0f };
input.transform = transform;
input.useRadii = false;
int shapeId = body->headShapeId;
while ( shapeId != B3_NULL_INDEX )
{
b3Shape* shape = b3Array_Get( world->shapes, shapeId );
shapeId = shape->nextShapeId;
b3ShapeType type = shape->type;
if ( type != b3_sphereShape && type != b3_capsuleShape && type != b3_hullShape )
{
continue;
}
input.proxyB = b3MakeShapeProxy( shape );
b3SimplexCache cache = { 0 };
b3DistanceOutput output = b3ShapeDistance( &input, &cache, NULL, 0 );
if ( output.distance < closestDistance )
{
closestDistance = output.distance;
closestPoint = output.pointB;
}
}
*result = closestPoint;
return closestDistance;
}
b3BodyCastResult b3Body_CastRay( b3BodyId bodyId, b3Pos origin, b3Vec3 translation, b3QueryFilter filter, float maxFraction,
b3WorldTransform bodyTransform )
{
b3World* world = b3GetUnlockedWorld( bodyId.world0 );
if ( world == NULL )
{
return (b3BodyCastResult){ 0 };
}
b3BodyCastResult result = { 0 };
b3Body* body = b3GetBodyFullId( world, bodyId );
b3RayCastInput shapeInput = { 0 };
shapeInput.origin = b3Vec3_zero;
shapeInput.translation = translation;
shapeInput.maxFraction = maxFraction;
b3Transform transform = b3ToRelativeTransform( bodyTransform, origin );
int shapeId = body->headShapeId;
while ( shapeId != B3_NULL_INDEX )
{
b3Shape* shape = b3Array_Get( world->shapes, shapeId );
shapeId = shape->nextShapeId;
if ( b3ShouldQueryCollide( &shape->filter, &filter ) == false )
{
continue;
}
b3CastOutput shapeOutput = b3RayCastShape( shape, transform, &shapeInput );
if ( shapeOutput.hit == false )
{
continue;
}
if ( shapeOutput.fraction > shapeInput.maxFraction )
{
continue;
}
b3ShapeId id = { shape->id + 1, bodyId.world0, shape->generation };
int materialIndex = b3ClampInt( shapeOutput.materialIndex, 0, shape->materialCount - 1 );
uint64_t userMaterialId = b3GetShapeMaterials( shape )[materialIndex].userMaterialId;
result = (b3BodyCastResult){
.shapeId = id,
.point = b3OffsetPos( origin, shapeOutput.point ),
.normal = shapeOutput.normal,
.fraction = shapeOutput.fraction,
.triangleIndex = shapeOutput.triangleIndex,
.userMaterialId = userMaterialId,
.iterations = shapeOutput.iterations,
.hit = true,
};
shapeInput.maxFraction = shapeOutput.fraction;
}
return result;
}
b3BodyCastResult b3Body_CastShape( b3BodyId bodyId, b3Pos origin, const b3ShapeProxy* proxy, b3Vec3 translation,
b3QueryFilter filter, float maxFraction, bool canEncroach, b3WorldTransform bodyTransform )
{
b3World* world = b3GetUnlockedWorld( bodyId.world0 );
if ( world == NULL )
{
return (b3BodyCastResult){ 0 };
}
b3BodyCastResult result = { 0 };
b3Body* body = b3GetBodyFullId( world, bodyId );
b3Transform transform = b3ToRelativeTransform( bodyTransform, origin );
b3ShapeCastInput shapeInput = { 0 };
shapeInput.proxy = *proxy;
shapeInput.translation = translation;
shapeInput.maxFraction = maxFraction;
shapeInput.canEncroach = canEncroach;
int shapeId = body->headShapeId;
while ( shapeId != B3_NULL_INDEX )
{
b3Shape* shape = b3Array_Get( world->shapes, shapeId );
shapeId = shape->nextShapeId;
if ( b3ShouldQueryCollide( &shape->filter, &filter ) == false )
{
continue;
}
b3CastOutput shapeOutput = b3ShapeCastShape( shape, transform, &shapeInput );
if ( shapeOutput.hit == false )
{
continue;
}
if ( shapeOutput.fraction > shapeInput.maxFraction )
{
continue;
}
b3ShapeId id = { shape->id + 1, bodyId.world0, shape->generation };
int materialIndex = b3ClampInt( shapeOutput.materialIndex, 0, shape->materialCount - 1 );
uint64_t userMaterialId = b3GetShapeMaterials( shape )[materialIndex].userMaterialId;
result = (b3BodyCastResult){
.shapeId = id,
.point = b3OffsetPos( origin, shapeOutput.point ),
.normal = shapeOutput.normal,
.fraction = shapeOutput.fraction,
.triangleIndex = shapeOutput.triangleIndex,
.userMaterialId = userMaterialId,
.iterations = shapeOutput.iterations,
.hit = true,
};
shapeInput.maxFraction = shapeOutput.fraction;
}
return result;
}
bool b3Body_OverlapShape( b3BodyId bodyId, b3Pos origin, const b3ShapeProxy* proxy, b3QueryFilter filter,
b3WorldTransform bodyTransform )
{
b3World* world = b3GetUnlockedWorld( bodyId.world0 );
if ( world == NULL )
{
return false;
}
b3Body* body = b3GetBodyFullId( world, bodyId );
b3Transform transform = b3ToRelativeTransform( bodyTransform, origin );
int shapeId = body->headShapeId;
while ( shapeId != B3_NULL_INDEX )
{
b3Shape* shape = b3Array_Get( world->shapes, shapeId );
shapeId = shape->nextShapeId;
if ( b3ShouldQueryCollide( &shape->filter, &filter ) == false )
{
continue;
}
bool overlaps = b3OverlapShape( shape, transform, proxy );
if ( overlaps )
{
return true;
}
}
return false;
}
int b3Body_CollideMover( b3BodyId bodyId, b3BodyPlaneResult* bodyPlanes, int planeCapacity, b3Pos origin, const b3Capsule* mover,
b3QueryFilter filter, b3WorldTransform bodyTransform )
{
b3World* world = b3GetUnlockedWorld( bodyId.world0 );
if ( world == NULL )
{
return 0;
}
if ( planeCapacity == 0 )
{
return 0;
}
int resultCount = 0;
b3Body* body = b3GetBodyFullId( world, bodyId );
b3Transform transform = b3ToRelativeTransform( bodyTransform, origin );
int shapeId = body->headShapeId;
while ( shapeId != B3_NULL_INDEX )
{
b3Shape* shape = b3Array_Get( world->shapes, shapeId );
shapeId = shape->nextShapeId;
if ( b3ShouldQueryCollide( &shape->filter, &filter ) == false )
{
continue;
}
b3ShapeType type = shape->type;
if ( type != b3_sphereShape && type != b3_capsuleShape && type != b3_hullShape )
{
continue;
}
b3PlaneResult plane;
int count = b3CollideMover( &plane, 1, shape, transform, mover );
if ( count > 0 )
{
b3ShapeId id = { shape->id + 1, bodyId.world0, shape->generation };
bodyPlanes[resultCount] = (b3BodyPlaneResult){ .shapeId = id, .result = plane };
resultCount += 1;
if ( resultCount == planeCapacity )
{
return resultCount;
}
}
}
return resultCount;
}
void b3UpdateBodyMassData( b3World* world, b3Body* body )
{
b3BodySim* bodySim = b3GetBodySim( world, body );
body->mass = 0.0f;
body->inertia = b3Mat3_zero;
bodySim->invMass = 0.0f;
bodySim->invInertiaLocal = b3Mat3_zero;
bodySim->invInertiaWorld = b3Mat3_zero;
bodySim->localCenter = b3Vec3_zero;
bodySim->minExtent = B3_HUGE;
bodySim->maxExtent = b3Vec3_zero;
if ( body->headShapeId == B3_NULL_INDEX )
{
return;
}
if ( body->type != b3_dynamicBody )
{
bodySim->center = bodySim->transform.p;
bodySim->center0 = bodySim->center;
if ( body->type == b3_kinematicBody )
{
int shapeId = body->headShapeId;
while ( shapeId != B3_NULL_INDEX )
{
const b3Shape* s = b3Array_Get( world->shapes, shapeId );
b3ShapeExtent extent = b3ComputeShapeExtent( s, b3Vec3_zero );
bodySim->minExtent = b3MinFloat( bodySim->minExtent, extent.minExtent );
bodySim->maxExtent = b3Max( bodySim->maxExtent, extent.maxExtent );
shapeId = s->nextShapeId;
}
}
return;
}
int shapeCount = body->shapeCount;
b3MassData* masses = b3StackAlloc( &world->stack, shapeCount * sizeof( b3MassData ), "mass data" );
b3Vec3 localCenter = b3Vec3_zero;
int shapeId = body->headShapeId;
int shapeIndex = 0;
while ( shapeId != B3_NULL_INDEX )
{
const b3Shape* s = b3Array_Get( world->shapes, shapeId );
shapeId = s->nextShapeId;
if ( s->density == 0.0f )
{
masses[shapeIndex] = (b3MassData){ 0 };
shapeIndex += 1;
continue;
}
b3MassData massData = b3ComputeShapeMass( s );
body->mass += massData.mass;
localCenter = b3MulAdd( localCenter, massData.mass, massData.center );
masses[shapeIndex] = massData;
shapeIndex += 1;
}
if ( body->mass > 0.0f )
{
bodySim->invMass = 1.0f / body->mass;
localCenter = b3MulSV( bodySim->invMass, localCenter );
}
for ( shapeIndex = 0; shapeIndex < shapeCount; ++shapeIndex )
{
b3MassData massData = masses[shapeIndex];
if ( massData.mass == 0.0f )
{
continue;
}
b3Vec3 offset = b3Sub( localCenter, massData.center );
b3Matrix3 inertia = b3AddMM( massData.inertia, b3Steiner( massData.mass, offset ) );
body->inertia = b3AddMM(body->inertia, inertia);
}
b3StackFree( &world->stack, masses );
masses = NULL;
float det = b3Det( body->inertia );
B3_ASSERT( det >= 0.0f );
if ( det > 0.0f )
{
bodySim->invInertiaLocal = b3InvertT( body->inertia );
b3Matrix3 rotationMatrix = b3MakeMatrixFromQuat( bodySim->transform.q );
bodySim->invInertiaWorld = b3MulMM( b3MulMM( rotationMatrix, bodySim->invInertiaLocal ), b3Transpose( rotationMatrix ) );
}
b3Pos oldCenter = bodySim->center;
bodySim->localCenter = localCenter;
bodySim->center = b3TransformWorldPoint( bodySim->transform, bodySim->localCenter );
bodySim->center0 = bodySim->center;
b3BodyState* state = b3GetBodyState( world, body );
if ( state != NULL )
{
b3Vec3 deltaLinear = b3Cross( state->angularVelocity, b3SubPos( bodySim->center, oldCenter ) );
state->linearVelocity = b3Add( state->linearVelocity, deltaLinear );
}
shapeId = body->headShapeId;
while ( shapeId != B3_NULL_INDEX )
{
b3Shape* s = b3Array_Get( world->shapes, shapeId );
b3ShapeExtent extent = b3ComputeShapeExtent( s, localCenter );
bodySim->minExtent = b3MinFloat( bodySim->minExtent, extent.minExtent );
bodySim->maxExtent = b3Max( bodySim->maxExtent, extent.maxExtent );
shapeId = s->nextShapeId;
}
if ( ( bodySim->flags & b3_fixedRotation ) == b3_fixedRotation )
{
body->inertia = b3Mat3_zero;
bodySim->invInertiaLocal = b3Mat3_zero;
bodySim->invInertiaWorld = b3Mat3_zero;
}
}
void b3DumpBody( b3World* world, b3Body* body )
{
b3BodySim* sim = b3GetBodySim( world, body );
b3Vec3 p = b3ToVec3( sim->transform.p );
b3Quat q = sim->transform.q;
b3Vec3 v = b3Vec3_zero;
b3Vec3 w = b3Vec3_zero;
b3BodyState* state = b3GetBodyState( world, body );
if ( state != NULL )
{
v = state->linearVelocity;
w = state->angularVelocity;
}
b3Dump( "{\n" );
b3Dump( " b3BodyDef bd = b3DefaultBodyDef();\n" );
if ( body->name[0] != 0 )
{
b3Dump( " bd.name = \"%s\";\n", body->name );
}
b3Dump( " bd.type = b3BodyType(%d);\n", body->type );
b3Dump( " bd.position = {%.9g, %.9g, %.9g};\n", p.x, p.y, p.z );
b3Dump( " bd.rotation = {{%.9g, %.9g, %.9g}, %.9g};\n", q.v.x, q.v.y, q.v.z, q.s );
b3Dump( " bd.linearVelocity = {%.9g, %.9g, %.9g};\n", v.x, v.y, v.z );
b3Dump( " bd.angularVelocity = {%.9g, %.9g, %.9g};\n", w.x, w.y, w.z );
b3Dump( " bd.linearDamping = %.9g;\n", sim->linearDamping );
b3Dump( " bd.angularDamping = %.9g;\n", sim->angularDamping );
b3Dump( " bd.enableSleep = bool(%d);\n", body->flags & b3_enableSleep );
b3Dump( " bd.isAwake = bool(%d);\n", body->setIndex == b3_awakeSet );
b3Dump( " bd.gravityScale = %.9g;\n", sim->gravityScale );
b3Dump( " b3BodyId bodyId = b3CreateBody(m_worldId, &bd);\n" );
b3Dump( " bodies.push_back(bodyId);\n" );
b3Dump( "\n" );
int shapeIndex = body->headShapeId;
while ( shapeIndex != B3_NULL_INDEX )
{
b3Dump( " {\n" );
b3DumpShape( world, shapeIndex );
b3Dump( " }\n" );
b3Shape* shape = b3Array_Get( world->shapes, shapeIndex );
shapeIndex = shape->nextShapeId;
}
b3Dump( "}\n" );
}
b3Pos b3Body_GetPosition( b3BodyId bodyId )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
b3WorldTransform transform = b3GetBodyTransformQuick( world, body );
return transform.p;
}
b3Quat b3Body_GetRotation( b3BodyId bodyId )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
b3WorldTransform transform = b3GetBodyTransformQuick( world, body );
return transform.q;
}
b3WorldTransform b3Body_GetTransform( b3BodyId bodyId )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
return b3GetBodyTransformQuick( world, body );
}
b3Vec3 b3Body_GetLocalPoint( b3BodyId bodyId, b3Pos worldPoint )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
b3WorldTransform transform = b3GetBodyTransformQuick( world, body );
return b3InvTransformWorldPoint( transform, worldPoint );
}
b3Pos b3Body_GetWorldPoint( b3BodyId bodyId, b3Vec3 localPoint )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
b3WorldTransform transform = b3GetBodyTransformQuick( world, body );
return b3TransformWorldPoint( transform, localPoint );
}
b3Vec3 b3Body_GetLocalVector( b3BodyId bodyId, b3Vec3 worldVector )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
b3WorldTransform transform = b3GetBodyTransformQuick( world, body );
return b3InvRotateVector( transform.q, worldVector );
}
b3Vec3 b3Body_GetWorldVector( b3BodyId bodyId, b3Vec3 localVector )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
b3WorldTransform transform = b3GetBodyTransformQuick( world, body );
return b3RotateVector( transform.q, localVector );
}
void b3Body_SetTransform( b3BodyId bodyId, b3Pos position, b3Quat rotation )
{
B3_ASSERT( b3IsValidPosition( position ) );
B3_ASSERT( b3IsValidQuat( rotation ) );
B3_ASSERT( b3Body_IsValid( bodyId ) );
b3World* world = b3GetWorld( bodyId.world0 );
B3_ASSERT( world->locked == false );
B3_REC( world, BodySetTransform, bodyId, position, rotation );
b3Body* body = b3GetBodyFullId( world, bodyId );
b3BodySim* bodySim = b3GetBodySim( world, body );
bodySim->transform.p = position;
bodySim->transform.q = rotation;
bodySim->center = b3TransformWorldPoint( bodySim->transform, bodySim->localCenter );
b3Matrix3 rotationMatrix = b3MakeMatrixFromQuat( bodySim->transform.q );
bodySim->invInertiaWorld = b3MulMM( b3MulMM( rotationMatrix, bodySim->invInertiaLocal ), b3Transpose( rotationMatrix ) );
bodySim->rotation0 = bodySim->transform.q;
bodySim->center0 = bodySim->center;
b3BroadPhase* broadPhase = &world->broadPhase;
b3WorldTransform transform = bodySim->transform;
const float speculativeDistance = B3_SPECULATIVE_DISTANCE;
int shapeId = body->headShapeId;
while ( shapeId != B3_NULL_INDEX )
{
b3Shape* shape = b3Array_Get( world->shapes, shapeId );
b3AABB aabb = b3ComputeFatShapeAABB( shape, transform, speculativeDistance );
shape->aabb = aabb;
if ( b3AABB_Contains( shape->fatAABB, aabb ) == false )
{
float margin = shape->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;
if ( shape->proxyKey != B3_NULL_INDEX )
{
b3BroadPhase_MoveProxy( broadPhase, shape->proxyKey, fatAABB );
}
}
shapeId = shape->nextShapeId;
}
}
b3Vec3 b3Body_GetLinearVelocity( b3BodyId bodyId )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
b3BodyState* state = b3GetBodyState( world, body );
if ( state != NULL )
{
return state->linearVelocity;
}
return b3Vec3_zero;
}
b3Vec3 b3Body_GetAngularVelocity( b3BodyId bodyId )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
b3BodyState* state = b3GetBodyState( world, body );
if ( state != NULL )
{
return state->angularVelocity;
}
return b3Vec3_zero;
}
void b3Body_SetLinearVelocity( b3BodyId bodyId, b3Vec3 linearVelocity )
{
B3_ASSERT( b3IsValidVec3( linearVelocity ) );
b3World* world = b3GetWorld( bodyId.world0 );
B3_REC( world, BodySetLinearVelocity, bodyId, linearVelocity );
b3Body* body = b3GetBodyFullId( world, bodyId );
if ( body->type == b3_staticBody )
{
return;
}
if ( b3LengthSquared( linearVelocity ) > 0.0f )
{
b3WakeBodyWithLock( world, body );
}
b3BodyState* state = b3GetBodyState( world, body );
if ( state == NULL )
{
return;
}
state->linearVelocity = linearVelocity;
}
void b3Body_SetAngularVelocity( b3BodyId bodyId, b3Vec3 angularVelocity )
{
B3_ASSERT( b3IsValidVec3( angularVelocity ) );
b3World* world = b3GetWorld( bodyId.world0 );
B3_REC( world, BodySetAngularVelocity, bodyId, angularVelocity );
b3Body* body = b3GetBodyFullId( world, bodyId );
if ( body->type == b3_staticBody )
{
return;
}
b3Vec3 w;
w.x = ( body->flags & b3_lockAngularX ) ? 0.0f : angularVelocity.x;
w.y = ( body->flags & b3_lockAngularY ) ? 0.0f : angularVelocity.y;
w.z = ( body->flags & b3_lockAngularZ ) ? 0.0f : angularVelocity.z;
if ( b3LengthSquared( w ) != 0.0f )
{
b3WakeBodyWithLock( world, body );
}
b3BodyState* state = b3GetBodyState( world, body );
if ( state == NULL )
{
return;
}
state->angularVelocity = w;
}
void b3Body_SetTargetTransform( b3BodyId bodyId, b3WorldTransform target, float timeStep, bool wake )
{
B3_ASSERT( b3IsValidWorldTransform( target ) );
b3World* world = b3GetWorld( bodyId.world0 );
B3_REC( world, BodySetTargetTransform, bodyId, target, timeStep, wake );
b3Body* body = b3GetBodyFullId( world, bodyId );
if ( body->setIndex == b3_disabledSet )
{
return;
}
if ( body->type == b3_staticBody || timeStep <= 0.0f )
{
return;
}
if ( body->setIndex != b3_awakeSet && wake == false )
{
return;
}
b3BodySim* sim = b3GetBodySim( world, body );
b3Pos center1 = sim->center;
b3Pos center2 = b3TransformWorldPoint( target, sim->localCenter );
float invTimeStep = 1.0f / timeStep;
b3Vec3 linearVelocity = b3MulSV( invTimeStep, b3SubPos( center2, center1 ) );
b3Quat q1 = sim->transform.q;
b3Quat q2 = target.q;
if ( b3DotQuat( q1, q2 ) < 0.0f )
{
q2 = b3NegateQuat( q2 );
}
b3Quat dq = { b3Sub( q2.v, q1.v ), q2.s - q1.s };
b3Quat omega = b3MulQuat( dq, b3Conjugate( q1 ) );
b3Vec3 angularVelocity = b3MulSV( 2.0f * invTimeStep, omega.v );
if ( body->setIndex != b3_awakeSet )
{
float maxVelocity = b3Length( linearVelocity ) + b3Length( b3Mul( angularVelocity, sim->maxExtent ) );
if ( maxVelocity < body->sleepThreshold )
{
return;
}
b3WakeBodyWithLock( world, body );
}
B3_ASSERT( body->setIndex == b3_awakeSet );
b3BodyState* state = b3GetBodyState( world, body );
state->linearVelocity = linearVelocity;
state->angularVelocity = angularVelocity;
}
b3Vec3 b3Body_GetLocalPointVelocity( b3BodyId bodyId, b3Vec3 localPoint )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
b3BodyState* state = b3GetBodyState( world, body );
if ( state == NULL )
{
return b3Vec3_zero;
}
b3SolverSet* set = b3Array_Get( world->solverSets, body->setIndex );
b3BodySim* bodySim = b3Array_Get( set->bodySims, body->localIndex );
b3Vec3 r = b3RotateVector( bodySim->transform.q, b3Sub( localPoint, bodySim->localCenter ) );
b3Vec3 v = b3Add( state->linearVelocity, b3Cross( state->angularVelocity, r ) );
return v;
}
b3Vec3 b3Body_GetWorldPointVelocity( b3BodyId bodyId, b3Pos worldPoint )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
b3BodyState* state = b3GetBodyState( world, body );
if ( state == NULL )
{
return b3Vec3_zero;
}
b3SolverSet* set = b3Array_Get( world->solverSets, body->setIndex );
b3BodySim* bodySim = b3Array_Get( set->bodySims, body->localIndex );
b3Vec3 r = b3SubPos( worldPoint, bodySim->center );
b3Vec3 v = b3Add( state->linearVelocity, b3Cross( state->angularVelocity, r ) );
return v;
}
void b3Body_ApplyForce( b3BodyId bodyId, b3Vec3 force, b3Pos point, bool wake )
{
B3_ASSERT( b3IsValidVec3( force ) );
b3World* world = b3GetWorld( bodyId.world0 );
B3_REC( world, BodyApplyForce, bodyId, force, point, wake );
b3Body* body = b3GetBodyFullId( world, bodyId );
if ( wake && body->setIndex >= b3_firstSleepingSet )
{
b3WakeBodyWithLock( world, body );
}
if ( body->setIndex == b3_awakeSet )
{
b3BodySim* bodySim = b3GetBodySim( world, body );
bodySim->force = b3Add( bodySim->force, force );
bodySim->torque = b3Add( bodySim->torque, b3Cross( b3SubPos( point, bodySim->center ), force ) );
}
}
void b3Body_ApplyForceToCenter( b3BodyId bodyId, b3Vec3 force, bool wake )
{
B3_ASSERT( b3IsValidVec3( force ) );
b3World* world = b3GetWorld( bodyId.world0 );
B3_REC( world, BodyApplyForceToCenter, bodyId, force, wake );
b3Body* body = b3GetBodyFullId( world, bodyId );
if ( wake && body->setIndex >= b3_firstSleepingSet )
{
b3WakeBodyWithLock( world, body );
}
if ( body->setIndex == b3_awakeSet )
{
b3BodySim* bodySim = b3GetBodySim( world, body );
bodySim->force = b3Add( bodySim->force, force );
}
}
void b3Body_ApplyTorque( b3BodyId bodyId, b3Vec3 torque, bool wake )
{
B3_ASSERT( b3IsValidVec3( torque ) );
b3World* world = b3GetWorld( bodyId.world0 );
B3_REC( world, BodyApplyTorque, bodyId, torque, wake );
b3Body* body = b3GetBodyFullId( world, bodyId );
if ( wake && body->setIndex >= b3_firstSleepingSet )
{
b3WakeBodyWithLock( world, body );
}
if ( body->setIndex == b3_awakeSet )
{
b3BodySim* bodySim = b3GetBodySim( world, body );
bodySim->torque = b3Add( bodySim->torque, torque );
}
}
void b3Body_ApplyLinearImpulse( b3BodyId bodyId, b3Vec3 impulse, b3Pos point, bool wake )
{
B3_ASSERT( b3IsValidVec3( impulse ) );
B3_ASSERT( b3IsValidPosition( point ) );
b3World* world = b3GetWorld( bodyId.world0 );
B3_REC( world, BodyApplyLinearImpulse, bodyId, impulse, point, wake );
b3Body* body = b3GetBodyFullId( world, bodyId );
if ( wake && body->setIndex >= b3_firstSleepingSet )
{
b3WakeBodyWithLock( world, body );
}
if ( body->setIndex == b3_awakeSet )
{
int localIndex = body->localIndex;
b3SolverSet* set = b3Array_Get( world->solverSets, b3_awakeSet );
b3BodyState* state = b3Array_Get( set->bodyStates, localIndex );
b3BodySim* bodySim = b3Array_Get( set->bodySims, localIndex );
state->linearVelocity = b3MulAdd( state->linearVelocity, bodySim->invMass, impulse );
float maxLinearSpeed = world->maxLinearSpeed;
if ( b3LengthSquared( state->linearVelocity ) > maxLinearSpeed * maxLinearSpeed )
{
state->linearVelocity = b3MulSV( maxLinearSpeed, b3Normalize( state->linearVelocity ) );
}
b3Vec3 delta = b3MulMV( bodySim->invInertiaWorld, b3Cross( b3SubPos( point, bodySim->center ), impulse ) );
state->angularVelocity = b3Add( state->angularVelocity, delta );
}
}
void b3Body_ApplyLinearImpulseToCenter( b3BodyId bodyId, b3Vec3 impulse, bool wake )
{
B3_ASSERT( b3IsValidVec3( impulse ) );
b3World* world = b3GetWorld( bodyId.world0 );
B3_REC( world, BodyApplyLinearImpulseToCenter, bodyId, impulse, wake );
b3Body* body = b3GetBodyFullId( world, bodyId );
if ( wake && body->setIndex >= b3_firstSleepingSet )
{
b3WakeBodyWithLock( world, body );
}
if ( body->setIndex == b3_awakeSet )
{
int localIndex = body->localIndex;
b3SolverSet* set = b3Array_Get( world->solverSets, b3_awakeSet );
b3BodyState* state = b3Array_Get( set->bodyStates, localIndex );
b3BodySim* bodySim = b3Array_Get( set->bodySims, localIndex );
state->linearVelocity = b3MulAdd( state->linearVelocity, bodySim->invMass, impulse );
float maxLinearSpeed = world->maxLinearSpeed;
if ( b3LengthSquared( state->linearVelocity ) > maxLinearSpeed * maxLinearSpeed )
{
state->linearVelocity = b3MulSV( maxLinearSpeed, b3Normalize( state->linearVelocity ) );
}
}
}
void b3Body_ApplyAngularImpulse( b3BodyId bodyId, b3Vec3 impulse, bool wake )
{
B3_ASSERT( b3IsValidVec3( impulse ) );
B3_ASSERT( b3Body_IsValid( bodyId ) );
b3World* world = b3GetWorld( bodyId.world0 );
B3_REC( world, BodyApplyAngularImpulse, bodyId, impulse, wake );
int id = bodyId.index1 - 1;
b3Body* body = b3Array_Get( world->bodies, id );
B3_ASSERT( body->generation == bodyId.generation );
if ( wake && body->setIndex >= b3_firstSleepingSet )
{
b3WakeBodyWithLock( world, body );
}
if ( body->setIndex == b3_awakeSet )
{
int localIndex = body->localIndex;
b3SolverSet* set = b3Array_Get( world->solverSets, b3_awakeSet );
b3BodyState* state = b3Array_Get( set->bodyStates, localIndex );
b3BodySim* bodySim = b3Array_Get( set->bodySims, localIndex );
b3Vec3 localImpulse = b3InvRotateVector( bodySim->transform.q, impulse );
b3Vec3 localAngularVelocityDelta = b3MulMV( bodySim->invInertiaLocal, localImpulse );
state->angularVelocity =
b3Add( state->angularVelocity, b3RotateVector( bodySim->transform.q, localAngularVelocityDelta ) );
}
}
b3BodyType b3Body_GetType( b3BodyId bodyId )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
return body->type;
}
void b3Body_SetType( b3BodyId bodyId, b3BodyType type )
{
b3World* world = b3GetUnlockedWorld( bodyId.world0 );
if ( world == NULL )
{
return;
}
B3_REC( world, BodySetType, bodyId, (int32_t)type );
world->locked = true;
b3Body* body = b3GetBodyFullId( world, bodyId );
b3BodyType originalType = body->type;
if ( originalType == type )
{
world->locked = false;
return;
}
if ( type != b3_staticBody )
{
int shapeId = body->headShapeId;
while ( shapeId != B3_NULL_INDEX )
{
b3Shape* shape = b3Array_Get( world->shapes, shapeId );
if ( shape->type == b3_compoundShape || shape->type == b3_heightShape )
{
return;
}
shapeId = shape->nextShapeId;
}
}
if ( body->setIndex == b3_disabledSet )
{
body->type = type;
if ( type == b3_dynamicBody )
{
body->flags |= b3_dynamicFlag;
}
else
{
body->flags &= ~b3_dynamicFlag;
}
b3SyncBodyFlags( world, body );
b3UpdateBodyMassData( world, body );
world->locked = false;
return;
}
bool wakeBodies = false;
b3DestroyBodyContacts( world, body, wakeBodies );
b3WakeBody( world, body );
b3SolverSet* staticSet = b3Array_Get( world->solverSets, b3_staticSet );
int jointKey = body->headJointKey;
while ( jointKey != B3_NULL_INDEX )
{
int jointId = jointKey >> 1;
int edgeIndex = jointKey & 1;
b3Joint* joint = b3Array_Get( world->joints, jointId );
jointKey = joint->edges[edgeIndex].nextKey;
if ( joint->setIndex == b3_disabledSet )
{
continue;
}
b3Body* bodyA = b3Array_Get( world->bodies, joint->edges[0].bodyId );
b3Body* bodyB = b3Array_Get( world->bodies, joint->edges[1].bodyId );
b3WakeBody( world, bodyA );
b3WakeBody( world, bodyB );
b3UnlinkJoint( world, joint );
b3SolverSet* jointSourceSet = b3Array_Get( world->solverSets, joint->setIndex );
b3TransferJoint( world, staticSet, jointSourceSet, joint );
}
body->type = type;
if ( type == b3_dynamicBody )
{
body->flags |= b3_dynamicFlag;
}
else
{
body->flags &= ~b3_dynamicFlag;
}
b3SolverSet* awakeSet = b3Array_Get( world->solverSets, b3_awakeSet );
b3SolverSet* sourceSet = b3Array_Get( world->solverSets, body->setIndex );
b3SolverSet* targetSet = type == b3_staticBody ? staticSet : awakeSet;
b3TransferBody( world, targetSet, sourceSet, body );
if ( originalType == b3_staticBody )
{
b3CreateIslandForBody( world, b3_awakeSet, body );
}
else if ( type == b3_staticBody )
{
b3RemoveBodyFromIsland( world, body );
}
jointKey = body->headJointKey;
while ( jointKey != B3_NULL_INDEX )
{
int jointId = jointKey >> 1;
int edgeIndex = jointKey & 1;
b3Joint* joint = b3Array_Get( world->joints, jointId );
jointKey = joint->edges[edgeIndex].nextKey;
if ( joint->setIndex == b3_disabledSet )
{
continue;
}
B3_ASSERT( joint->setIndex == b3_staticSet );
b3Body* bodyA = b3Array_Get( world->bodies, joint->edges[0].bodyId );
b3Body* bodyB = b3Array_Get( world->bodies, joint->edges[1].bodyId );
B3_ASSERT( bodyA->setIndex == b3_staticSet || bodyA->setIndex == b3_awakeSet );
B3_ASSERT( bodyB->setIndex == b3_staticSet || bodyB->setIndex == b3_awakeSet );
if ( bodyA->type == b3_dynamicBody || bodyB->type == b3_dynamicBody )
{
b3TransferJoint( world, awakeSet, staticSet, joint );
}
}
b3WorldTransform transform = b3GetBodyTransformQuick( world, body );
int shapeId = body->headShapeId;
while ( shapeId != B3_NULL_INDEX )
{
b3Shape* shape = b3Array_Get( world->shapes, shapeId );
B3_ASSERT( shape->type != b3_compoundShape );
shapeId = shape->nextShapeId;
b3DestroyShapeProxy( shape, &world->broadPhase );
bool forcePairCreation = true;
b3CreateShapeProxy( shape, &world->broadPhase, type, transform, forcePairCreation );
}
jointKey = body->headJointKey;
while ( jointKey != B3_NULL_INDEX )
{
int jointId = jointKey >> 1;
int edgeIndex = jointKey & 1;
b3Joint* joint = b3Array_Get( world->joints, jointId );
jointKey = joint->edges[edgeIndex].nextKey;
int otherEdgeIndex = edgeIndex ^ 1;
int otherBodyId = joint->edges[otherEdgeIndex].bodyId;
b3Body* otherBody = b3Array_Get( world->bodies, otherBodyId );
if ( otherBody->setIndex == b3_disabledSet )
{
continue;
}
if ( body->type != b3_dynamicBody && otherBody->type != b3_dynamicBody )
{
continue;
}
b3LinkJoint( world, joint );
}
b3SyncBodyFlags( world, body );
b3UpdateBodyMassData( world, body );
b3ValidateSolverSets( world );
b3ValidateIsland( world, body->islandId );
world->locked = false;
}
void b3Body_SetName( b3BodyId bodyId, const char* name )
{
b3World* world = b3GetWorld( bodyId.world0 );
B3_REC( world, BodySetName, bodyId, name );
b3Body* body = b3GetBodyFullId( world, bodyId );
if ( name )
{
#if defined( _MSC_VER )
strncpy_s( body->name, B3_BODY_NAME_LENGTH + 1, name, B3_BODY_NAME_LENGTH );
#else
strncpy( body->name, name, B3_BODY_NAME_LENGTH );
body->name[B3_BODY_NAME_LENGTH] = 0;
#endif
}
else
{
memset( body->name, 0, sizeof( body->name ) );
}
}
const char* b3Body_GetName( b3BodyId bodyId )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
return body->name;
}
void b3Body_SetUserData( b3BodyId bodyId, void* userData )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
body->userData = userData;
}
void* b3Body_GetUserData( b3BodyId bodyId )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
return body->userData;
}
float b3Body_GetMass( b3BodyId bodyId )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
return body->mass;
}
b3Matrix3 b3Body_GetLocalRotationalInertia( b3BodyId bodyId )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
return body->inertia;
}
float b3Body_GetInverseMass( b3BodyId bodyId )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
b3BodySim* sim = b3GetBodySim( world, body );
return sim->invMass;
}
b3Matrix3 b3Body_GetWorldInverseRotationalInertia( b3BodyId bodyId )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
b3BodySim* sim = b3GetBodySim( world, body );
return sim->invInertiaWorld;
}
b3Vec3 b3Body_GetLocalCenterOfMass( b3BodyId bodyId )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
b3BodySim* bodySim = b3GetBodySim( world, body );
return bodySim->localCenter;
}
b3Pos b3Body_GetWorldCenterOfMass( b3BodyId bodyId )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
b3BodySim* bodySim = b3GetBodySim( world, body );
return bodySim->center;
}
void b3Body_SetMassData( b3BodyId bodyId, b3MassData massData )
{
B3_ASSERT( b3IsValidFloat( massData.mass ) && massData.mass >= 0.0f );
B3_ASSERT( b3IsValidMatrix3( massData.inertia ) );
B3_ASSERT( b3IsValidVec3( massData.center ) );
b3World* world = b3GetUnlockedWorld( bodyId.world0 );
if ( world == NULL )
{
return;
}
B3_REC( world, BodySetMassData, bodyId, massData );
b3Body* body = b3GetBodyFullId( world, bodyId );
b3BodySim* bodySim = b3GetBodySim( world, body );
body->mass = massData.mass;
body->inertia = massData.inertia;
bodySim->localCenter = massData.center;
b3Pos center = b3TransformWorldPoint( bodySim->transform, massData.center );
bodySim->center = center;
bodySim->center0 = center;
bodySim->invMass = body->mass > 0.0f ? 1.0f / body->mass : 0.0f;
bodySim->invInertiaLocal = b3Det( body->inertia ) > 0.0f ? b3InvertT( body->inertia ) : b3Mat3_zero;
}
b3MassData b3Body_GetMassData( b3BodyId bodyId )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
b3BodySim* bodySim = b3GetBodySim( world, body );
b3MassData massData = { body->mass, bodySim->localCenter, body->inertia };
return massData;
}
void b3Body_ApplyMassFromShapes( b3BodyId bodyId )
{
b3World* world = b3GetUnlockedWorld( bodyId.world0 );
if ( world == NULL )
{
return;
}
B3_REC( world, BodyApplyMassFromShapes, bodyId );
b3Body* body = b3GetBodyFullId( world, bodyId );
b3UpdateBodyMassData( world, body );
}
void b3Body_SetLinearDamping( b3BodyId bodyId, float linearDamping )
{
B3_ASSERT( b3IsValidFloat( linearDamping ) && linearDamping >= 0.0f );
b3World* world = b3GetUnlockedWorld( bodyId.world0 );
if ( world == NULL )
{
return;
}
B3_REC( world, BodySetLinearDamping, bodyId, linearDamping );
b3Body* body = b3GetBodyFullId( world, bodyId );
b3BodySim* bodySim = b3GetBodySim( world, body );
bodySim->linearDamping = linearDamping;
}
float b3Body_GetLinearDamping( b3BodyId bodyId )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
b3BodySim* bodySim = b3GetBodySim( world, body );
return bodySim->linearDamping;
}
void b3Body_SetAngularDamping( b3BodyId bodyId, float angularDamping )
{
B3_ASSERT( b3IsValidFloat( angularDamping ) && angularDamping >= 0.0f );
b3World* world = b3GetUnlockedWorld( bodyId.world0 );
if ( world == NULL )
{
return;
}
B3_REC( world, BodySetAngularDamping, bodyId, angularDamping );
b3Body* body = b3GetBodyFullId( world, bodyId );
b3BodySim* bodySim = b3GetBodySim( world, body );
bodySim->angularDamping = angularDamping;
}
float b3Body_GetAngularDamping( b3BodyId bodyId )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
b3BodySim* bodySim = b3GetBodySim( world, body );
return bodySim->angularDamping;
}
void b3Body_SetGravityScale( b3BodyId bodyId, float gravityScale )
{
B3_ASSERT( b3Body_IsValid( bodyId ) );
B3_ASSERT( b3IsValidFloat( gravityScale ) );
b3World* world = b3GetUnlockedWorld( bodyId.world0 );
if ( world == NULL )
{
return;
}
B3_REC( world, BodySetGravityScale, bodyId, gravityScale );
b3Body* body = b3GetBodyFullId( world, bodyId );
b3BodySim* bodySim = b3GetBodySim( world, body );
bodySim->gravityScale = gravityScale;
}
float b3Body_GetGravityScale( b3BodyId bodyId )
{
B3_ASSERT( b3Body_IsValid( bodyId ) );
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
b3BodySim* bodySim = b3GetBodySim( world, body );
return bodySim->gravityScale;
}
bool b3Body_IsAwake( b3BodyId bodyId )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
return body->setIndex == b3_awakeSet;
}
void b3Body_SetAwake( b3BodyId bodyId, bool awake )
{
b3World* world = b3GetUnlockedWorld( bodyId.world0 );
if ( world == NULL )
{
return;
}
B3_REC( world, BodySetAwake, bodyId, awake );
world->locked = true;
b3Body* body = b3GetBodyFullId( world, bodyId );
if ( awake && body->setIndex >= b3_firstSleepingSet )
{
b3WakeBody( world, body );
}
else if ( awake == false && body->setIndex == b3_awakeSet )
{
b3Island* island = b3Array_Get( world->islands, body->islandId );
if ( island->constraintRemoveCount > 0 )
{
b3SplitIsland( world, body->islandId );
}
b3TrySleepIsland( world, body->islandId );
}
world->locked = false;
}
bool b3Body_IsEnabled( b3BodyId bodyId )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
return body->setIndex != b3_disabledSet;
}
bool b3Body_IsSleepEnabled( b3BodyId bodyId )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
return ( body->flags & b3_enableSleep ) == b3_enableSleep;
}
void b3Body_SetSleepThreshold( b3BodyId bodyId, float sleepThreshold )
{
b3World* world = b3GetWorld( bodyId.world0 );
B3_REC( world, BodySetSleepThreshold, bodyId, sleepThreshold );
b3Body* body = b3GetBodyFullId( world, bodyId );
body->sleepThreshold = sleepThreshold;
}
float b3Body_GetSleepThreshold( b3BodyId bodyId )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
return body->sleepThreshold;
}
void b3Body_EnableSleep( b3BodyId bodyId, bool enableSleep )
{
b3World* world = b3GetUnlockedWorld( bodyId.world0 );
if ( world == NULL )
{
return;
}
B3_REC( world, BodyEnableSleep, bodyId, enableSleep );
b3Body* body = b3GetBodyFullId( world, bodyId );
bool flag = ( body->flags & b3_enableSleep ) == b3_enableSleep;
if ( enableSleep == flag )
{
return;
}
world->locked = true;
body->flags = enableSleep ? body->flags | b3_enableSleep : body->flags & ~b3_enableSleep;
b3SyncBodyFlags( world, body );
if ( enableSleep == false )
{
b3WakeBody( world, body );
}
world->locked = false;
}
void b3Body_Disable( b3BodyId bodyId )
{
b3World* world = b3GetUnlockedWorld( bodyId.world0 );
if ( world == NULL )
{
return;
}
B3_REC( world, BodyDisable, bodyId );
world->locked = true;
b3Body* body = b3GetBodyFullId( world, bodyId );
if ( body->setIndex == b3_disabledSet )
{
world->locked = false;
return;
}
bool wakeBodies = true;
b3DestroyBodyContacts( world, body, wakeBodies );
b3SolverSet* set = b3Array_Get( world->solverSets, body->setIndex );
b3SolverSet* disabledSet = b3Array_Get( world->solverSets, b3_disabledSet );
int jointKey = body->headJointKey;
while ( jointKey != B3_NULL_INDEX )
{
int jointId = jointKey >> 1;
int edgeIndex = jointKey & 1;
b3Joint* joint = b3Array_Get( world->joints, jointId );
jointKey = joint->edges[edgeIndex].nextKey;
if ( joint->setIndex == b3_disabledSet )
{
continue;
}
B3_ASSERT( joint->setIndex == set->setIndex || set->setIndex == b3_staticSet );
b3UnlinkJoint( world, joint );
b3SolverSet* jointSet = b3Array_Get( world->solverSets, joint->setIndex );
b3TransferJoint( world, disabledSet, jointSet, joint );
}
int shapeId = body->headShapeId;
while ( shapeId != B3_NULL_INDEX )
{
b3Shape* shape = b3Array_Get( world->shapes, shapeId );
shapeId = shape->nextShapeId;
b3DestroyShapeProxy( shape, &world->broadPhase );
}
b3RemoveBodyFromIsland( world, body );
b3TransferBody( world, disabledSet, set, body );
b3ValidateConnectivity( world );
b3ValidateSolverSets( world );
world->locked = false;
}
void b3Body_Enable( b3BodyId bodyId )
{
b3World* world = b3GetUnlockedWorld( bodyId.world0 );
if ( world == NULL )
{
return;
}
B3_REC( world, BodyEnable, bodyId );
b3Body* body = b3GetBodyFullId( world, bodyId );
if ( body->setIndex != b3_disabledSet )
{
return;
}
b3SolverSet* disabledSet = b3Array_Get( world->solverSets, b3_disabledSet );
int setId = body->type == b3_staticBody ? b3_staticSet : b3_awakeSet;
b3SolverSet* targetSet = b3Array_Get( world->solverSets, setId );
b3TransferBody( world, targetSet, disabledSet, body );
b3WorldTransform transform = b3GetBodyTransformQuick( world, body );
b3BodyType proxyType = body->type;
bool forcePairCreation = true;
int shapeId = body->headShapeId;
while ( shapeId != B3_NULL_INDEX )
{
b3Shape* shape = b3Array_Get( world->shapes, shapeId );
shapeId = shape->nextShapeId;
b3CreateShapeProxy( shape, &world->broadPhase, proxyType, transform, forcePairCreation );
}
if ( setId != b3_staticSet )
{
b3CreateIslandForBody( world, setId, body );
}
int jointKey = body->headJointKey;
while ( jointKey != B3_NULL_INDEX )
{
int jointId = jointKey >> 1;
int edgeIndex = jointKey & 1;
b3Joint* joint = b3Array_Get( world->joints, jointId );
B3_ASSERT( joint->setIndex == b3_disabledSet );
B3_ASSERT( joint->islandId == B3_NULL_INDEX );
jointKey = joint->edges[edgeIndex].nextKey;
b3Body* bodyA = b3Array_Get( world->bodies, joint->edges[0].bodyId );
b3Body* bodyB = b3Array_Get( world->bodies, joint->edges[1].bodyId );
if ( bodyA->setIndex == b3_disabledSet || bodyB->setIndex == b3_disabledSet )
{
continue;
}
int jointSetId;
if ( bodyA->setIndex == b3_staticSet && bodyB->setIndex == b3_staticSet )
{
jointSetId = b3_staticSet;
}
else if ( bodyA->setIndex == b3_staticSet )
{
jointSetId = bodyB->setIndex;
}
else
{
jointSetId = bodyA->setIndex;
}
b3SolverSet* jointSet = b3Array_Get( world->solverSets, jointSetId );
b3TransferJoint( world, jointSet, disabledSet, joint );
if ( jointSetId != b3_staticSet )
{
b3LinkJoint( world, joint );
}
}
b3ValidateSolverSets( world );
}
void b3Body_SetMotionLocks( b3BodyId bodyId, b3MotionLocks locks )
{
b3World* world = b3GetUnlockedWorld( bodyId.world0 );
if ( world == NULL )
{
return;
}
B3_REC( world, BodySetMotionLocks, bodyId, locks );
uint32_t newLocks = 0;
newLocks |= locks.linearX ? b3_lockLinearX : 0;
newLocks |= locks.linearY ? b3_lockLinearY : 0;
newLocks |= locks.linearZ ? b3_lockLinearZ : 0;
newLocks |= locks.angularX ? b3_lockAngularX : 0;
newLocks |= locks.angularY ? b3_lockAngularY : 0;
newLocks |= locks.angularZ ? b3_lockAngularZ : 0;
b3Body* body = b3GetBodyFullId( world, bodyId );
if ( ( body->flags & b3_allLocks ) == newLocks )
{
return;
}
bool fixedRotation1 = ( body->flags & b3_fixedRotation ) == b3_fixedRotation;
bool fixedRotation2 = ( newLocks & b3_fixedRotation ) == b3_fixedRotation;
body->flags &= ~b3_allLocks;
body->flags |= newLocks;
b3SyncBodyFlags( world, body );
b3BodyState* state = b3GetBodyState( world, body );
if ( state != NULL )
{
if ( locks.linearX )
{
state->linearVelocity.x = 0.0f;
}
if ( locks.linearY )
{
state->linearVelocity.y = 0.0f;
}
if ( locks.linearZ )
{
state->linearVelocity.z = 0.0f;
}
if ( locks.angularX )
{
state->angularVelocity.x = 0.0f;
}
if ( locks.angularY )
{
state->angularVelocity.y = 0.0f;
}
if ( locks.angularZ )
{
state->angularVelocity.z = 0.0f;
}
}
if ( fixedRotation1 != fixedRotation2 )
{
b3UpdateBodyMassData( world, body );
}
}
b3MotionLocks b3Body_GetMotionLocks( b3BodyId bodyId )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
b3MotionLocks locks;
locks.linearX = ( body->flags & b3_lockLinearX );
locks.linearY = ( body->flags & b3_lockLinearY );
locks.linearZ = ( body->flags & b3_lockLinearZ );
locks.angularX = ( body->flags & b3_lockAngularX );
locks.angularY = ( body->flags & b3_lockAngularY );
locks.angularZ = ( body->flags & b3_lockAngularZ );
return locks;
}
void b3Body_SetBullet( b3BodyId bodyId, bool flag )
{
b3World* world = b3GetUnlockedWorld( bodyId.world0 );
if ( world == NULL )
{
return;
}
B3_REC( world, BodySetBullet, bodyId, flag );
uint32_t newFlag = flag ? b3_isBullet : 0;
b3Body* body = b3GetBodyFullId( world, bodyId );
if ( ( body->flags & b3_isBullet ) == newFlag )
{
return;
}
body->flags &= ~b3_isBullet;
body->flags |= newFlag;
b3SyncBodyFlags( world, body );
}
bool b3Body_IsBullet( b3BodyId bodyId )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
return ( body->flags & b3_isBullet ) != 0;
}
void b3Body_EnableContactRecycling( b3BodyId bodyId, bool flag )
{
b3World* world = b3GetUnlockedWorld( bodyId.world0 );
if ( world == NULL )
{
return;
}
B3_REC( world, BodyEnableContactRecycling, bodyId, flag );
uint32_t newFlag = flag ? b3_bodyEnableContactRecycling : 0;
b3Body* body = b3GetBodyFullId( world, bodyId );
if ( ( body->flags & b3_bodyEnableContactRecycling ) == newFlag )
{
return;
}
body->flags &= ~b3_bodyEnableContactRecycling;
body->flags |= newFlag;
b3SyncBodyFlags( world, body );
}
bool b3Body_IsContactRecyclingEnabled( b3BodyId bodyId )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
return ( body->flags & b3_bodyEnableContactRecycling ) != 0;
}
void b3Body_EnableHitEvents( b3BodyId bodyId, bool enableHitEvents )
{
b3World* world = b3GetWorld( bodyId.world0 );
B3_REC( world, BodyEnableHitEvents, bodyId, enableHitEvents );
b3Body* body = b3GetBodyFullId( world, bodyId );
int shapeId = body->headShapeId;
while ( shapeId != B3_NULL_INDEX )
{
b3Shape* shape = b3Array_Get( world->shapes, shapeId );
shape->enableHitEvents = enableHitEvents;
shapeId = shape->nextShapeId;
}
}
b3WorldId b3Body_GetWorld( b3BodyId bodyId )
{
b3World* world = b3GetWorld( bodyId.world0 );
return (b3WorldId){ (uint16_t)( bodyId.world0 + 1 ), world->generation };
}
int b3Body_GetShapeCount( b3BodyId bodyId )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
return body->shapeCount;
}
int b3Body_GetShapes( b3BodyId bodyId, b3ShapeId* shapeArray, int capacity )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
int shapeId = body->headShapeId;
int shapeCount = 0;
while ( shapeId != B3_NULL_INDEX && shapeCount < capacity )
{
b3Shape* shape = b3Array_Get( world->shapes, shapeId );
b3ShapeId id = { shape->id + 1, bodyId.world0, shape->generation };
shapeArray[shapeCount] = id;
shapeCount += 1;
shapeId = shape->nextShapeId;
}
return shapeCount;
}
int b3Body_GetJointCount( b3BodyId bodyId )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
return body->jointCount;
}
int b3Body_GetJoints( b3BodyId bodyId, b3JointId* jointArray, int capacity )
{
b3World* world = b3GetWorld( bodyId.world0 );
b3Body* body = b3GetBodyFullId( world, bodyId );
int jointKey = body->headJointKey;
int jointCount = 0;
while ( jointKey != B3_NULL_INDEX && jointCount < capacity )
{
int jointId = jointKey >> 1;
int edgeIndex = jointKey & 1;
b3Joint* joint = b3Array_Get( world->joints, jointId );
b3JointId id = { jointId + 1, bodyId.world0, joint->generation };
jointArray[jointCount] = id;
jointCount += 1;
jointKey = joint->edges[edgeIndex].nextKey;
}
return jointCount;
}
bool b3ShouldBodiesCollide( b3World* world, b3Body* bodyA, b3Body* bodyB )
{
if ( bodyA->type != b3_dynamicBody && bodyB->type != b3_dynamicBody )
{
return false;
}
int jointKey;
int otherBodyId;
if ( bodyA->jointCount < bodyB->jointCount )
{
jointKey = bodyA->headJointKey;
otherBodyId = bodyB->id;
}
else
{
jointKey = bodyB->headJointKey;
otherBodyId = bodyA->id;
}
while ( jointKey != B3_NULL_INDEX )
{
int jointId = jointKey >> 1;
int edgeIndex = jointKey & 1;
int otherEdgeIndex = edgeIndex ^ 1;
b3Joint* joint = b3Array_Get( world->joints, jointId );
if ( joint->collideConnected == false && joint->edges[otherEdgeIndex].bodyId == otherBodyId )
{
return false;
}
jointKey = joint->edges[edgeIndex].nextKey;
}
return true;
}