packages feed

Box2D-0.1.0.0: upstream/src/solver_set.c

// SPDX-FileCopyrightText: 2023 Erin Catto
// SPDX-License-Identifier: MIT

#include "solver_set.h"

#include "body.h"
#include "constraint_graph.h"
#include "contact.h"
#include "core.h"
#include "island.h"
#include "joint.h"
#include "physics_world.h"

#include <string.h>

void b2DestroySolverSet( b2World* world, int setIndex )
{
	b2SolverSet* set = b2Array_Get( world->solverSets, setIndex );
	b2Array_Destroy( set->bodySims );
	b2Array_Destroy( set->bodyStates );
	b2Array_Destroy( set->contactSims );
	b2Array_Destroy( set->jointSims );
	b2Array_Destroy( set->islandSims );
	b2FreeId( &world->solverSetIdPool, setIndex );
	*set = (b2SolverSet){ 0 };
	set->setIndex = B2_NULL_INDEX;
}

// Wake a solver set. Does not merge islands.
// Contacts can be in several places:
// 1. non-touching contacts in the disabled set
// 2. non-touching contacts already in the awake set
// 3. touching contacts in the sleeping set
// This handles contact types 1 and 3. Type 2 doesn't need any action.
void b2WakeSolverSet( b2World* world, int setIndex )
{
	B2_ASSERT( setIndex >= b2_firstSleepingSet );
	b2SolverSet* set = b2Array_Get( world->solverSets, setIndex );
	b2SolverSet* awakeSet = b2Array_Get( world->solverSets, b2_awakeSet );
	b2SolverSet* disabledSet = b2Array_Get( world->solverSets, b2_disabledSet );

	b2Body* bodies = world->bodies.data;

	int bodyCount = set->bodySims.count;
	for ( int i = 0; i < bodyCount; ++i )
	{
		b2BodySim* simSrc = set->bodySims.data + i;

		b2Body* body = bodies + simSrc->bodyId;
		B2_ASSERT( body->setIndex == setIndex );
		body->setIndex = b2_awakeSet;
		body->localIndex = awakeSet->bodySims.count;

		// Reset sleep timer
		body->sleepTime = 0.0f;

		b2BodySim* simDst = b2Array_Emplace( awakeSet->bodySims );
		memcpy( simDst, simSrc, sizeof( b2BodySim ) );

		b2BodyState* state = b2Array_Emplace( awakeSet->bodyStates );
		*state = b2_identityBodyState;
		state->flags = body->flags;

		// Move non-touching contacts from disabled set to awake set.
		int encodedBodySimIndex = body->localIndex;
		int contactKey = body->headContactKey;
		while ( contactKey != B2_NULL_INDEX )
		{
			int edgeIndex = contactKey & 1;
			int contactId = contactKey >> 1;

			b2Contact* contact = b2Array_Get( world->contacts, contactId );

			contactKey = contact->edges[edgeIndex].nextKey;

			if ( contact->setIndex != b2_disabledSet )
			{
				B2_ASSERT( contact->setIndex == b2_awakeSet || contact->setIndex == setIndex );

				// If this contact is awake but not touching I need to fixup the body sim index here.
				if ( contact->setIndex == b2_awakeSet )
				{
					B2_ASSERT( contact->colorIndex == B2_NULL_INDEX );
					b2ContactSim* awakeContactSim = b2Array_Get( awakeSet->contactSims, contact->localIndex );
					if ( edgeIndex == 0 )
					{
						awakeContactSim->encodedBodySimA = encodedBodySimIndex;
					}
					else
					{
						awakeContactSim->encodedBodySimB = encodedBodySimIndex;
					}
				}

				continue;
			}

			int localIndex = contact->localIndex;
			b2ContactSim* contactSim = b2Array_Get( disabledSet->contactSims, localIndex );

			B2_ASSERT( ( contact->flags & b2_contactTouchingFlag ) == 0 && contactSim->manifold.pointCount == 0 );

			contact->setIndex = b2_awakeSet;
			contact->localIndex = awakeSet->contactSims.count;
			b2ContactSim* awakeContactSim = b2Array_Emplace( awakeSet->contactSims );
			memcpy( awakeContactSim, contactSim, sizeof( b2ContactSim ) );

			if ( edgeIndex == 0 )
			{
				awakeContactSim->encodedBodySimA = encodedBodySimIndex;
			}
			else
			{
				awakeContactSim->encodedBodySimB = encodedBodySimIndex;
			}

			int movedLocalIndex = b2Array_RemoveSwap( disabledSet->contactSims, localIndex );
			if ( movedLocalIndex != B2_NULL_INDEX )
			{
				// fix moved element
				b2ContactSim* movedContactSim = disabledSet->contactSims.data + localIndex;
				b2Contact* movedContact = b2Array_Get( world->contacts, movedContactSim->contactId );
				B2_ASSERT( movedContact->localIndex == movedLocalIndex );
				movedContact->localIndex = localIndex;
			}
		}
	}

	// transfer touching contacts from sleeping set to contact graph
	{
		int contactCount = set->contactSims.count;
		for ( int i = 0; i < contactCount; ++i )
		{
			b2ContactSim* contactSim = set->contactSims.data + i;
			b2Contact* contact = b2Array_Get( world->contacts, contactSim->contactId );
			B2_ASSERT( contact->flags & b2_contactTouchingFlag );
			B2_ASSERT( contactSim->simFlags & b2_simTouchingFlag );
			B2_ASSERT( contactSim->manifold.pointCount > 0 );
			B2_ASSERT( contact->setIndex == setIndex );
			b2AddContactToGraph( world, contactSim, contact );
			contact->setIndex = b2_awakeSet;
		}
	}

	// transfer joints from sleeping set to awake set
	{
		int jointCount = set->jointSims.count;
		for ( int i = 0; i < jointCount; ++i )
		{
			b2JointSim* jointSim = set->jointSims.data + i;
			b2Joint* joint = b2Array_Get( world->joints, jointSim->jointId );
			B2_ASSERT( joint->setIndex == setIndex );
			b2AddJointToGraph( world, jointSim, joint );
			joint->setIndex = b2_awakeSet;
		}
	}

	// transfer island from sleeping set to awake set
	// Usually a sleeping set has only one island, but it is possible
	// that joints are created between sleeping islands and they
	// are moved to the same sleeping set.
	{
		int islandCount = set->islandSims.count;
		for ( int i = 0; i < islandCount; ++i )
		{
			b2IslandSim* islandSrc = set->islandSims.data + i;
			b2Island* island = b2Array_Get( world->islands, islandSrc->islandId );
			island->setIndex = b2_awakeSet;
			island->localIndex = awakeSet->islandSims.count;
			b2IslandSim* islandDst = b2Array_Emplace( awakeSet->islandSims );
			memcpy( islandDst, islandSrc, sizeof( b2IslandSim ) );
		}
	}

	// destroy the sleeping set
	b2DestroySolverSet( world, setIndex );
}

