Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/IslandBuilder.cpp
21416 views
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)1// SPDX-FileCopyrightText: 2021 Jorrit Rouwe2// SPDX-License-Identifier: MIT34#include <Jolt/Jolt.h>56#include <Jolt/Physics/IslandBuilder.h>7#include <Jolt/Physics/Body/Body.h>8#include <Jolt/Physics/PhysicsSettings.h>9#include <Jolt/Core/Profiler.h>10#include <Jolt/Core/Atomics.h>11#include <Jolt/Core/TempAllocator.h>12#include <Jolt/Core/QuickSort.h>1314JPH_NAMESPACE_BEGIN1516IslandBuilder::~IslandBuilder()17{18JPH_ASSERT(mConstraintLinks == nullptr);19JPH_ASSERT(mContactLinks == nullptr);20JPH_ASSERT(mBodyIslands == nullptr);21JPH_ASSERT(mBodyIslandEnds == nullptr);22JPH_ASSERT(mConstraintIslands == nullptr);23JPH_ASSERT(mConstraintIslandEnds == nullptr);24JPH_ASSERT(mContactIslands == nullptr);25JPH_ASSERT(mContactIslandEnds == nullptr);26JPH_ASSERT(mIslandsSorted == nullptr);2728delete [] mBodyLinks;29}3031void IslandBuilder::Init(uint32 inMaxActiveBodies)32{33mMaxActiveBodies = inMaxActiveBodies;3435// Link each body to itself, BuildBodyIslands() will restore this so that we don't need to do this each step36JPH_ASSERT(mBodyLinks == nullptr);37mBodyLinks = new BodyLink [mMaxActiveBodies];38for (uint32 i = 0; i < mMaxActiveBodies; ++i)39mBodyLinks[i].mLinkedTo.store(i, memory_order_relaxed);40}4142void IslandBuilder::PrepareContactConstraints(uint32 inMaxContacts, TempAllocator *inTempAllocator)43{44JPH_PROFILE_FUNCTION();4546// Need to call Init first47JPH_ASSERT(mBodyLinks != nullptr);4849// Check that the builder has been reset50JPH_ASSERT(mNumContacts == 0);51JPH_ASSERT(mNumIslands == 0);5253// Create contact link buffer, not initialized so each contact needs to be explicitly set54JPH_ASSERT(mContactLinks == nullptr);55mContactLinks = (uint32 *)inTempAllocator->Allocate(inMaxContacts * sizeof(uint32));56mMaxContacts = inMaxContacts;5758#ifdef JPH_VALIDATE_ISLAND_BUILDER59// Create validation structures60JPH_ASSERT(mLinkValidation == nullptr);61mLinkValidation = (LinkValidation *)inTempAllocator->Allocate(inMaxContacts * sizeof(LinkValidation));62mNumLinkValidation = 0;63#endif64}6566void IslandBuilder::PrepareNonContactConstraints(uint32 inNumConstraints, TempAllocator *inTempAllocator)67{68JPH_PROFILE_FUNCTION();6970// Need to call Init first71JPH_ASSERT(mBodyLinks != nullptr);7273// Check that the builder has been reset74JPH_ASSERT(mNumIslands == 0);7576// Store number of constraints77mNumConstraints = inNumConstraints;7879// Create constraint link buffer, not initialized so each constraint needs to be explicitly set80JPH_ASSERT(mConstraintLinks == nullptr);81mConstraintLinks = (uint32 *)inTempAllocator->Allocate(inNumConstraints * sizeof(uint32));82}8384uint32 IslandBuilder::GetLowestBodyIndex(uint32 inActiveBodyIndex) const85{86uint32 index = inActiveBodyIndex;87for (;;)88{89uint32 link_to = mBodyLinks[index].mLinkedTo.load(memory_order_relaxed);90if (link_to == index)91break;92index = link_to;93}94return index;95}9697void IslandBuilder::LinkBodies(uint32 inFirst, uint32 inSecond)98{99// Both need to be active, we don't want to create an island with static objects100if (inFirst >= mMaxActiveBodies || inSecond >= mMaxActiveBodies)101return;102103#ifdef JPH_VALIDATE_ISLAND_BUILDER104// Add link to the validation list105if (mNumLinkValidation < uint32(mMaxContacts))106mLinkValidation[mNumLinkValidation++] = { inFirst, inSecond };107else108JPH_ASSERT(false, "Out of links");109#endif110111// Start the algorithm with the two bodies112uint32 first_link_to = inFirst;113uint32 second_link_to = inSecond;114115for (;;)116{117// Follow the chain until we get to the body with lowest index118// If the swap compare below fails, we'll keep searching from the lowest index for the new lowest index119first_link_to = GetLowestBodyIndex(first_link_to);120second_link_to = GetLowestBodyIndex(second_link_to);121122// If the targets are the same, the bodies are already connected123if (first_link_to != second_link_to)124{125// We always link the highest to the lowest126if (first_link_to < second_link_to)127{128// Attempt to link the second to the first129// Since we found this body to be at the end of the chain it must point to itself, and if it130// doesn't it has been reparented and we need to retry the algorithm131if (!mBodyLinks[second_link_to].mLinkedTo.compare_exchange_weak(second_link_to, first_link_to, memory_order_relaxed))132continue;133}134else135{136// Attempt to link the first to the second137// Since we found this body to be at the end of the chain it must point to itself, and if it138// doesn't it has been reparented and we need to retry the algorithm139if (!mBodyLinks[first_link_to].mLinkedTo.compare_exchange_weak(first_link_to, second_link_to, memory_order_relaxed))140continue;141}142}143144// Linking succeeded!145// Chains of bodies can become really long, resulting in an O(N) loop to find the lowest body index146// to prevent this we attempt to update the link of the bodies that were passed in to directly point147// to the lowest index that we found. If the value became lower than our lowest link, some other148// thread must have relinked these bodies in the mean time so we won't update the value.149uint32 lowest_link_to = min(first_link_to, second_link_to);150AtomicMin(mBodyLinks[inFirst].mLinkedTo, lowest_link_to, memory_order_relaxed);151AtomicMin(mBodyLinks[inSecond].mLinkedTo, lowest_link_to, memory_order_relaxed);152break;153}154}155156void IslandBuilder::LinkConstraint(uint32 inConstraintIndex, uint32 inFirst, uint32 inSecond)157{158LinkBodies(inFirst, inSecond);159160JPH_ASSERT(inConstraintIndex < mNumConstraints);161uint32 min_value = min(inFirst, inSecond); // Use fact that invalid index is 0xffffffff, we want the active body of two162JPH_ASSERT(min_value != Body::cInactiveIndex); // At least one of the bodies must be active163mConstraintLinks[inConstraintIndex] = min_value;164}165166void IslandBuilder::LinkContact(uint32 inContactIndex, uint32 inFirst, uint32 inSecond)167{168JPH_ASSERT(inContactIndex < mMaxContacts);169mContactLinks[inContactIndex] = min(inFirst, inSecond); // Use fact that invalid index is 0xffffffff, we want the active body of two170}171172#ifdef JPH_VALIDATE_ISLAND_BUILDER173174void IslandBuilder::ValidateIslands(uint32 inNumActiveBodies) const175{176JPH_PROFILE_FUNCTION();177178// Go through all links so far179for (uint32 i = 0; i < mNumLinkValidation; ++i)180{181// If the bodies in this link ended up in different groups we have a problem182if (mBodyLinks[mLinkValidation[i].mFirst].mIslandIndex != mBodyLinks[mLinkValidation[i].mSecond].mIslandIndex)183{184Trace("Fail: %u, %u", mLinkValidation[i].mFirst, mLinkValidation[i].mSecond);185Trace("Num Active: %u", inNumActiveBodies);186187for (uint32 j = 0; j < mNumLinkValidation; ++j)188Trace("builder.Link(%u, %u);", mLinkValidation[j].mFirst, mLinkValidation[j].mSecond);189190IslandBuilder tmp;191tmp.Init(inNumActiveBodies);192for (uint32 j = 0; j < mNumLinkValidation; ++j)193{194Trace("Link %u -> %u", mLinkValidation[j].mFirst, mLinkValidation[j].mSecond);195tmp.LinkBodies(mLinkValidation[j].mFirst, mLinkValidation[j].mSecond);196for (uint32 t = 0; t < inNumActiveBodies; ++t)197Trace("%u -> %u", t, (uint32)tmp.mBodyLinks[t].mLinkedTo);198}199200JPH_ASSERT(false, "IslandBuilder validation failed");201}202}203}204205#endif206207void IslandBuilder::BuildBodyIslands(const BodyID *inActiveBodies, uint32 inNumActiveBodies, TempAllocator *inTempAllocator)208{209JPH_PROFILE_FUNCTION();210211// Store the amount of active bodies212mNumActiveBodies = inNumActiveBodies;213214// Create output arrays for body ID's, don't call constructors215JPH_ASSERT(mBodyIslands == nullptr);216mBodyIslands = (BodyID *)inTempAllocator->Allocate(inNumActiveBodies * sizeof(BodyID));217218// Create output array for start index of each island. At this point we don't know how many islands there will be, but we know it cannot be more than inNumActiveBodies.219// Note: We allocate 1 extra entry because we always increment the count of the next island.220uint32 *body_island_starts = (uint32 *)inTempAllocator->Allocate((inNumActiveBodies + 1) * sizeof(uint32));221222// First island always starts at 0223body_island_starts[0] = 0;224225// Calculate island index for all bodies226JPH_ASSERT(mNumIslands == 0);227for (uint32 i = 0; i < inNumActiveBodies; ++i)228{229BodyLink &link = mBodyLinks[i];230uint32 s = link.mLinkedTo.load(memory_order_relaxed);231if (s != i)232{233// Links to another body, take island index from other body (this must have been filled in already since we're looping from low to high)234JPH_ASSERT(s < uint32(i));235uint32 island_index = mBodyLinks[s].mIslandIndex;236link.mIslandIndex = island_index;237238// Increment the start of the next island239body_island_starts[island_index + 1]++;240}241else242{243// Does not link to other body, this is the start of a new island244link.mIslandIndex = mNumIslands;245++mNumIslands;246247// Set the start of the next island to 1248body_island_starts[mNumIslands] = 1;249}250}251252#ifdef JPH_VALIDATE_ISLAND_BUILDER253ValidateIslands(inNumActiveBodies);254#endif255256// Make the start array absolute (so far we only counted)257for (uint32 island = 1; island < mNumIslands; ++island)258body_island_starts[island] += body_island_starts[island - 1];259260// Convert the to a linear list grouped by island261for (uint32 i = 0; i < inNumActiveBodies; ++i)262{263BodyLink &link = mBodyLinks[i];264265// Copy the body to the correct location in the array and increment it266uint32 &start = body_island_starts[link.mIslandIndex];267mBodyIslands[start] = inActiveBodies[i];268start++;269270// Reset linked to field for the next update271link.mLinkedTo.store(i, memory_order_relaxed);272}273274// We should now have a full array275JPH_ASSERT(mNumIslands == 0 || body_island_starts[mNumIslands - 1] == inNumActiveBodies);276277// We've incremented all body indices so that they now point at the end instead of the starts278JPH_ASSERT(mBodyIslandEnds == nullptr);279mBodyIslandEnds = body_island_starts;280}281282void IslandBuilder::BuildConstraintIslands(const uint32 *inConstraintToBody, uint32 inNumConstraints, uint32 *&outConstraints, uint32 *&outConstraintsEnd, TempAllocator *inTempAllocator) const283{284JPH_PROFILE_FUNCTION();285286// Check if there's anything to do287if (inNumConstraints == 0)288return;289290// Create output arrays for constraints291// Note: For the end indices we allocate 1 extra entry so we don't have to do an if in the inner loop292uint32 *constraints = (uint32 *)inTempAllocator->Allocate(inNumConstraints * sizeof(uint32));293uint32 *constraint_ends = (uint32 *)inTempAllocator->Allocate((mNumIslands + 1) * sizeof(uint32));294295// Reset sizes296for (uint32 island = 0; island < mNumIslands; ++island)297constraint_ends[island] = 0;298299// Loop over array and increment start relative position for the next island300for (uint32 constraint = 0; constraint < inNumConstraints; ++constraint)301{302uint32 body_idx = inConstraintToBody[constraint];303uint32 next_island_idx = mBodyLinks[body_idx].mIslandIndex + 1;304JPH_ASSERT(next_island_idx <= mNumIslands);305constraint_ends[next_island_idx]++;306}307308// Make start positions absolute309for (uint32 island = 1; island < mNumIslands; ++island)310constraint_ends[island] += constraint_ends[island - 1];311312// Loop over array and collect constraints313for (uint32 constraint = 0; constraint < inNumConstraints; ++constraint)314{315uint32 body_idx = inConstraintToBody[constraint];316uint32 island_idx = mBodyLinks[body_idx].mIslandIndex;317constraints[constraint_ends[island_idx]++] = constraint;318}319320JPH_ASSERT(outConstraints == nullptr);321outConstraints = constraints;322JPH_ASSERT(outConstraintsEnd == nullptr);323outConstraintsEnd = constraint_ends;324}325326void IslandBuilder::SortIslands(TempAllocator *inTempAllocator)327{328JPH_PROFILE_FUNCTION();329330if (mNumContacts > 0 || mNumConstraints > 0)331{332// Allocate mapping table333JPH_ASSERT(mIslandsSorted == nullptr);334mIslandsSorted = (uint32 *)inTempAllocator->Allocate(mNumIslands * sizeof(uint32));335336// Initialize index337for (uint32 island = 0; island < mNumIslands; ++island)338mIslandsSorted[island] = island;339340// Determine the sum of contact constraints / constraints per island341uint32 *num_constraints = (uint32 *)inTempAllocator->Allocate(mNumIslands * sizeof(uint32));342if (mNumContacts > 0 && mNumConstraints > 0)343{344num_constraints[0] = mConstraintIslandEnds[0] + mContactIslandEnds[0];345for (uint32 island = 1; island < mNumIslands; ++island)346num_constraints[island] = mConstraintIslandEnds[island] - mConstraintIslandEnds[island - 1]347+ mContactIslandEnds[island] - mContactIslandEnds[island - 1];348}349else if (mNumContacts > 0)350{351num_constraints[0] = mContactIslandEnds[0];352for (uint32 island = 1; island < mNumIslands; ++island)353num_constraints[island] = mContactIslandEnds[island] - mContactIslandEnds[island - 1];354}355else356{357num_constraints[0] = mConstraintIslandEnds[0];358for (uint32 island = 1; island < mNumIslands; ++island)359num_constraints[island] = mConstraintIslandEnds[island] - mConstraintIslandEnds[island - 1];360}361362// Sort so the biggest islands go first, this means that the jobs that take longest will be running363// first which improves the chance that all jobs finish at the same time.364QuickSort(mIslandsSorted, mIslandsSorted + mNumIslands, [num_constraints](uint32 inLHS, uint32 inRHS) {365return num_constraints[inLHS] > num_constraints[inRHS];366});367368inTempAllocator->Free(num_constraints, mNumIslands * sizeof(uint32));369}370}371372void IslandBuilder::Finalize(const BodyID *inActiveBodies, uint32 inNumActiveBodies, uint32 inNumContacts, TempAllocator *inTempAllocator)373{374JPH_PROFILE_FUNCTION();375376mNumContacts = inNumContacts;377378BuildBodyIslands(inActiveBodies, inNumActiveBodies, inTempAllocator);379BuildConstraintIslands(mConstraintLinks, mNumConstraints, mConstraintIslands, mConstraintIslandEnds, inTempAllocator);380BuildConstraintIslands(mContactLinks, mNumContacts, mContactIslands, mContactIslandEnds, inTempAllocator);381SortIslands(inTempAllocator);382383mNumPositionSteps = (uint8 *)inTempAllocator->Allocate(mNumIslands * sizeof(uint8));384}385386void IslandBuilder::GetBodiesInIsland(uint32 inIslandIndex, BodyID *&outBodiesBegin, BodyID *&outBodiesEnd) const387{388JPH_ASSERT(inIslandIndex < mNumIslands);389uint32 sorted_index = mIslandsSorted != nullptr? mIslandsSorted[inIslandIndex] : inIslandIndex;390outBodiesBegin = sorted_index > 0? mBodyIslands + mBodyIslandEnds[sorted_index - 1] : mBodyIslands;391outBodiesEnd = mBodyIslands + mBodyIslandEnds[sorted_index];392}393394bool IslandBuilder::GetConstraintsInIsland(uint32 inIslandIndex, uint32 *&outConstraintsBegin, uint32 *&outConstraintsEnd) const395{396JPH_ASSERT(inIslandIndex < mNumIslands);397if (mNumConstraints == 0)398{399outConstraintsBegin = nullptr;400outConstraintsEnd = nullptr;401return false;402}403else404{405uint32 sorted_index = mIslandsSorted[inIslandIndex];406outConstraintsBegin = sorted_index > 0? mConstraintIslands + mConstraintIslandEnds[sorted_index - 1] : mConstraintIslands;407outConstraintsEnd = mConstraintIslands + mConstraintIslandEnds[sorted_index];408return outConstraintsBegin != outConstraintsEnd;409}410}411412bool IslandBuilder::GetContactsInIsland(uint32 inIslandIndex, uint32 *&outContactsBegin, uint32 *&outContactsEnd) const413{414JPH_ASSERT(inIslandIndex < mNumIslands);415if (mNumContacts == 0)416{417outContactsBegin = nullptr;418outContactsEnd = nullptr;419return false;420}421else422{423uint32 sorted_index = mIslandsSorted[inIslandIndex];424outContactsBegin = sorted_index > 0? mContactIslands + mContactIslandEnds[sorted_index - 1] : mContactIslands;425outContactsEnd = mContactIslands + mContactIslandEnds[sorted_index];426return outContactsBegin != outContactsEnd;427}428}429430void IslandBuilder::ResetIslands(TempAllocator *inTempAllocator)431{432JPH_PROFILE_FUNCTION();433434inTempAllocator->Free(mNumPositionSteps, mNumIslands * sizeof(uint8));435436if (mIslandsSorted != nullptr)437{438inTempAllocator->Free(mIslandsSorted, mNumIslands * sizeof(uint32));439mIslandsSorted = nullptr;440}441442if (mContactIslands != nullptr)443{444inTempAllocator->Free(mContactIslandEnds, (mNumIslands + 1) * sizeof(uint32));445mContactIslandEnds = nullptr;446inTempAllocator->Free(mContactIslands, mNumContacts * sizeof(uint32));447mContactIslands = nullptr;448}449450if (mConstraintIslands != nullptr)451{452inTempAllocator->Free(mConstraintIslandEnds, (mNumIslands + 1) * sizeof(uint32));453mConstraintIslandEnds = nullptr;454inTempAllocator->Free(mConstraintIslands, mNumConstraints * sizeof(uint32));455mConstraintIslands = nullptr;456}457458inTempAllocator->Free(mBodyIslandEnds, (mNumActiveBodies + 1) * sizeof(uint32));459mBodyIslandEnds = nullptr;460inTempAllocator->Free(mBodyIslands, mNumActiveBodies * sizeof(uint32));461mBodyIslands = nullptr;462463inTempAllocator->Free(mConstraintLinks, mNumConstraints * sizeof(uint32));464mConstraintLinks = nullptr;465466#ifdef JPH_VALIDATE_ISLAND_BUILDER467inTempAllocator->Free(mLinkValidation, mMaxContacts * sizeof(LinkValidation));468mLinkValidation = nullptr;469#endif470471inTempAllocator->Free(mContactLinks, mMaxContacts * sizeof(uint32));472mContactLinks = nullptr;473474mNumActiveBodies = 0;475mNumConstraints = 0;476mMaxContacts = 0;477mNumContacts = 0;478mNumIslands = 0;479}480481JPH_NAMESPACE_END482483484