Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/IslandBuilder.cpp
9912 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{99JPH_PROFILE_FUNCTION();100101// Both need to be active, we don't want to create an island with static objects102if (inFirst >= mMaxActiveBodies || inSecond >= mMaxActiveBodies)103return;104105#ifdef JPH_VALIDATE_ISLAND_BUILDER106// Add link to the validation list107if (mNumLinkValidation < uint32(mMaxContacts))108mLinkValidation[mNumLinkValidation++] = { inFirst, inSecond };109else110JPH_ASSERT(false, "Out of links");111#endif112113// Start the algorithm with the two bodies114uint32 first_link_to = inFirst;115uint32 second_link_to = inSecond;116117for (;;)118{119// Follow the chain until we get to the body with lowest index120// If the swap compare below fails, we'll keep searching from the lowest index for the new lowest index121first_link_to = GetLowestBodyIndex(first_link_to);122second_link_to = GetLowestBodyIndex(second_link_to);123124// If the targets are the same, the bodies are already connected125if (first_link_to != second_link_to)126{127// We always link the highest to the lowest128if (first_link_to < second_link_to)129{130// Attempt to link the second to the first131// Since we found this body to be at the end of the chain it must point to itself, and if it132// doesn't it has been reparented and we need to retry the algorithm133if (!mBodyLinks[second_link_to].mLinkedTo.compare_exchange_weak(second_link_to, first_link_to, memory_order_relaxed))134continue;135}136else137{138// Attempt to link the first to the second139// Since we found this body to be at the end of the chain it must point to itself, and if it140// doesn't it has been reparented and we need to retry the algorithm141if (!mBodyLinks[first_link_to].mLinkedTo.compare_exchange_weak(first_link_to, second_link_to, memory_order_relaxed))142continue;143}144}145146// Linking succeeded!147// Chains of bodies can become really long, resulting in an O(N) loop to find the lowest body index148// to prevent this we attempt to update the link of the bodies that were passed in to directly point149// to the lowest index that we found. If the value became lower than our lowest link, some other150// thread must have relinked these bodies in the mean time so we won't update the value.151uint32 lowest_link_to = min(first_link_to, second_link_to);152AtomicMin(mBodyLinks[inFirst].mLinkedTo, lowest_link_to, memory_order_relaxed);153AtomicMin(mBodyLinks[inSecond].mLinkedTo, lowest_link_to, memory_order_relaxed);154break;155}156}157158void IslandBuilder::LinkConstraint(uint32 inConstraintIndex, uint32 inFirst, uint32 inSecond)159{160LinkBodies(inFirst, inSecond);161162JPH_ASSERT(inConstraintIndex < mNumConstraints);163uint32 min_value = min(inFirst, inSecond); // Use fact that invalid index is 0xffffffff, we want the active body of two164JPH_ASSERT(min_value != Body::cInactiveIndex); // At least one of the bodies must be active165mConstraintLinks[inConstraintIndex] = min_value;166}167168void IslandBuilder::LinkContact(uint32 inContactIndex, uint32 inFirst, uint32 inSecond)169{170JPH_ASSERT(inContactIndex < mMaxContacts);171mContactLinks[inContactIndex] = min(inFirst, inSecond); // Use fact that invalid index is 0xffffffff, we want the active body of two172}173174#ifdef JPH_VALIDATE_ISLAND_BUILDER175176void IslandBuilder::ValidateIslands(uint32 inNumActiveBodies) const177{178JPH_PROFILE_FUNCTION();179180// Go through all links so far181for (uint32 i = 0; i < mNumLinkValidation; ++i)182{183// If the bodies in this link ended up in different groups we have a problem184if (mBodyLinks[mLinkValidation[i].mFirst].mIslandIndex != mBodyLinks[mLinkValidation[i].mSecond].mIslandIndex)185{186Trace("Fail: %u, %u", mLinkValidation[i].mFirst, mLinkValidation[i].mSecond);187Trace("Num Active: %u", inNumActiveBodies);188189for (uint32 j = 0; j < mNumLinkValidation; ++j)190Trace("builder.Link(%u, %u);", mLinkValidation[j].mFirst, mLinkValidation[j].mSecond);191192IslandBuilder tmp;193tmp.Init(inNumActiveBodies);194for (uint32 j = 0; j < mNumLinkValidation; ++j)195{196Trace("Link %u -> %u", mLinkValidation[j].mFirst, mLinkValidation[j].mSecond);197tmp.LinkBodies(mLinkValidation[j].mFirst, mLinkValidation[j].mSecond);198for (uint32 t = 0; t < inNumActiveBodies; ++t)199Trace("%u -> %u", t, (uint32)tmp.mBodyLinks[t].mLinkedTo);200}201202JPH_ASSERT(false, "IslandBuilder validation failed");203}204}205}206207#endif208209void IslandBuilder::BuildBodyIslands(const BodyID *inActiveBodies, uint32 inNumActiveBodies, TempAllocator *inTempAllocator)210{211JPH_PROFILE_FUNCTION();212213// Store the amount of active bodies214mNumActiveBodies = inNumActiveBodies;215216// Create output arrays for body ID's, don't call constructors217JPH_ASSERT(mBodyIslands == nullptr);218mBodyIslands = (BodyID *)inTempAllocator->Allocate(inNumActiveBodies * sizeof(BodyID));219220// 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.221// Note: We allocate 1 extra entry because we always increment the count of the next island.222uint32 *body_island_starts = (uint32 *)inTempAllocator->Allocate((inNumActiveBodies + 1) * sizeof(uint32));223224// First island always starts at 0225body_island_starts[0] = 0;226227// Calculate island index for all bodies228JPH_ASSERT(mNumIslands == 0);229for (uint32 i = 0; i < inNumActiveBodies; ++i)230{231BodyLink &link = mBodyLinks[i];232uint32 s = link.mLinkedTo.load(memory_order_relaxed);233if (s != i)234{235// 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)236JPH_ASSERT(s < uint32(i));237uint32 island_index = mBodyLinks[s].mIslandIndex;238link.mIslandIndex = island_index;239240// Increment the start of the next island241body_island_starts[island_index + 1]++;242}243else244{245// Does not link to other body, this is the start of a new island246link.mIslandIndex = mNumIslands;247++mNumIslands;248249// Set the start of the next island to 1250body_island_starts[mNumIslands] = 1;251}252}253254#ifdef JPH_VALIDATE_ISLAND_BUILDER255ValidateIslands(inNumActiveBodies);256#endif257258// Make the start array absolute (so far we only counted)259for (uint32 island = 1; island < mNumIslands; ++island)260body_island_starts[island] += body_island_starts[island - 1];261262// Convert the to a linear list grouped by island263for (uint32 i = 0; i < inNumActiveBodies; ++i)264{265BodyLink &link = mBodyLinks[i];266267// Copy the body to the correct location in the array and increment it268uint32 &start = body_island_starts[link.mIslandIndex];269mBodyIslands[start] = inActiveBodies[i];270start++;271272// Reset linked to field for the next update273link.mLinkedTo.store(i, memory_order_relaxed);274}275276// We should now have a full array277JPH_ASSERT(mNumIslands == 0 || body_island_starts[mNumIslands - 1] == inNumActiveBodies);278279// We've incremented all body indices so that they now point at the end instead of the starts280JPH_ASSERT(mBodyIslandEnds == nullptr);281mBodyIslandEnds = body_island_starts;282}283284void IslandBuilder::BuildConstraintIslands(const uint32 *inConstraintToBody, uint32 inNumConstraints, uint32 *&outConstraints, uint32 *&outConstraintsEnd, TempAllocator *inTempAllocator) const285{286JPH_PROFILE_FUNCTION();287288// Check if there's anything to do289if (inNumConstraints == 0)290return;291292// Create output arrays for constraints293// Note: For the end indices we allocate 1 extra entry so we don't have to do an if in the inner loop294uint32 *constraints = (uint32 *)inTempAllocator->Allocate(inNumConstraints * sizeof(uint32));295uint32 *constraint_ends = (uint32 *)inTempAllocator->Allocate((mNumIslands + 1) * sizeof(uint32));296297// Reset sizes298for (uint32 island = 0; island < mNumIslands; ++island)299constraint_ends[island] = 0;300301// Loop over array and increment start relative position for the next island302for (uint32 constraint = 0; constraint < inNumConstraints; ++constraint)303{304uint32 body_idx = inConstraintToBody[constraint];305uint32 next_island_idx = mBodyLinks[body_idx].mIslandIndex + 1;306JPH_ASSERT(next_island_idx <= mNumIslands);307constraint_ends[next_island_idx]++;308}309310// Make start positions absolute311for (uint32 island = 1; island < mNumIslands; ++island)312constraint_ends[island] += constraint_ends[island - 1];313314// Loop over array and collect constraints315for (uint32 constraint = 0; constraint < inNumConstraints; ++constraint)316{317uint32 body_idx = inConstraintToBody[constraint];318uint32 island_idx = mBodyLinks[body_idx].mIslandIndex;319constraints[constraint_ends[island_idx]++] = constraint;320}321322JPH_ASSERT(outConstraints == nullptr);323outConstraints = constraints;324JPH_ASSERT(outConstraintsEnd == nullptr);325outConstraintsEnd = constraint_ends;326}327328void IslandBuilder::SortIslands(TempAllocator *inTempAllocator)329{330JPH_PROFILE_FUNCTION();331332if (mNumContacts > 0 || mNumConstraints > 0)333{334// Allocate mapping table335JPH_ASSERT(mIslandsSorted == nullptr);336mIslandsSorted = (uint32 *)inTempAllocator->Allocate(mNumIslands * sizeof(uint32));337338// Initialize index339for (uint32 island = 0; island < mNumIslands; ++island)340mIslandsSorted[island] = island;341342// Determine the sum of contact constraints / constraints per island343uint32 *num_constraints = (uint32 *)inTempAllocator->Allocate(mNumIslands * sizeof(uint32));344if (mNumContacts > 0 && mNumConstraints > 0)345{346num_constraints[0] = mConstraintIslandEnds[0] + mContactIslandEnds[0];347for (uint32 island = 1; island < mNumIslands; ++island)348num_constraints[island] = mConstraintIslandEnds[island] - mConstraintIslandEnds[island - 1]349+ mContactIslandEnds[island] - mContactIslandEnds[island - 1];350}351else if (mNumContacts > 0)352{353num_constraints[0] = mContactIslandEnds[0];354for (uint32 island = 1; island < mNumIslands; ++island)355num_constraints[island] = mContactIslandEnds[island] - mContactIslandEnds[island - 1];356}357else358{359num_constraints[0] = mConstraintIslandEnds[0];360for (uint32 island = 1; island < mNumIslands; ++island)361num_constraints[island] = mConstraintIslandEnds[island] - mConstraintIslandEnds[island - 1];362}363364// Sort so the biggest islands go first, this means that the jobs that take longest will be running365// first which improves the chance that all jobs finish at the same time.366QuickSort(mIslandsSorted, mIslandsSorted + mNumIslands, [num_constraints](uint32 inLHS, uint32 inRHS) {367return num_constraints[inLHS] > num_constraints[inRHS];368});369370inTempAllocator->Free(num_constraints, mNumIslands * sizeof(uint32));371}372}373374void IslandBuilder::Finalize(const BodyID *inActiveBodies, uint32 inNumActiveBodies, uint32 inNumContacts, TempAllocator *inTempAllocator)375{376JPH_PROFILE_FUNCTION();377378mNumContacts = inNumContacts;379380BuildBodyIslands(inActiveBodies, inNumActiveBodies, inTempAllocator);381BuildConstraintIslands(mConstraintLinks, mNumConstraints, mConstraintIslands, mConstraintIslandEnds, inTempAllocator);382BuildConstraintIslands(mContactLinks, mNumContacts, mContactIslands, mContactIslandEnds, inTempAllocator);383SortIslands(inTempAllocator);384385mNumPositionSteps = (uint8 *)inTempAllocator->Allocate(mNumIslands * sizeof(uint8));386}387388void IslandBuilder::GetBodiesInIsland(uint32 inIslandIndex, BodyID *&outBodiesBegin, BodyID *&outBodiesEnd) const389{390JPH_ASSERT(inIslandIndex < mNumIslands);391uint32 sorted_index = mIslandsSorted != nullptr? mIslandsSorted[inIslandIndex] : inIslandIndex;392outBodiesBegin = sorted_index > 0? mBodyIslands + mBodyIslandEnds[sorted_index - 1] : mBodyIslands;393outBodiesEnd = mBodyIslands + mBodyIslandEnds[sorted_index];394}395396bool IslandBuilder::GetConstraintsInIsland(uint32 inIslandIndex, uint32 *&outConstraintsBegin, uint32 *&outConstraintsEnd) const397{398JPH_ASSERT(inIslandIndex < mNumIslands);399if (mNumConstraints == 0)400{401outConstraintsBegin = nullptr;402outConstraintsEnd = nullptr;403return false;404}405else406{407uint32 sorted_index = mIslandsSorted[inIslandIndex];408outConstraintsBegin = sorted_index > 0? mConstraintIslands + mConstraintIslandEnds[sorted_index - 1] : mConstraintIslands;409outConstraintsEnd = mConstraintIslands + mConstraintIslandEnds[sorted_index];410return outConstraintsBegin != outConstraintsEnd;411}412}413414bool IslandBuilder::GetContactsInIsland(uint32 inIslandIndex, uint32 *&outContactsBegin, uint32 *&outContactsEnd) const415{416JPH_ASSERT(inIslandIndex < mNumIslands);417if (mNumContacts == 0)418{419outContactsBegin = nullptr;420outContactsEnd = nullptr;421return false;422}423else424{425uint32 sorted_index = mIslandsSorted[inIslandIndex];426outContactsBegin = sorted_index > 0? mContactIslands + mContactIslandEnds[sorted_index - 1] : mContactIslands;427outContactsEnd = mContactIslands + mContactIslandEnds[sorted_index];428return outContactsBegin != outContactsEnd;429}430}431432void IslandBuilder::ResetIslands(TempAllocator *inTempAllocator)433{434JPH_PROFILE_FUNCTION();435436inTempAllocator->Free(mNumPositionSteps, mNumIslands * sizeof(uint8));437438if (mIslandsSorted != nullptr)439{440inTempAllocator->Free(mIslandsSorted, mNumIslands * sizeof(uint32));441mIslandsSorted = nullptr;442}443444if (mContactIslands != nullptr)445{446inTempAllocator->Free(mContactIslandEnds, (mNumIslands + 1) * sizeof(uint32));447mContactIslandEnds = nullptr;448inTempAllocator->Free(mContactIslands, mNumContacts * sizeof(uint32));449mContactIslands = nullptr;450}451452if (mConstraintIslands != nullptr)453{454inTempAllocator->Free(mConstraintIslandEnds, (mNumIslands + 1) * sizeof(uint32));455mConstraintIslandEnds = nullptr;456inTempAllocator->Free(mConstraintIslands, mNumConstraints * sizeof(uint32));457mConstraintIslands = nullptr;458}459460inTempAllocator->Free(mBodyIslandEnds, (mNumActiveBodies + 1) * sizeof(uint32));461mBodyIslandEnds = nullptr;462inTempAllocator->Free(mBodyIslands, mNumActiveBodies * sizeof(uint32));463mBodyIslands = nullptr;464465inTempAllocator->Free(mConstraintLinks, mNumConstraints * sizeof(uint32));466mConstraintLinks = nullptr;467468#ifdef JPH_VALIDATE_ISLAND_BUILDER469inTempAllocator->Free(mLinkValidation, mMaxContacts * sizeof(LinkValidation));470mLinkValidation = nullptr;471#endif472473inTempAllocator->Free(mContactLinks, mMaxContacts * sizeof(uint32));474mContactLinks = nullptr;475476mNumActiveBodies = 0;477mNumConstraints = 0;478mMaxContacts = 0;479mNumContacts = 0;480mNumIslands = 0;481}482483JPH_NAMESPACE_END484485486