// Islands need to have a deterministic order because data is moved to a sleeping set according
// to island order.
void b2TrySleepIsland( b2World* world, int islandId )
{
	b2Island* island = b2Array_Get( world->islands, islandId );
	B2_ASSERT( island->setIndex == b2_awakeSet );

	// Cannot put an island to sleep while it has a pending split and more than one body.
	if ( island->constraintRemoveCount > 0 && island->bodies.count > 1 )
	{
		return;
	}

	// island is sleeping
	// - create new sleeping solver set
	// - move island to sleeping solver set
	// - identify non-touching contacts that should move to sleeping solver set or disabled set
	// - remove old island
	// - fix island
	int sleepSetId = b2AllocId( &world->solverSetIdPool );
	if ( sleepSetId == world->solverSets.count )
	{
		b2SolverSet set = { 0 };
		set.setIndex = B2_NULL_INDEX;
		b2Array_Push( world->solverSets, set );
	}

	b2SolverSet* sleepSet = b2Array_Get( world->solverSets, sleepSetId );
	*sleepSet = (b2SolverSet){ 0 };

	// grab awake set after creating the sleep set because the solver set array may have been resized
	b2SolverSet* awakeSet = b2Array_Get( world->solverSets, b2_awakeSet );
	B2_ASSERT( 0 <= island->localIndex && island->localIndex < awakeSet->islandSims.count );

	sleepSet->setIndex = sleepSetId;
	b2Array_CreateN( sleepSet->bodySims, island->bodies.count );
	b2Array_CreateN( sleepSet->contactSims, island->contacts.count );
	b2Array_CreateN( sleepSet->jointSims, island->joints.count );

	// move awake bodies to sleeping set
	// this shuffles around bodies in the awake set
	{
		b2SolverSet* disabledSet = b2Array_Get( world->solverSets, b2_disabledSet );
		for ( int i = 0; i < island->bodies.count; ++i )
		{
			int bodyId = island->bodies.data[i];
			b2Body* body = b2Array_Get( world->bodies, bodyId );
			B2_ASSERT( body->setIndex == b2_awakeSet );
			B2_ASSERT( body->islandId == islandId );
			B2_ASSERT( body->islandIndex == i );

			body->flags &= ~b2_bodyTransientFlags;

			// Update the body move event to indicate this body fell asleep
			// It could happen the body is forced asleep before it ever moves.
			if ( body->bodyMoveIndex != B2_NULL_INDEX )
			{
				b2BodyMoveEvent* moveEvent = b2Array_Get( world->bodyMoveEvents, body->bodyMoveIndex );
				B2_ASSERT( moveEvent->bodyId.index1 - 1 == bodyId );
				B2_ASSERT( moveEvent->bodyId.generation == body->generation );
				moveEvent->fellAsleep = true;
				body->bodyMoveIndex = B2_NULL_INDEX;
			}

			int awakeBodyIndex = body->localIndex;
			b2BodySim* awakeSim = b2Array_Get( awakeSet->bodySims, awakeBodyIndex );
			awakeSim->flags &= ~b2_bodyTransientFlags;

			// move body sim to sleep set
			int sleepBodyIndex = sleepSet->bodySims.count;
			b2BodySim* sleepBodySim = b2Array_Emplace( sleepSet->bodySims );
			memcpy( sleepBodySim, awakeSim, sizeof( b2BodySim ) );

			b2Body* movedBody = b2RemoveBodySim( world, awakeSet, awakeBodyIndex );
			if ( movedBody != NULL && movedBody->islandId != islandId )
			{
				// Update the contact sims for the moved body.
				b2RefreshBodyContactIndices( world, movedBody );
			}

			// Destroy state, no need to clone.
			(void)b2Array_RemoveSwap( awakeSet->bodyStates, awakeBodyIndex );

			body->setIndex = sleepSetId;
			body->localIndex = sleepBodyIndex;

			// Move non-touching contacts to the disabled set.
			// Non-touching contacts may exist between sleeping islands and there is no clear ownership.
			int contactKey = body->headContactKey;
			while ( contactKey != B2_NULL_INDEX )
			{
				int contactId = contactKey >> 1;
				int edgeIndex = contactKey & 1;

				b2Contact* contact = b2Array_Get( world->contacts, contactId );

				B2_ASSERT( contact->setIndex == b2_awakeSet );

				contactKey = contact->edges[edgeIndex].nextKey;

				if ( contact->colorIndex != B2_NULL_INDEX )
				{
					// contact is touching and will be moved separately
					B2_ASSERT( ( contact->flags & b2_contactTouchingFlag ) != 0 );
					continue;
				}

				int localIndex = contact->localIndex;
				b2ContactSim* contactSim = b2Array_Get( awakeSet->contactSims, localIndex );

				if ( edgeIndex == 0 )
				{
					contactSim->encodedBodySimA = B2_NULL_INDEX;
				}
				else
				{
					contactSim->encodedBodySimB = B2_NULL_INDEX;
				}

				// the other body may still be awake, it still may go to sleep and then it will be responsible
				// for moving this contact to the disabled set.
				int otherEdgeIndex = edgeIndex ^ 1;
				int otherBodyId = contact->edges[otherEdgeIndex].bodyId;
				b2Body* otherBody = b2Array_Get( world->bodies, otherBodyId );
				if ( otherBody->setIndex == b2_awakeSet )
				{
					continue;
				}

				B2_ASSERT( contactSim->manifold.pointCount == 0 );
				B2_ASSERT( ( contact->flags & b2_contactTouchingFlag ) == 0 );

				// move the non-touching contact to the disabled set
				contact->setIndex = b2_disabledSet;
				contact->localIndex = disabledSet->contactSims.count;
				b2ContactSim* disabledContactSim = b2Array_Emplace( disabledSet->contactSims );
				memcpy( disabledContactSim, contactSim, sizeof( b2ContactSim ) );

				int movedLocalIndex = b2Array_RemoveSwap( awakeSet->contactSims, localIndex );
				if ( movedLocalIndex != B2_NULL_INDEX )
				{
					// fix moved element
					b2ContactSim* movedContactSim = awakeSet->contactSims.data + localIndex;
					b2Contact* movedContact = b2Array_Get( world->contacts, movedContactSim->contactId );
					B2_ASSERT( movedContact->localIndex == movedLocalIndex );
					movedContact->localIndex = localIndex;
				}
			}
		}
	}

	// move touching contacts
	// this shuffles contacts in the awake set
	{
		for ( int i = 0; i < island->contacts.count; ++i )
		{
			b2ContactLink* link = island->contacts.data + i;
			b2Contact* contact = b2Array_Get( world->contacts, link->contactId );
			B2_ASSERT( contact->setIndex == b2_awakeSet );
			B2_ASSERT( contact->islandId == islandId );
			int colorIndex = contact->colorIndex;
			B2_ASSERT( 0 <= colorIndex && colorIndex < B2_GRAPH_COLOR_COUNT );

			b2GraphColor* color = world->constraintGraph.colors + colorIndex;

			// Remove bodies from graph coloring associated with this constraint
			if ( colorIndex != B2_OVERFLOW_INDEX )
			{
				// might clear a bit for a static body, but this has no effect
				b2ClearBit( &color->bodySet, contact->edges[0].bodyId );
				b2ClearBit( &color->bodySet, contact->edges[1].bodyId );
			}

			int localIndex = contact->localIndex;
			b2ContactSim* awakeContactSim = b2Array_Get( color->contactSims, localIndex );

			int sleepContactIndex = sleepSet->contactSims.count;
			b2ContactSim* sleepContactSim = b2Array_Emplace( sleepSet->contactSims );
			memcpy( sleepContactSim, awakeContactSim, sizeof( b2ContactSim ) );

			sleepContactSim->encodedBodySimA = b2SleepBodySimIndex( sleepContactSim->encodedBodySimA );
			sleepContactSim->encodedBodySimB = b2SleepBodySimIndex( sleepContactSim->encodedBodySimB );

			int movedLocalIndex = b2Array_RemoveSwap( color->contactSims, localIndex );
			if ( movedLocalIndex != B2_NULL_INDEX )
			{
				// fix moved element
				b2ContactSim* movedContactSim = color->contactSims.data + localIndex;
				b2Contact* movedContact = b2Array_Get( world->contacts, movedContactSim->contactId );
				B2_ASSERT( movedContact->localIndex == movedLocalIndex );
				movedContact->localIndex = localIndex;
			}

			contact->setIndex = sleepSetId;
			contact->colorIndex = B2_NULL_INDEX;
			contact->localIndex = sleepContactIndex;
		}
	}

	// move joints
	// this shuffles joints in the awake set
	{
		for ( int i = 0; i < island->joints.count; ++i )
		{
			b2JointLink* link = island->joints.data + i;
			b2Joint* joint = b2Array_Get( world->joints, link->jointId );
			B2_ASSERT( joint->setIndex == b2_awakeSet );
			B2_ASSERT( joint->islandId == islandId );
			int colorIndex = joint->colorIndex;
			int localIndex = joint->localIndex;

			B2_ASSERT( 0 <= colorIndex && colorIndex < B2_GRAPH_COLOR_COUNT );

			b2GraphColor* color = world->constraintGraph.colors + colorIndex;

			b2JointSim* awakeJointSim = b2Array_Get( color->jointSims, localIndex );

			if ( colorIndex != B2_OVERFLOW_INDEX )
			{
				// might clear a bit for a static body, but this has no effect
				b2ClearBit( &color->bodySet, joint->edges[0].bodyId );
				b2ClearBit( &color->bodySet, joint->edges[1].bodyId );
			}

			int sleepJointIndex = sleepSet->jointSims.count;
			b2JointSim* sleepJointSim = b2Array_Emplace( sleepSet->jointSims );
			memcpy( sleepJointSim, awakeJointSim, sizeof( b2JointSim ) );

			int movedIndex = b2Array_RemoveSwap( color->jointSims, localIndex );
			if ( movedIndex != B2_NULL_INDEX )
			{
				// fix moved element
				b2JointSim* movedJointSim = color->jointSims.data + localIndex;
				int movedId = movedJointSim->jointId;
				b2Joint* movedJoint = b2Array_Get( world->joints, movedId );
				B2_ASSERT( movedJoint->localIndex == movedIndex );
				movedJoint->localIndex = localIndex;
			}

			joint->setIndex = sleepSetId;
			joint->colorIndex = B2_NULL_INDEX;
			joint->localIndex = sleepJointIndex;
		}
	}

	// move island struct
	{
		B2_ASSERT( island->setIndex == b2_awakeSet );

		int islandIndex = island->localIndex;
		b2IslandSim* sleepIsland = b2Array_Emplace( sleepSet->islandSims );
		sleepIsland->islandId = islandId;

		int movedIslandIndex = b2Array_RemoveSwap( awakeSet->islandSims, islandIndex );
		if ( movedIslandIndex != B2_NULL_INDEX )
		{
			// fix index on moved element
			b2IslandSim* movedIslandSim = awakeSet->islandSims.data + islandIndex;
			int movedIslandId = movedIslandSim->islandId;
			b2Island* movedIsland = b2Array_Get( world->islands, movedIslandId );
			B2_ASSERT( movedIsland->localIndex == movedIslandIndex );
			movedIsland->localIndex = islandIndex;
		}

		island->setIndex = sleepSetId;
		island->localIndex = 0;
	}

	if ( world->splitIslandId == islandId )
	{
		world->splitIslandId = B2_NULL_INDEX;
	}

	b2ValidateSolverSets( world );
}

