Box2D-0.1.0.0: upstream/src/broad_phase.c
// SPDX-FileCopyrightText: 2026 Erin Catto
// SPDX-License-Identifier: MIT
#if defined( _MSC_VER ) && !defined( _CRT_SECURE_NO_WARNINGS )
#define _CRT_SECURE_NO_WARNINGS
#endif
#include "broad_phase.h"
#include "aabb.h"
#include "arena_allocator.h"
#include "body.h"
#include "contact.h"
#include "core.h"
#include "dynamic_tree.h"
#include "parallel_for.h"
#include "physics_world.h"
#include "platform.h"
#include "shape.h"
#include "simd.h"
#include <stdbool.h>
#include <string.h>
void b2CreateBroadPhase( b2BroadPhase* bp, const b2Capacity* capacity )
{
_Static_assert( b2_bodyTypeCount == 3, "must be three body types" );
bp->pairSet = b2CreateSet( b2MaxInt( 32, 2 * capacity->contactCount ) );
int staticCapacity = b2MaxInt( 16, capacity->staticShapeCount );
b2CreateDynamicTreeInternal( bp->trees + b2_staticBody, staticCapacity );
int kinematicCapacity = 16;
b2CreateDynamicTreeInternal( bp->trees + b2_kinematicBody, kinematicCapacity );
int dynamicCapacity = b2MaxInt( 16, capacity->dynamicShapeCount );
b2CreateDynamicTreeInternal( bp->trees + b2_dynamicBody, dynamicCapacity );
bp->movedSiblings = NULL;
}
void b2DestroyBroadPhase( b2BroadPhase* bp )
{
for ( int i = 0; i < b2_bodyTypeCount; ++i )
{
b2DestroyDynamicTreeInternal( bp->trees + i );
}
b2DestroySet( &bp->pairSet );
memset( bp, 0, sizeof( b2BroadPhase ) );
}
int b2BroadPhase_CreateProxy( b2BroadPhase* bp, b2BodyType proxyType, b2AABB aabb, uint64_t categoryBits, int shapeIndex,
bool forcePairCreation )
{
B2_ASSERT( 0 <= proxyType && proxyType < b2_bodyTypeCount );
bool mark = ( proxyType != b2_staticBody || forcePairCreation );
int proxyId = b2CreateTreeProxyInternal( bp->trees + proxyType, aabb, categoryBits, shapeIndex, mark );
int proxyKey = B2_PROXY_KEY( proxyId, proxyType );
return proxyKey;
}
void b2BroadPhase_DestroyProxy( b2BroadPhase* bp, int proxyKey )
{
b2BodyType proxyType = B2_PROXY_TYPE( proxyKey );
int proxyId = B2_PROXY_ID( proxyKey );
B2_ASSERT( 0 <= proxyType && proxyType < b2_bodyTypeCount );
b2DestroyTreeProxy( bp->trees + proxyType, proxyId );
}
void b2BroadPhase_MoveProxy( b2BroadPhase* bp, int proxyKey, b2AABB aabb )
{
b2BodyType proxyType = B2_PROXY_TYPE( proxyKey );
int proxyId = B2_PROXY_ID( proxyKey );
bool mark = true;
b2DynamicTree_MoveProxyInternal( bp->trees + proxyType, proxyId, aabb, mark );
}
// Gather the sibling pairs with a moved node. This is done serially it is cache friendly.
static int b2GatherMovedSiblings( const b2DynamicTree* tree, int* pairIndices )
{
const b2TreeNode* nodes = tree->nodes;
int nodeEnd = tree->nodeEnd;
int count = 0;
// Skip the root.
for ( int pair = 2; pair < nodeEnd; pair += 2 )
{
// Push when either sibling moved.
if ( ( nodes[pair].flagIndex | nodes[pair + 1].flagIndex ) & B2_MOVED_NODE )
{
pairIndices[count++] = pair;
}
}
return count;
}
#define B2_CANDIDATE_BATCH 32
typedef struct b2Candidate
{
int shapeIdA;
int shapeIdB;
} b2CandidatePair;
typedef struct b2PairContext
{
b2World* world;
b2Array( uint64_t ) * pairKeys;
b2CandidatePair batch[B2_CANDIDATE_BATCH];
int batchCount;
} b2PairContext;
typedef struct b2NodePair
{
b2TreeNode a;
b2TreeNode b;
} b2NodePair;
typedef struct b2IndexPair
{
int a, b;
} b2IndexPair;
static inline void b2PrefetchHash( b2HashSet* set, uint64_t hash )
{
uint32_t capacity = set->capacity;
uint32_t index = (uint32_t)hash & ( capacity - 1 );
b2Prefetch( set->items + index );
}
static void b2FlushCandidatePairs( b2PairContext* context )
{
b2World* world = context->world;
b2BroadPhase* bp = &world->broadPhase;
int count1 = context->batchCount;
context->batchCount = 0;
// Prefetch hash set entries. Less than 1% gain from this but it might
// matter more for Box3D. Also, if I switch to verstable then this might
// not be possible.
uint64_t keys[B2_CANDIDATE_BATCH];
uint64_t hashes[B2_CANDIDATE_BATCH];
for ( int i = 0; i < count1; ++i )
{
b2CandidatePair* candidate = context->batch + i;
keys[i] = B2_SHAPE_PAIR_KEY( candidate->shapeIdA, candidate->shapeIdB );
hashes[i] = b2KeyHash( keys[i] );
b2PrefetchHash( &bp->pairSet, hashes[i] );
}
// Cull existing pairs.
b2CandidatePair candidates[B2_CANDIDATE_BATCH];
int count2 = 0;
for ( int i = 0; i < count1; ++i )
{
bool pairExists = b2ContainsHashedKey( &bp->pairSet, keys[i], hashes[i] );
if ( pairExists == false )
{
candidates[count2] = context->batch[i];
count2 += 1;
}
}
// Prefetch shapes. Again, less than a 1% gain from this. Might matter more for Box3D.
const b2Shape* shapes = world->shapes.data;
for ( int i = 0; i < count2; ++i )
{
b2Prefetch( shapes + candidates[i].shapeIdA );
b2Prefetch( shapes + candidates[i].shapeIdB );
}
// Filter candidates.
b2Array( uint64_t )* pairKeys = context->pairKeys;
for ( int i = 0; i < count2; ++i )
{
int shapeIdA = candidates[i].shapeIdA;
int shapeIdB = candidates[i].shapeIdB;
b2Shape* shapeA = b2Array_Get( world->shapes, shapeIdA );
b2Shape* shapeB = b2Array_Get( world->shapes, shapeIdB );
int bodyIdA = shapeA->bodyId;
int bodyIdB = shapeB->bodyId;
// Are the shapes on the same body?
if ( bodyIdA == bodyIdB )
{
continue;
}
// Sensors are handled elsewhere
if ( shapeA->sensorIndex != B2_NULL_INDEX || shapeB->sensorIndex != B2_NULL_INDEX )
{
continue;
}
if ( b2ShouldShapesCollide( shapeA->filter, shapeB->filter ) == false )
{
continue;
}
if ( b2CanCollide( shapeA->type, shapeB->type ) == false )
{
// For example, no segment vs segment collision
continue;
}
// Does a joint override collision?
b2Body* bodyA = b2Array_Get( world->bodies, bodyIdA );
b2Body* bodyB = b2Array_Get( world->bodies, bodyIdB );
if ( b2ShouldBodiesCollide( world, bodyA, bodyB ) == false )
{
continue;
}
// Custom user filter
if ( shapeA->enableCustomFiltering || shapeB->enableCustomFiltering )
{
b2CustomFilterCallbackFcn* customFilterFcn = world->customFilterFcn;
if ( customFilterFcn != NULL )
{
b2ShapeId idA = { shapeIdA + 1, world->worldId, shapeA->generation };
b2ShapeId idB = { shapeIdB + 1, world->worldId, shapeB->generation };
bool shouldCollide = customFilterFcn( idA, idB, world->customFilterContext );
if ( shouldCollide == false )
{
continue;
}
}
}
// The pair passed the gauntlet. A new contact will be created.
b2Array_Push( *pairKeys, B2_SHAPE_PAIR_KEY( shapeIdA, shapeIdB ) );
}
}
B2_FORCE_INLINE void b2AddCandidatePair( int shapeIdA, int shapeIdB, b2PairContext* context )
{
// Follow shape index order.
b2CandidatePair* candidate = context->batch + context->batchCount;
candidate->shapeIdA = b2MinInt( shapeIdA, shapeIdB );
candidate->shapeIdB = b2MaxInt( shapeIdA, shapeIdB );
context->batchCount += 1;
if ( context->batchCount == B2_CANDIDATE_BATCH )
{
b2FlushCandidatePairs( context );
}
}
// Did either move and if so do they overlap?
B2_FORCE_INLINE bool b2TestPair( const b2TreeNode* a, const b2TreeNode* b )
{
if ( ( ( a->flagIndex | b->flagIndex ) & B2_MOVED_NODE ) == 0 )
{
return false;
}
return b2AABB_Overlaps( a->aabb, b->aabb );
}
static void b2CollideProxyAndSubtree( const b2TreeNode* proxy, const b2TreeNode* nodes, int pair, b2PairContext* context )
{
uint32_t proxyMark = proxy->flagIndex & B2_MOVED_NODE;
b2AABBV boxv = b2LoadAABBV( &proxy->aabb );
int shapeId = proxy->shapeIndex;
int stack[B2_TREE_STACK_SIZE];
int stackCount = 0;
stack[stackCount++] = pair;
while ( stackCount > 0 )
{
pair = stack[--stackCount];
const b2TreeNode* children = nodes + pair;
int movedMask = 3;
if ( proxyMark == 0 )
{
movedMask = ( b2IsNodeMoved( children + 0 ) ? 1 : 0 ) | ( b2IsNodeMoved( children + 1 ) ? 2 : 0 );
}
int mask = b2OverlapNodePairMask( boxv, children ) & movedMask;
while ( mask != 0 )
{
int i = (int)b2CTZ32( (uint32_t)mask );
mask &= mask - 1;
const b2TreeNode* node = children + i;
if ( b2IsLeaf( node ) )
{
b2AddCandidatePair( shapeId, node->shapeIndex, context );
}
else
{
if ( stackCount < B2_TREE_STACK_SIZE )
{
stack[stackCount++] = b2GetLeftChild( node );
}
else
{
B2_ASSERT( stackCount < B2_TREE_STACK_SIZE );
}
}
}
}
}
B2_FORCE_INLINE void b2ExpandPair( const b2TreeNode* arrayA, const b2TreeNode* arrayB, const b2TreeNode* nodeA,
const b2TreeNode* nodeB, b2IndexPair* stack, int* stackCount, b2PairContext* context )
{
bool leafA = b2IsLeaf( nodeA );
bool leafB = b2IsLeaf( nodeB );
if ( leafA && leafB )
{
b2AddCandidatePair( nodeA->shapeIndex, nodeB->shapeIndex, context );
}
else if ( leafA )
{
b2CollideProxyAndSubtree( nodeA, arrayB, b2GetLeftChild( nodeB ), context );
}
else if ( leafB )
{
b2CollideProxyAndSubtree( nodeB, arrayA, b2GetLeftChild( nodeA ), context );
}
else
{
if ( *stackCount < B2_TREE_STACK_SIZE )
{
stack[*stackCount] = (b2IndexPair){ .a = b2GetLeftChild( nodeA ), .b = b2GetLeftChild( nodeB ) };
*stackCount += 1;
}
else
{
B2_ASSERT( *stackCount < B2_TREE_STACK_SIZE );
}
}
}
// This collides two sub-trees against each other. They can live in the same dynamic tree.
// This can only generate pairs cross sub-tree, but not within a sub-tree. This fact means
// this does not generate duplicate pairs.
// For example consider the full binary tree A (B (D E) C (F G))
// Colliding children of A (B and C) can give pairs (D,F) (D,G) (E,F) and (E,G).
// Then colliding children of B can give the pair (D,E) and for C (F,G).
// So no duplicates even when used for self-collision.
// Whenever a proxy is moved, the flag is propagated up the hierarchy to the root. So
// self collision gathers all those moved internal nodes and collides their subtrees together.
// See Real-time collision detection section 6.3.2. This is faster than querying every moved
// proxy against the whole tree. Scaling is linear instead of linear * log.
// Many other physics engines do this (Bepu, Rapier, etc). So nothing new here.
static void b2CollideCrossPairs( const b2TreeNode* arrayA, const b2TreeNode* arrayB, const b2TreeNode* subtreeA,
const b2TreeNode* subtreeB, b2PairContext* context )
{
b2IndexPair stack[B2_TREE_STACK_SIZE];
int stackCount = 0;
// Seed the stack.
if ( b2TestPair( subtreeA, subtreeB ) )
{
b2ExpandPair( arrayA, arrayB, subtreeA, subtreeB, stack, &stackCount, context );
}
while ( stackCount > 0 )
{
b2IndexPair pair = stack[--stackCount];
const b2TreeNode* pairA = arrayA + pair.a;
const b2TreeNode* pairB = arrayB + pair.b;
int movedA = ( b2IsNodeMoved( pairA + 0 ) ? 0x3 : 0 ) | ( b2IsNodeMoved( pairA + 1 ) ? 0xC : 0 );
int movedB = ( b2IsNodeMoved( pairB + 0 ) ? 0x5 : 0 ) | ( b2IsNodeMoved( pairB + 1 ) ? 0xA : 0 );
int mask = b2OverlapPairMask( pairA, pairB ) & ( movedA | movedB );
while ( mask != 0 )
{
int lane = (int)b2CTZ32( (uint32_t)mask );
mask &= mask - 1;
b2ExpandPair( arrayA, arrayB, pairA + ( lane >> 1 ), pairB + ( lane & 1 ), stack, &stackCount, context );
}
}
}
// This takes moved internal nodes and collides their sub-trees against each other.
static void b2SelfPairsTask( int startIndex, int endIndex, int workerIndex, void* context )
{
b2TracyCZoneNC( self_pairs, "Self", b2_colorCoral, true );
b2World* world = context;
b2BroadPhase* bp = &world->broadPhase;
const b2DynamicTree* tree = bp->trees + b2_dynamicBody;
const b2TreeNode* nodes = tree->nodes;
const int* siblingIndices = bp->movedSiblings;
b2PairContext pairContext = { .world = world, .pairKeys = &world->taskContexts.data[workerIndex].pairKeys };
for ( int i = startIndex; i < endIndex; ++i )
{
int nodeIndex = siblingIndices[i];
b2CollideCrossPairs( nodes, nodes, nodes + nodeIndex, nodes + nodeIndex + 1, &pairContext );
}
b2FlushCandidatePairs( &pairContext );
b2TracyCZoneEnd( self_pairs );
}
#define B2_CROSS_SEED_COUNT 64
_Static_assert( ( B2_CROSS_SEED_COUNT & ( B2_CROSS_SEED_COUNT - 1 ) ) == 0, "must be power of 2" );
// This does a serial cross-tree breadth first search until the queue is full. Then it returns
// the queue pairs as seeds for a parallel search.
static int b2GatherCrossSeeds( const b2DynamicTree* treeA, const b2DynamicTree* treeB, b2NodePair* seeds )
{
const b2TreeNode* nodesA = treeA->nodes;
const b2TreeNode* nodesB = treeB->nodes;
// Breadth-first search from the two roots as one pair. An empty tree has a sentinel root that
// survives nothing.
b2NodePair queue[2 * B2_CROSS_SEED_COUNT];
int mask = 2 * B2_CROSS_SEED_COUNT - 1;
int head = 0;
int tail = 0;
const b2TreeNode* rootA = nodesA + B2_ROOT_NODE;
const b2TreeNode* rootB = nodesB + B2_ROOT_NODE;
if ( b2TestPair( rootA, rootB ) )
{
queue[tail & mask] = (b2NodePair){ .a = *rootA, .b = *rootB };
tail += 1;
}
int seedCount = 0;
while ( head < tail && seedCount + ( tail - head ) + 3 < B2_CROSS_SEED_COUNT )
{
b2NodePair pair = queue[head & mask];
head += 1;
if ( b2IsLeaf( &pair.a ) || b2IsLeaf( &pair.b ) )
{
seeds[seedCount++] = pair;
continue;
}
const b2TreeNode* a = nodesA + b2GetLeftChild( &pair.a );
const b2TreeNode* b = nodesB + b2GetLeftChild( &pair.b );
// Nodes have two children each, so four combinations.
for ( int i = 0; i < 2; ++i )
{
for ( int j = 0; j < 2; ++j )
{
if ( b2TestPair( a + i, b + j ) )
{
queue[tail & mask] = (b2NodePair){ a[i], b[j] };
tail += 1;
}
}
}
}
while ( head < tail )
{
seeds[seedCount] = queue[head & mask];
seedCount += 1;
head += 1;
}
return seedCount;
}
typedef struct b2CrossContext
{
b2World* world;
const b2NodePair* seeds;
int staticSeedCount;
} b2CrossContext;
static void b2CrossPairsTask( int startIndex, int endIndex, int workerIndex, void* context )
{
b2TracyCZoneNC( cross_pairs, "Cross", b2_colorCoral, true );
b2CrossContext* crossContext = context;
b2World* world = crossContext->world;
b2BroadPhase* bp = &world->broadPhase;
const b2TreeNode* staticNodes = bp->trees[b2_staticBody].nodes;
const b2TreeNode* kinematicNodes = bp->trees[b2_kinematicBody].nodes;
const b2TreeNode* dynamicNodes = bp->trees[b2_dynamicBody].nodes;
b2PairContext pairContext = { .world = world, .pairKeys = &world->taskContexts.data[workerIndex].pairKeys };
for ( int i = startIndex; i < endIndex; ++i )
{
const b2TreeNode* nodesB = i < crossContext->staticSeedCount ? staticNodes : kinematicNodes;
b2NodePair seed = crossContext->seeds[i];
b2CollideCrossPairs( dynamicNodes, nodesB, &seed.a, &seed.b, &pairContext );
}
b2FlushCandidatePairs( &pairContext );
b2TracyCZoneEnd( cross_pairs );
}
// LSD radix sort. Faster than qsort when count is large and fast enough otherwise.
// https://en.wikipedia.org/wiki/Radix_sort
void b2RadixSortKeys( uint64_t* keys, uint64_t* tempKeys, int count )
{
int digitCounts[8][256] = { 0 };
for ( int i = 0; i < count; ++i )
{
uint64_t key = keys[i];
for ( int digit = 0; digit < 8; ++digit )
{
digitCounts[digit][( key >> ( 8 * digit ) ) & 0xFF] += 1;
}
}
uint64_t* source = keys;
uint64_t* target = tempKeys;
for ( int digit = 0; digit < 8; ++digit )
{
int shift = 8 * digit;
int* offsets = digitCounts[digit];
if ( offsets[( source[0] >> shift ) & 0xFF] == count )
{
// All keys have the same value for this digit. Common for shape pair keys.
continue;
}
int sum = 0;
for ( int i = 0; i < 256; ++i )
{
int digitCount = offsets[i];
offsets[i] = sum;
sum += digitCount;
}
for ( int i = 0; i < count; ++i )
{
uint64_t key = source[i];
target[offsets[( key >> shift ) & 0xFF]++] = key;
}
// Swap for the next pass
uint64_t* swap = source;
source = target;
target = swap;
}
if ( source != keys )
{
memcpy( keys, source, count * sizeof( uint64_t ) );
}
}
static void b2UpdateTreesTask( void* context )
{
b2TracyCZoneNC( tree_task, "Rebuild BVH", b2_colorFireBrick, true );
b2World* world = context;
b2DynamicTree_Rebuild( world->broadPhase.trees + b2_dynamicBody, false );
b2DynamicTree_Rebuild( world->broadPhase.trees + b2_kinematicBody, false );
b2TracyCZoneEnd( tree_task );
}
// Task that can be done in parallel with the narrow-phase
// - rebuild the collision tree for dynamic and kinematic bodies to keep their query performance good
static void b2EnqueueTreeUpdate( b2World* world )
{
if ( world->taskCount < B2_MAX_TASKS )
{
world->userTreeTask = world->enqueueTaskFcn( &b2UpdateTreesTask, world, world->userTaskContext );
world->taskCount += 1;
world->activeTaskCount += world->userTreeTask == NULL ? 0 : 1;
}
else
{
world->userTreeTask = NULL;
b2UpdateTreesTask( world );
}
}
void b2UpdateBroadPhasePairs( b2World* world )
{
b2BroadPhase* bp = &world->broadPhase;
bool needUpdate = b2HasTreeMoved( bp->trees + b2_staticBody );
needUpdate = needUpdate || b2NeedsRebuild( bp->trees + b2_kinematicBody );
needUpdate = needUpdate || b2NeedsRebuild( bp->trees + b2_dynamicBody );
if ( needUpdate == false )
{
return;
}
b2TracyCZoneNC( update_pairs, "Find Pairs", b2_colorMediumSlateBlue, true );
b2Stack* alloc = &world->stack;
for ( int i = 0; i < world->workerCount; ++i )
{
b2Array_Clear( world->taskContexts.data[i].pairKeys );
}
// Generate pairs by querying the dynamic body tree against itself and against
// the kinematic and static trees.
{
// Get the sibling pairs of the dynamic body tree that have moved.
const b2DynamicTree* dynamicTree = bp->trees + b2_dynamicBody;
int pairCapacity = dynamicTree->nodeEnd / 2;
bp->movedSiblings = b2StackAlloc( alloc, pairCapacity * sizeof( int ), "moved pairs" );
int dynamicMoveCount = b2GatherMovedSiblings( dynamicTree, bp->movedSiblings );
// Get seeds for colliding against the static and kinematic trees.
b2NodePair crossSeeds[2 * B2_CROSS_SEED_COUNT];
int staticSeedCount = b2GatherCrossSeeds( dynamicTree, bp->trees + b2_staticBody, crossSeeds );
B2_ASSERT( staticSeedCount <= B2_CROSS_SEED_COUNT );
int kinematicSeedCount = b2GatherCrossSeeds( dynamicTree, bp->trees + b2_kinematicBody, crossSeeds + staticSeedCount );
B2_ASSERT( kinematicSeedCount <= B2_CROSS_SEED_COUNT );
int crossMoveCount = staticSeedCount + kinematicSeedCount;
// Collide the dynamic body tree against the static and kinematic trees.
b2CrossContext crossContext = {
.world = world,
.seeds = crossSeeds,
.staticSeedCount = staticSeedCount,
};
b2ParallelFor( world, &b2CrossPairsTask, crossMoveCount, 1, &crossContext );
// Collide the dynamic body tree against itself.
b2ParallelFor( world, &b2SelfPairsTask, dynamicMoveCount, 64, world );
}
b2DynamicTree_ClearMoved( bp->trees + b2_staticBody );
b2TracyCZoneEnd( update_pairs );
b2TracyCZoneNC( create_contacts, "Create Contacts", b2_colorCoral, true );
// Update stale trees.
b2EnqueueTreeUpdate( world );
// Pairs arrive in deterministic order but scrambled relative to body and shape order
// sorting them here improves solver performance.
int pairCount = 0;
for ( int i = 0; i < world->workerCount; ++i )
{
pairCount += world->taskContexts.data[i].pairKeys.count;
}
uint64_t* pairKeys = b2StackAlloc( alloc, b2MaxInt( pairCount, 1 ) * sizeof( uint64_t ), "pair keys" );
int keyCount = 0;
for ( int i = 0; i < world->workerCount; ++i )
{
const b2Array( uint64_t )* workerKeys = &world->taskContexts.data[i].pairKeys;
if ( workerKeys->count > 0 )
{
memcpy( pairKeys + keyCount, workerKeys->data, workerKeys->count * sizeof( uint64_t ) );
keyCount += workerKeys->count;
}
}
B2_ASSERT( keyCount == pairCount );
if ( pairCount > 1 )
{
uint64_t* tempKeys = b2StackAlloc( alloc, pairCount * sizeof( uint64_t ), "temp keys" );
b2RadixSortKeys( pairKeys, tempKeys, pairCount );
b2StackFree( alloc, tempKeys );
}
for ( int i = 0; i < keyCount; ++i )
{
int shapeIdA = (int)( pairKeys[i] >> 32 );
int shapeIdB = (int)( pairKeys[i] & 0xFFFFFFFF );
b2Shape* shapeA = b2Array_Get( world->shapes, shapeIdA );
b2Shape* shapeB = b2Array_Get( world->shapes, shapeIdB );
b2CreateContact( world, shapeA, shapeB );
}
b2StackFree( alloc, pairKeys );
b2StackFree( alloc, bp->movedSiblings );
bp->movedSiblings = NULL;
b2ValidateSolverSets( world );
b2TracyCZoneEnd( create_contacts );
}
void b2ValidateBroadphase( const b2BroadPhase* bp )
{
b2DynamicTree_Validate( bp->trees + b2_dynamicBody );
b2DynamicTree_Validate( bp->trees + b2_kinematicBody );
// TODO_ERIN validate every shape AABB is contained in tree AABB
}
void b2ValidateNoEnlarged( const b2BroadPhase* bp )
{
#if B2_ENABLE_VALIDATION == 1
for ( int j = 0; j < b2_bodyTypeCount; ++j )
{
const b2DynamicTree* tree = bp->trees + j;
b2DynamicTree_ValidateNoMoved( tree );
}
#else
B2_UNUSED( bp );
#endif
}