// This is called when joints are created between sets. I want to allow the sets
// to continue sleeping if both are asleep. Otherwise one set is waked.
// Islands will get merge when the set is waked.
void b2MergeSolverSets( b2World* world, int setId1, int setId2 )
{
	B2_ASSERT( setId1 >= b2_firstSleepingSet );
	B2_ASSERT( setId2 >= b2_firstSleepingSet );
	b2SolverSet* set1 = b2Array_Get( world->solverSets, setId1 );
	b2SolverSet* set2 = b2Array_Get( world->solverSets, setId2 );

	// Move the fewest number of bodies
	if ( set1->bodySims.count < set2->bodySims.count )
	{
		b2SolverSet* tempSet = set1;
		set1 = set2;
		set2 = tempSet;

		int tempId = setId1;
		setId1 = setId2;
		setId2 = tempId;
	}

	// transfer bodies
	{
		b2Body* bodies = world->bodies.data;
		int bodyCount = set2->bodySims.count;
		for ( int i = 0; i < bodyCount; ++i )
		{
			b2BodySim* simSrc = set2->bodySims.data + i;

			b2Body* body = bodies + simSrc->bodyId;
			B2_ASSERT( body->setIndex == setId2 );
			body->setIndex = setId1;
			body->localIndex = set1->bodySims.count;

			b2BodySim* simDst = b2Array_Emplace( set1->bodySims );
			memcpy( simDst, simSrc, sizeof( b2BodySim ) );
		}
	}

	// transfer contacts
	{
		int contactCount = set2->contactSims.count;
		for ( int i = 0; i < contactCount; ++i )
		{
			b2ContactSim* contactSrc = set2->contactSims.data + i;

			b2Contact* contact = b2Array_Get( world->contacts, contactSrc->contactId );
			B2_ASSERT( contact->setIndex == setId2 );
			contact->setIndex = setId1;
			contact->localIndex = set1->contactSims.count;

			b2ContactSim* contactDst = b2Array_Emplace( set1->contactSims );
			memcpy( contactDst, contactSrc, sizeof( b2ContactSim ) );
		}
	}

	// transfer joints
	{
		int jointCount = set2->jointSims.count;
		for ( int i = 0; i < jointCount; ++i )
		{
			b2JointSim* jointSrc = set2->jointSims.data + i;

			b2Joint* joint = b2Array_Get( world->joints, jointSrc->jointId );
			B2_ASSERT( joint->setIndex == setId2 );
			joint->setIndex = setId1;
			joint->localIndex = set1->jointSims.count;

			b2JointSim* jointDst = b2Array_Emplace( set1->jointSims );
			memcpy( jointDst, jointSrc, sizeof( b2JointSim ) );
		}
	}

	// transfer islands
	{
		int islandCount = set2->islandSims.count;
		for ( int i = 0; i < islandCount; ++i )
		{
			b2IslandSim* islandSrc = set2->islandSims.data + i;
			int islandId = islandSrc->islandId;

			b2Island* island = b2Array_Get( world->islands, islandId );
			island->setIndex = setId1;
			island->localIndex = set1->islandSims.count;

			b2IslandSim* islandDst = b2Array_Emplace( set1->islandSims );
			memcpy( islandDst, islandSrc, sizeof( b2IslandSim ) );
		}
	}

	// destroy the merged set
	b2DestroySolverSet( world, setId2 );

	b2ValidateSolverSets( world );
}

// Remove a body from a solver set and return the body swapped into the slot.
b2Body* b2RemoveBodySim( b2World* world, b2SolverSet* set, int localIndex )
{
	b2Array( b2BodySim )* bodySims = &set->bodySims;
	B2_ASSERT( 0 <= localIndex && localIndex < bodySims->count );
	int lastIndex = bodySims->count - 1;
	bodySims->count -= 1;

	if ( localIndex == lastIndex )
	{
		return NULL;
	}

	bodySims->data[localIndex] = bodySims->data[lastIndex];
	b2Body* movedBody = b2Array_Get( world->bodies, bodySims->data[localIndex].bodyId );
	B2_ASSERT( movedBody->localIndex == lastIndex );
	movedBody->localIndex = localIndex;

	return movedBody;
}

void b2TransferBody( b2World* world, b2SolverSet* targetSet, b2SolverSet* sourceSet, b2Body* body )
{
	B2_ASSERT( body->headContactKey == B2_NULL_INDEX );

	if ( targetSet == sourceSet )
	{
		return;
	}

	// The use cases for transfer body (set body type, disable, etc) destroy all the contacts
	// first. Double check here because this function wouldn't work correctly otherwise.
	B2_ASSERT( body->headContactKey == B2_NULL_INDEX );

	int sourceIndex = body->localIndex;
	b2BodySim* sourceSim = b2Array_Get( sourceSet->bodySims, sourceIndex );

	int targetIndex = targetSet->bodySims.count;
	b2BodySim* targetSim = b2Array_Emplace( targetSet->bodySims );
	memcpy( targetSim, sourceSim, sizeof( b2BodySim ) );

	// Clear transient body flags
	body->flags &= ~b2_bodyTransientFlags;
	targetSim->flags &= ~( b2_isFast | b2_bodyTransientFlags );

	// Remove body sim from solver set that owns it
	b2Body* movedBody = b2RemoveBodySim( world, sourceSet, sourceIndex );
	if ( movedBody != NULL )
	{
		// Fixup the moved body's contact sims.
		b2RefreshBodyContactIndices( world, movedBody );
	}

	if ( sourceSet->setIndex == b2_awakeSet )
	{
		(void)b2Array_RemoveSwap( sourceSet->bodyStates, sourceIndex );
	}
	else if ( targetSet->setIndex == b2_awakeSet )
	{
		b2BodyState* state = b2Array_Emplace( targetSet->bodyStates );
		*state = b2_identityBodyState;
		state->flags = body->flags;
	}

	body->setIndex = targetSet->setIndex;
	body->localIndex = targetIndex;
}

void b2TransferJoint( b2World* world, b2SolverSet* targetSet, b2SolverSet* sourceSet, b2Joint* joint )
{
	if ( targetSet == sourceSet )
	{
		return;
	}

	int localIndex = joint->localIndex;
	int colorIndex = joint->colorIndex;

	// Retrieve source.
	b2JointSim* sourceSim;
	if ( sourceSet->setIndex == b2_awakeSet )
	{
		B2_ASSERT( 0 <= colorIndex && colorIndex < B2_GRAPH_COLOR_COUNT );
		b2GraphColor* color = world->constraintGraph.colors + colorIndex;

		sourceSim = b2Array_Get( color->jointSims, localIndex );
	}
	else
	{
		B2_ASSERT( colorIndex == B2_NULL_INDEX );
		sourceSim = b2Array_Get( sourceSet->jointSims, localIndex );
	}

	// Create target and copy. Fix joint.
	if ( targetSet->setIndex == b2_awakeSet )
	{
		b2AddJointToGraph( world, sourceSim, joint );
		joint->setIndex = b2_awakeSet;
	}
	else
	{
		joint->setIndex = targetSet->setIndex;
		joint->localIndex = targetSet->jointSims.count;
		joint->colorIndex = B2_NULL_INDEX;

		b2JointSim* targetSim = b2Array_Emplace( targetSet->jointSims );
		memcpy( targetSim, sourceSim, sizeof( b2JointSim ) );
	}

	// Destroy source.
	if ( sourceSet->setIndex == b2_awakeSet )
	{
		b2RemoveJointFromGraph( world, joint->edges[0].bodyId, joint->edges[1].bodyId, colorIndex, localIndex );
	}
	else
	{
		int movedIndex = b2Array_RemoveSwap( sourceSet->jointSims, localIndex );
		if ( movedIndex != B2_NULL_INDEX )
		{
			// fix swapped element
			b2JointSim* movedJointSim = sourceSet->jointSims.data + localIndex;
			int movedId = movedJointSim->jointId;
			b2Joint* movedJoint = b2Array_Get( world->joints, movedId );
			movedJoint->localIndex = localIndex;
		}
	}
}