UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
SpatialAccelerationBroadPhase.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2#pragma once
3
11#include "Chaos/PBDRigidsSOAs.h"
12#include "Chaos/Capsule.h"
13#include "ChaosStats.h"
16#include "Chaos/AABBTree.h"
17#include "Tasks/Task.h"
18
19// Set to 1 to enable some sanity chacks on the filtered broadphase results
20#ifndef CHAOS_CHECK_BROADPHASE
21#define CHAOS_CHECK_BROADPHASE 0
22#endif
23
24namespace Chaos::CVars
25{
29}
30
31namespace Chaos
32{
33 template <typename TPayloadType, typename T, int d>
34 class ISpatialAcceleration;
35 class IResimCacheBase;
36
37 namespace Private
38 {
39
44 const FGeometryParticleHandle* Particle1,
46 const FIgnoreCollisionManager& IgnoreCollisionManager,
47 const bool bIsResimming,
48 bool& bOutSwapOrder)
49 {
50 bOutSwapOrder = false;
51
52 if (!ParticlePairBroadPhaseFilter(Particle1, Particle2, &IgnoreCollisionManager))
53 {
54 return false;
55 }
56
57 bool bIsMovingKinematic1 = false;
58 bool bIsDynamicAwake1 = false;
59 bool bIsDynamicAsleep1 = false;
61 if (Rigid1 != nullptr)
62 {
63 bIsMovingKinematic1 = Rigid1->IsMovingKinematic();
64 bIsDynamicAsleep1 = Rigid1->IsDynamic() && Rigid1->IsSleeping();
65 bIsDynamicAwake1 = Rigid1->IsDynamic() && !Rigid1->IsSleeping();;
66 }
67
68 bool bIsMovingKinematic2 = false;
69 bool bIsDynamicAwake2 = false;
70 bool bIsDynamicAsleep2 = false;
72 if (Rigid2 != nullptr)
73 {
74 bIsMovingKinematic2 = Rigid2->IsMovingKinematic();
75 bIsDynamicAsleep2 = Rigid2->IsDynamic() && Rigid2->IsSleeping();
76 bIsDynamicAwake2 = Rigid2->IsDynamic() && !Rigid2->IsSleeping();
77 }
78
79 // Used to determine a winner in cases where we visit particle pairs in both orders
81
82 bool bAcceptParticlePair = false;
83 if (!bIsResimming)
84 {
85 // Assumptions for the non-resim case:
86 // - Particle1 is the particle from an outer loop over either
87 // (A) all dynamic particles, or
88 // (B) all awake dynamic and moving kinematic particles.
89 // - Particle2 is one of the particles whose bounds overlaps Particle1
90
91 // If the first particle is an awake dynamic
92 // - accept if the other particle is an awake dynamic and we are the preferred particle (lower ID)
93 // - accept if the other particle is in any other state (static, kinematic, asleep)
95 {
97 {
99 }
100 else
101 {
102 bAcceptParticlePair = true;
103 }
104 }
105 // If the first particle is an asleep dynamic
106 // - accept if the other particle is a moving kinematic
107 // - reject if the other particle is a stationary kinematic or static - sleeping and static particles do not collide
108 // - reject if the other particle is an awake dynamic - will be picked up when visiting in the opposite order
109 // - reject if the other particle if an asleep dynamic - two sleeping dynamics do not collide
110 else if (bIsDynamicAsleep1)
111 {
113 }
114 // If the first particle is a moving kinematic
115 // - accept if the other particle is sleeping dynamic
116 // - reject if the other particle is an awake dynamic - will be picked up when visiting in the opposite order
117 // - reject if the other particle is a kinematic or static - they do not collide
118 else if (bIsMovingKinematic1)
119 {
121 }
122 // If the first particle is static or non-moving kinematic
123 // - reject always - will be picked up when visiting in the opposite order
124 else
125 {
126 }
127 }
128 else
129 {
130 // When resimming we iterate over "desynced" particles which may be kinematic so:
131 // - Particle1 is always desynced
132 // - Particle2 may also be desynced, in which case we will also visit the opposite ordering regardless of dynamic/kinematic status
133 // - Particle1 may be static, kinematic, dynamic, asleep
134 // - Particle2 may be static, kinematic, dynamic, asleep
135 //
136 // Even though Particle1 may be kinematic when resimming, we want to create the contacts in the original order (i.e., dynamic first)
137 //
138 const bool bIsParticle2Desynced = bIsResimming && (Particle2->SyncState() == ESyncState::HardDesync);
141
142 // If Particle1 is dynamic, accept if the other is asleep or nor dynamic
144 {
145 bAcceptParticlePair = true;
146 }
147
148 // If Particle1 is non dynamic but particle 2 is dynamic, the case should already be handled by (1) for
149 // the desynced dynamic - synced/desynced (static,kinematic,asleep) pairs. But we still need to process
150 // the desynced (static,kinematic,asleep) against the synced dynamic since this case have not been handled by (1)
152 {
154 }
155 // If Particle1 and Particle2 are dynamic we validate the pair if particle1 has higher ID to discard duplicates since we will visit twice the pairs
156 // We validate the pairs as well if the particle 2 is synced since we will never visit the opposite order (we only iterate over desynced particles)
158 {
160 }
161 // If Particle1 is kinematic we are discarding the pairs against sleeping desynced particles since the case has been handled by (2)
163 {
165 }
166 }
167
168 // Since particles can change type (between Kinematic and Dynamic) we may visit them in different orders at different times, but if we allow
169 // that it would break Resim and constraint reuse. Also, if only one particle is Dynamic, we want it in first position. This isn't a strtct
170 // requirement but some downstream systems assume this is true (e.g., CCD, TriMesh collision).
172 {
174 }
175
176 return bAcceptParticlePair;
177 }
178
183 {
184 public:
186 {
187 }
188
195
196 void ApplyFilter(const FIgnoreCollisionManager& IgnoreCollisionManager, const bool bIsResimming)
197 {
198 bool bSwapOrder = false;
199
200 bCollisionsEnabled = Private::ParticlePairCollisionAllowed(Particles[0], Particles[1], IgnoreCollisionManager, bIsResimming, bSwapOrder);
201
202 if (bSwapOrder)
203 {
204 Swap(Particles[0], Particles[1]);
206 }
207 }
208
212 };
213
235
240 {
242 : Context(InContext)
243 , ParticleUniqueIdx(ParticleHandle ? ParticleHandle->UniqueIdx() : FUniqueIdx(0))
244 , AccelerationHandle(ParticleHandle)
245 {
246 // Should never be null. Also suppresses a warning about possible null ptr.
247 check(ParticleHandle != nullptr);
251 }
252
253 UE_DEPRECATED(5.7, "Use the constructor that does not take the sim filter.")
258
260 {
262 FGeometryParticleHandle* Particle2 = Instance.Payload.GetGeometryParticleHandle_PhysicsThread();
263
264 Context.Overlaps.Emplace(Particle1, Particle2, 0);
265
266 return true;
267 }
268
274
280
281 const void* GetQueryData() const { return nullptr; }
282
283 const void* GetSimData() const { return &SimFilterData; }
284
286 {
287 return Instance.Payload.UniqueIdx() == ParticleUniqueIdx;
288 }
290 const void* GetQueryPayload() const
291 {
292 return &AccelerationHandle;
293 }
294
295 bool HasBlockingHit() const
296 {
297 return false;
298 }
299
301 {
302 if (ShouldIgnore(Payload))
303 {
304 return true;
305 }
307 return Payload.PrePreSimFilter(SimFilterData);
309 }
310
311 private:
313 // @todo(chaos): cache this on the particle?
314 FCollisionFilterData SimFilterData;
315 FUniqueIdx ParticleUniqueIdx; // unique id of the particle visiting, used to skip self intersection as early as possible
316
318 FAccelerationStructureHandle AccelerationHandle;
319 };
320 }
321
322 template <>
324 {
326 return Visitor.PrePreFilter(Payload);
328 }
329
335 {
336 public:
338
340 : Particles(InParticles)
341 , SpatialAcceleration(nullptr)
342 , NumActiveBroadphaseContexts(0)
343 , bNeedsResim(false)
344 {
345 }
346
351
356 FReal Dt,
358 const FCollisionDetectorSettings& Settings,
360 {
363
364 if (!ensure(SpatialAcceleration))
365 {
366 // Must call SetSpatialAcceleration
367 return;
368 }
369
370 bNeedsResim = ResimCache && ResimCache->IsResimming();
371
372 // Reset stats
373 NumBroadPhasePairs = 0;
374 NumMidPhases = 0;
375
376 {
378
379 if (const auto AABBTree = SpatialAcceleration->template As<TAABBTree<FAccelerationStructureHandle, TAABBTreeLeafArray<FAccelerationStructureHandle>>>())
380 {
382 }
383 else if (const auto BV = SpatialAcceleration->template As<TBoundingVolume<FAccelerationStructureHandle>>())
384 {
385 ProduceOverlaps(Dt, *BV, Allocator, Settings, ResimCache);
386 }
387 else if (const auto AABBTreeBV = SpatialAcceleration->template As<TAABBTree<FAccelerationStructureHandle, TBoundingVolume<FAccelerationStructureHandle>>>())
388 {
390 }
391 else if (const auto Collection = SpatialAcceleration->template As<ISpatialAccelerationCollection<FAccelerationStructureHandle, FReal, 3>>())
392 {
393 Collection->PBDComputeConstraintsLowLevel(Dt, *this, Allocator, Settings, ResimCache);
394 }
395 else
396 {
397 check(false); //question: do we want to support a dynamic dispatch version?
398 }
399 }
400 {
403
405 const int32 NumContextTask = PendingTasks.Num();
406 check(NumContextTask <= NumActiveBroadphaseContexts);
407 for (int32 ContextIndex = 0; ContextIndex < NumContextTask; ContextIndex++)
408 {
409 Private::FBroadPhaseContext& BroadPhaseContext = BroadphaseContexts[ContextIndex];
410
412 {
414 AssignMidPhases(BroadPhaseContext);
415 }, PendingTasks[ContextIndex]);
417 }
418 // It is required to wait for the middle phase modifiers, which is a callback to modify middle phases.
419 // This could be removed by adding dependent tasks for the middle phase modifiers.
421 }
422
423 // Run some error checks in non-shipping builds
424 // NOTE: This must come after MidPhase assignment for now because that's where the filter is applied
425 CheckOverlapResults();
426
427 // Update stats
428 for (int32 ContextIndex = 0; ContextIndex < NumActiveBroadphaseContexts; ++ContextIndex)
429 {
430 NumBroadPhasePairs += BroadphaseContexts[ContextIndex].Overlaps.Num();
431 NumMidPhases += BroadphaseContexts[ContextIndex].MidPhases.Num();
432 }
433 }
434
439 {
442
443 // The broadphase overlaps probably resulted in a very different number of MidPhases
444 // (overlaps) on each thread. Redistribute the MidPhases so that we get more even
445 // processing. NOTE: This does not take into account that some MidPhases may be expensive.
446 // For that we would need to queue and redistribute the NarrowPhases, but currently each
447 // MidPhase runs the NarrowPhase in ProcessMidPhase
448 RedistributeMidPhasesInContexts();
449 const int32 NumContextTask = PendingTasks.Num();
450 PendingTasks.Reset();
451 ContextsWithConstraints.Reset();
452 check(NumContextTask <= NumActiveBroadphaseContexts);
453 for (int32 ContextIndex = 0; ContextIndex < NumContextTask; ContextIndex++)
454 {
455 Private::FBroadPhaseContext& BroadphaseContext = BroadphaseContexts[ContextIndex];
456 // Accessing the overlaps array is safe here, all overlaps have been finished to be computed at this point
457 if (!BroadphaseContext.Overlaps.IsEmpty())
458 {
460 {
462 ProcessMidPhases(Dt, BroadphaseContext);
463 }, UE::Tasks::ETaskPriority::High);
464 PendingTasks.Add(ProcessMidPhaseTask);
465 ContextsWithConstraints.Add(ContextIndex);
466 }
467 }
468 }
469
470 void GatherConstraints(bool bIsDeterministic)
471 {
472 // This lambda merge constraints of 2 broad phase context.
473 // If it is deterministic the lambda assume they are already sorted and merge them in the first given context
474 // maintaining the sorted data.
475 auto MergeLambda = [this, bIsDeterministic](int32 SmallIndexToMerge, int32 BigIndexToMerge)
476 {
480
481 TArray<FPBDCollisionConstraint*>& ThisArray = Context.CollisionContext.Allocator->NewActiveConstraints;
482 TArray<FPBDCollisionConstraint*>& OtherArray = OtherContext.CollisionContext.Allocator->NewActiveConstraints;
483
484 if (bIsDeterministic)
485 {
487 const int32 NumThis = ThisArray.Num();
488 const int32 NumOther = OtherArray.Num();
489 ResultArray.Reserve(NumThis + NumOther);
490
491 int32 ThisIndex = 0;
492 int32 OtherIndex = 0;
493
494 while (ThisIndex < NumThis || OtherIndex < NumOther)
495 {
496 if (ThisIndex < NumThis && (OtherIndex >= NumOther ||
497 ThisArray[ThisIndex]->GetContact().GetCollisionSortKey() < OtherArray[OtherIndex]->GetContact().GetCollisionSortKey()))
498 {
499 ResultArray.Add(ThisArray[ThisIndex++]);
500 }
501 else
502 {
503 check(OtherIndex < NumOther);
504 ResultArray.Add(OtherArray[OtherIndex++]);
505 }
506 }
507 ThisArray = MoveTemp(ResultArray);
508 }
509 else
510 {
511 ThisArray.Append(OtherArray);
512 }
513 OtherArray.Reset();
514 };
515
516 // This atomic is use to merge the sorted data in a safe multithreaded program.
517 // When reaching the end of a process,
518 // - either this atomic is INDEX_NONE, meaning there is no other batch to be merged with. So this atomic take the value of the batch
519 // - either this atomic is the index to be merged with, so the merging is called with this current index, and the one stored in the atomic.
520 std::atomic<int32> NextBatchToMerge = INDEX_NONE;
521 if (bIsDeterministic)
522 {
524 for (int32 ContextIndex = 0; ContextIndex < PendingTasks.Num(); ContextIndex++)
525 {
526 int32 CurrentIndex = ContextsWithConstraints[ContextIndex];
527 Private::FBroadPhaseContext& BroadphaseContext = BroadphaseContexts[CurrentIndex];
529 {
531 BroadphaseContext.CollisionContext.Allocator->NewActiveConstraints.Sort(
533 {
534 return L.GetContact().GetCollisionSortKey() < R.GetContact().GetCollisionSortKey();
535 });
536 int32 NextIndexToMerge = CurrentIndex;
537 while (true)
538 {
539 int32 NextExpected = NextBatchToMerge.load(std::memory_order_relaxed);
540 // If there is a sorted batch ready to be merged, lets merge it
542 {
543 // Make sure the value hasn't been overwritten by another thread otherwise, retry
544 if (NextBatchToMerge.compare_exchange_weak(NextExpected, INDEX_NONE, std::memory_order_relaxed))
545 {
551 }
552 }
553 else // Otherwise if no batch are available set the current one as the next available for merging
554 {
555 int32 IndexNone = INDEX_NONE;
556 // Make sure there is no available one, otherwise retry
557 if (NextBatchToMerge.compare_exchange_weak(IndexNone, NextIndexToMerge, std::memory_order_relaxed))
558 {
559 break;
560 }
561 }
562 }
563 }, PendingTasks[ContextIndex]);
565 }
566 PendingTasks = MoveTemp(ProcessSorting);
567 }
568 else // For no deterministic program merge all context in new tasks, without sorting them
569 {
570 // Create a binary tree of task to merge all sorted constraints
572 while (PendingTasks.Num() >= 2)
573 {
574 int32 NumProcessSorting = PendingTasks.Num();
576 // We merge data in the context that have a power of 2 index, so for first layer 0, 2, 4..., for the second layer 0, 4, 8... and so on.
577 for (int32 ProcessIndex = 0, ContextIndex = 0; ProcessIndex < NumProcessSorting - 1; ProcessIndex += 2, ContextIndex += 2 * NeighborStep)
578 {
579 TArray<UE::Tasks::FTask> Preriq{ PendingTasks[ProcessIndex], PendingTasks[ProcessIndex + 1] };
580 int32 SmallIndexToMerge = ContextsWithConstraints[ContextIndex];
581 int32 BigIndexToMerge = ContextsWithConstraints[ContextIndex + NeighborStep];
583 {
586
587 }, Preriq);
589 }
590 // Add the last one for odd process number
591 if (NumProcessSorting % 2 == 1)
592 {
593 ProcessMerging.Add(PendingTasks[NumProcessSorting - 1]);
594 }
595 PendingTasks = MoveTemp(ProcessMerging);
596 NeighborStep *= 2;
597 }
598 ensure(PendingTasks.Num() <= 1);
599 }
600 UE::Tasks::Wait(PendingTasks);
601
602 // Lets move all constraints in the first allocator to be picked up by the CollisionConstraintAllocator
603 if (!ContextsWithConstraints.IsEmpty() && ContextsWithConstraints[0] != 0)
604 {
605 BroadphaseContexts[0].CollisionContext.Allocator->NewActiveConstraints =
606 MoveTemp(BroadphaseContexts[ContextsWithConstraints[0]].CollisionContext.Allocator->NewActiveConstraints);
607 }
608 }
609
617 template<bool bOnlyRigid, typename ViewType, typename SpatialAccelerationType>
619 ViewType& OverlapView,
620 FReal Dt,
623 const FCollisionDetectorSettings& Settings)
624 {
625 // Reset all the contexts (we don't always use all of them, but don't reduce the array size so that the element arrays don't need reallocating)
626 for (Private::FBroadPhaseContext& BroadphaseContext : BroadphaseContexts)
627 {
628 BroadphaseContext.Reset();
629 }
630
631 // The number of contexts that may have new overlaps in them
632 NumActiveBroadphaseContexts = !!GSingleThreadedPhysics ? 1 : FMath::Max(FMath::Min(Chaos::CVars::NumWorkerCollisionFactor * FTaskGraphInterface::Get().GetNumWorkerThreads(), Chaos::MaxNumWorkers), 1);
633
634 BroadphaseContexts.SetNum(NumActiveBroadphaseContexts);
635 Allocator->SetMaxContexts(NumActiveBroadphaseContexts);
636
637 for (int32 Index = 0; Index < NumActiveBroadphaseContexts; Index++)
638 {
640 BroadphaseContexts[Index].CollisionContext.SetSettings(Settings);
641 BroadphaseContexts[Index].CollisionContext.SetAllocator(ContextAllocator);
642 }
643
644 using TSOA = typename ViewType::TSOA;
645 using THandle = typename TSOA::THandleType;
646 using THandleBase = typename THandle::THandleBase;
647 PendingTasks.Reset();
648
649 int32 ContextIndex = 0;
650
651 for (int32 ViewIndex = 0; ViewIndex < OverlapView.SOAViews.Num(); ++ViewIndex)
652 {
653 const TSOAView<TSOA>& SOAView = OverlapView.SOAViews[ViewIndex];
654 const int32 NumParticles = SOAView.Size();
655 if (NumParticles == 0)
656 {
657 continue;
658 }
659 // Dispatch tasks according if it is an array of handles or a SOA
660 if (const TArray<THandle*>* CurHandlesArray = SOAView.HandlesArray)
661 {
662 auto ReturnHandle = [CurHandlesArray](int32 Index) -> THandleBase
663 {
664 THandle* HandlePtr = (*CurHandlesArray)[Index];
665 return THandleBase(HandlePtr->GeometryParticles, HandlePtr->ParticleIdx);
666 };
667 using LambdaType = decltype(ReturnHandle);
668 const int32 NumHandles = CurHandlesArray->Num();
670 }
671 else if (TSOA* Soa = SOAView.SOA)
672 {
673 auto ReturnHandle = [Soa](int32 Index) { return THandleBase(Soa, Index); };
674 using LambdaType = decltype(ReturnHandle);
676 }
677 }
678 }
679
680 template<typename T_SPATIALACCELERATION>
682 FReal Dt,
685 const FCollisionDetectorSettings& Settings,
687 )
688 {
689 // Select the set of particles that we loop over in the outer collision detection loop.
690 // The goal is to detection all required collisions (dynamic-vs-everything) while not
691 // visiting pairs that cannot collide (e.g., kinemtic-kinematic, or kinematic-sleeping for
692 // stationary kinematics)
694 if(!bNeedsResim)
695 {
698
699 // Usually we ignore sleeping particles in the outer loop and iterate over awake-dynamics and moving-kinematics.
700 // However, for scenes with a very large number of moving kinematics, it is faster to loop over awake-dynamics
701 // and sleeping-dynamics, even though this means we visit sleeping pairs.
703 {
705 }
706 else
707 {
709 }
710 }
711 else
712 {
713 const TParticleView<TGeometryParticles<FReal, 3>>& DesyncedView = ResimCache->GetDesyncedView();
714
716 }
717 }
718
719 FIgnoreCollisionManager& GetIgnoreCollisionManager() { return IgnoreCollisionManager; }
720
721 // Stats
723 {
724 return NumBroadPhasePairs;
725 }
726
728 {
729 return NumMidPhases;
730 }
731
732 private:
733 template<bool bOnlyRigid, typename SpatialAccelerationType, typename THandle, typename LambdaType>
734 void DispatchTasks(int32& ContextIndex, int32 NumParticles, const SpatialAccelerationType& InSpatialAcceleration, FReal Dt, LambdaType ReturnHandle)
735 {
736 using THandleBase = typename THandle::THandleBase;
737 using TTransientHandle = typename THandle::TTransientHandle;
738
739 const int32 ParticleByContext = FMath::Max(FMath::DivideAndRoundUp(NumParticles, NumActiveBroadphaseContexts), SmallBatchSize);
741
742 for (int32 BatchIndex = 0; BatchIndex < NumBatch; ContextIndex++, BatchIndex++)
743 {
744 if (ContextIndex >= NumActiveBroadphaseContexts)
745 {
746 ContextIndex = 0;
747 }
748
749 int32 EndIndex = (BatchIndex + 1) * ParticleByContext;
750 EndIndex = BatchIndex == NumBatch - 1 ? FMath::Min(NumParticles, EndIndex) : EndIndex;
751
752 if (ContextIndex >= PendingTasks.Num())
753 {
754 Private::FBroadPhaseContext& BroadPhaseContext = BroadphaseContexts[ContextIndex];
755 const int32 StartIndex = BatchIndex * ParticleByContext;
757 {
759 for (int32 Index = StartIndex; Index < EndIndex; Index++)
760 {
761 THandleBase Handle = ReturnHandle(Index);
762 TTransientHandle& TransientHandle = static_cast<TTransientHandle&>(Handle);
763 if (!TransientHandle.LightWeightDisabled())
764 {
766 }
767 }
768 });
769 PendingTasks.Add(PendingTask);
770 }
771 else
772 {
773
774 // If all context threads has been filled, create other tasks and link them to the previous one,
775 Private::FBroadPhaseContext& BroadPhaseContext = BroadphaseContexts[ContextIndex];
776 const int32 StartIndex = BatchIndex * ParticleByContext;
778 {
780 for (int32 Index = StartIndex; Index < EndIndex; Index++)
781 {
782 THandleBase Handle = ReturnHandle(Index);
783 TTransientHandle& TransientHandle = static_cast<TTransientHandle&>(Handle);
784 if (!TransientHandle.LightWeightDisabled())
785 {
787 }
788 }
789 }, PendingTasks[ContextIndex]);
790
791 PendingTasks[ContextIndex] = PendingTask;
792 }
793 }
794 }
795
796 // Generate the set of particles that overlap the specified particle and are allowed to collide with it
797 template<bool bOnlyRigid, typename T_SPATIALACCELERATION>
798 void ProduceParticleOverlaps(
799 FReal Dt,
800 FGeometryParticleHandle* Particle1,
802 Private::FBroadPhaseContext& Context)
803 {
804 // @todo(chaos):We shouldn't need this data here (see uses below)
805 bool bIsKinematic1 = true;
806 bool bIsDynamicAwake1 = false;
807 bool bIsDynamicAsleep1 = false;
808 bool bDisabled1 = false;
809 const FPBDRigidParticleHandle* Rigid1 = Particle1->CastToRigidParticle();
810 if (Rigid1 != nullptr)
811 {
812 bIsKinematic1 = Rigid1->IsKinematic();
813 bIsDynamicAsleep1 = !bIsKinematic1 && Rigid1->IsSleeping();
815 bDisabled1 = Rigid1->Disabled();
816 }
817
818 // Skip particles we are not interested in
819 // @todo(chaos) Ideally we would we should not be getting disabled particles, but we currently do from GeometryCollections.
820 if (bDisabled1)
821 {
822 return;
823 }
824
825 // @todo(chaos): This should already be handled by selecting the appropriate particle view in the calling function. GeometryCollections?
827 if (!bHasValidState && !bNeedsResim)
828 {
829 return;
830 }
831
832 const bool bBody1Bounded = Particle1->HasBounds();
833 if (bBody1Bounded)
834 {
835 const FAABB3 Box1 = Particle1->WorldSpaceInflatedBounds();
836
837 {
839 Private::FSimOverlapVisitor OverlapVisitor(Particle1, Context);
841 }
842 }
843 else
844 {
845 const auto& GlobalElems = InSpatialAcceleration.GlobalObjects();
846 Context.Overlaps.Reserve(GlobalElems.Num());
847
848 for (auto& Elem : GlobalElems)
849 {
850 if (Particle1 != Elem.Payload.GetGeometryParticleHandle_PhysicsThread())
851 {
852 Context.Overlaps.Emplace(Particle1, Elem.Payload.GetGeometryParticleHandle_PhysicsThread(), 1);
853 }
854 }
855 }
856 }
857
858 // Redistribute the midphases among the contexts to even out the per-core work a bit
859 void RedistributeMidPhasesInContexts()
860 {
862
863 if ((NumActiveBroadphaseContexts > 1) && CVars::bChaosMidPhaseRedistributionEnabled)
864 {
865 // We want this many midphases per worker
866 const int32 NumMidPhasesPerContext = FMath::DivideAndRoundUp(NumMidPhases, NumActiveBroadphaseContexts);
867
868 // Reserve array space
869 for (int32 ContextIndex = 0; ContextIndex < NumActiveBroadphaseContexts; ++ContextIndex)
870 {
871 BroadphaseContexts[ContextIndex].MidPhases.Reserve(NumMidPhasesPerContext);
872 }
873
874 // Find the over-filled arrays and move elements to the under-filled arrays
875 for (int32 SrcContextIndex = 0; SrcContextIndex < NumActiveBroadphaseContexts; ++SrcContextIndex)
876 {
877 TArray<FParticlePairMidPhase*>& SrcMidPhases = BroadphaseContexts[SrcContextIndex].MidPhases;
878 int32 SrcNum = SrcMidPhases.Num();
880 {
881 // SrcMidPhases is over-filled
882 for (int32 DstContextIndex = 0; DstContextIndex < NumActiveBroadphaseContexts; ++DstContextIndex)
883 {
884 TArray<FParticlePairMidPhase*>& DstMidPhases = BroadphaseContexts[DstContextIndex].MidPhases;
886 {
887 // DstMidPhases is under-filled, see how many elements we can move into it
889
890 // Copy the elements from the end or Src to end of Dst (we will resize SrcMidPhases at the end)
891 SrcNum -= NumToMove;
893
895 {
896 break;
897 }
898 }
899 }
900
902 SrcMidPhases.SetNum(SrcNum);
903 }
904 }
905 }
906 }
907
908 // Find or assign midphases to each of the overlapping particle pairs
909 // @todo(chaos): optimize
910 void AssignMidPhases(Private::FBroadPhaseContext& BroadphaseContext)
911 {
913
914 Private::FCollisionContextAllocator* ContextAllocator = BroadphaseContext.CollisionContext.GetAllocator();
915
916 BroadphaseContext.MidPhases.SetNum(BroadphaseContext.Overlaps.Num());
917
918 int32 MidPhaseIndex = 0;
920 {
921 Private::FBroadPhaseOverlap& Overlap = BroadphaseContext.Overlaps[OverlapIndex];
922
923 // Check to see if the two particles are allowed to collide.
924 // NOTE: this may also swap the order of the particles.
925 Overlap.ApplyFilter(IgnoreCollisionManager, bNeedsResim);
926
927 if (Overlap.bCollisionsEnabled)
928 {
930 {
931 const bool bIsOneWay0 = FConstGenericParticleHandle(Overlap.Particles[0])->OneWayInteraction();
932 const bool bIsOneWay1 = FConstGenericParticleHandle(Overlap.Particles[1])->OneWayInteraction();
933 if (bIsOneWay0 && bIsOneWay1)
934 {
935 continue;
936 }
937 }
938
939 // Get the midphase for this pair
940 FParticlePairMidPhase* MidPhase = ContextAllocator->GetMidPhase(Overlap.Particles[0], Overlap.Particles[1], Overlap.Particles[Overlap.SearchParticleIndex], BroadphaseContext.CollisionContext);
941 BroadphaseContext.MidPhases[MidPhaseIndex] = MidPhase;
942
943 CVD_TRACE_MID_PHASE(MidPhase);
944
945 ++MidPhaseIndex;
946 }
947 }
948
949 BroadphaseContext.MidPhases.SetNum(MidPhaseIndex);
950 }
951
952 // Process all the midphases: generate constraints and execute the narrowphase
953 void ProcessMidPhases(const FReal Dt, const Private::FBroadPhaseContext& BroadphaseContext)
954 {
956
957 // Prefetch initial set of MidPhases
958 const int32 PrefetchLookahead = 4;
959 const int32 NumContextMidPhases = BroadphaseContext.MidPhases.Num();
961 {
962 BroadphaseContext.MidPhases[Index]->CachePrefetch();
963 }
964
966 {
967 // Prefetch next MidPhase
969 {
970 BroadphaseContext.MidPhases[Index + PrefetchLookahead]->CachePrefetch();
971 }
972
973 FParticlePairMidPhase* MidPhase = BroadphaseContext.MidPhases[Index];
974 const FGeometryParticleHandle* Particle0 = MidPhase->GetParticle0();
975 const FGeometryParticleHandle* Particle1 = MidPhase->GetParticle1();
976 if ((Particle0 != nullptr) && (Particle1 != nullptr))
977 {
978 // Run MidPhase + NarrowPhase
979 MidPhase->GenerateCollisions(BroadphaseContext.CollisionContext.GetSettings().BoundsExpansion, Dt, BroadphaseContext.CollisionContext);
980 }
981
982 CVD_TRACE_MID_PHASE(MidPhase);
983 }
984
985 PHYSICS_CSV_CUSTOM_EXPENSIVE(PhysicsCounters, NumFromBroadphase, NumPotentials, ECsvCustomStatOp::Accumulate);
986 }
987
988 void CheckOverlapResults()
989 {
990#if CHAOS_CHECK_BROADPHASE
991 // Make sure that no particle pair is in the overlap lists twice which will cause a race condition
992 // in the narrow phase as multiple threads will attempt to build the same constraint(s) leading to
993 // buffer overruns and who knows what other craziness.
995 for (int32 ContextIndex = 0; ContextIndex < NumActiveBroadphaseContexts; ++ContextIndex)
996 {
997 for (const Private::FBroadPhaseOverlap& Overlap : BroadphaseContexts[ContextIndex].Overlaps)
998 {
999 if (Overlap.bCollisionsEnabled)
1000 {
1001 const Private::FCollisionParticlePairKey PairKey = Private::FCollisionParticlePairKey(Overlap.Particles[0], Overlap.Particles[1]);
1002 const bool bIsInSet = ParticlePairKeys.Contains(PairKey.GetKey());
1003 if (ensure(!bIsInSet))
1004 {
1005 ParticlePairKeys.Add(PairKey.GetKey());
1006 }
1007 else
1008 {
1009 // Last time this happened it was because a particle was in multiple views that should be mutually exclusive
1010 UE_LOG(LogChaos, Fatal, TEXT("Broadphase overlaps generated the same particle pair twice"));
1011 }
1012 }
1013 }
1014 }
1015#endif
1016 }
1017
1018 const FPBDRigidsSOAs& Particles;
1019 const FAccelerationStructure* SpatialAcceleration;
1020 TArray<Private::FBroadPhaseContext> BroadphaseContexts;
1021 FIgnoreCollisionManager IgnoreCollisionManager;
1022 int32 NumActiveBroadphaseContexts;
1023 bool bNeedsResim;
1024
1025 int32 NumBroadPhasePairs;
1026 int32 NumMidPhases;
1027
1028 TArray<UE::Tasks::FTask> PendingTasks;
1029 // Contexts indexes in which there are constraints
1030 TArray<int32> ContextsWithConstraints;
1031 };
1032}
#define check(expr)
Definition AssertionMacros.h:314
#define ensure( InExpression)
Definition AssertionMacros.h:464
#define PHYSICS_CSV_CUSTOM_EXPENSIVE(Category, Name, Value, Op)
Definition ChaosStats.h:156
#define PHYSICS_CSV_SCOPED_EXPENSIVE(Category, Name)
Definition ChaosStats.h:155
#define CVD_GET_WRAPPED_CURRENT_CONTEXT(OutWrappedContext)
Definition ChaosVDContextProvider.h:223
#define CVD_SCOPE_CONTEXT(InContext)
Definition ChaosVDContextProvider.h:219
#define CVD_TRACE_MID_PHASE(MidPhase)
Definition ChaosVDTraceMacros.h:278
#define PRAGMA_DISABLE_INTERNAL_WARNINGS
Definition CoreMiscDefines.h:346
@ INDEX_NONE
Definition CoreMiscDefines.h:150
#define PRAGMA_ENABLE_INTERNAL_WARNINGS
Definition CoreMiscDefines.h:347
#define UE_INTERNAL
Definition CoreMiscDefines.h:345
#define UE_DEPRECATED(Version, Message)
Definition CoreMiscDefines.h:302
#define TEXT(x)
Definition Platform.h:1272
FPlatformTypes::int32 int32
A 32-bit signed integer.
Definition Platform.h:1125
#define QUICK_SCOPE_CYCLE_COUNTER(Stat)
Definition Stats.h:652
#define SCOPE_CYCLE_COUNTER(Stat)
Definition Stats.h:650
UE_FORCEINLINE_HINT TSharedRef< CastToType, Mode > StaticCastSharedRef(TSharedRef< CastFromType, Mode > const &InSharedRef)
Definition SharedPointer.h:127
#define CSV_SCOPED_TIMING_STAT(Category, StatName)
Definition CsvProfiler.h:150
return true
Definition ExternalRpcRegistry.cpp:601
#define UE_LOG(CategoryName, Verbosity, Format,...)
Definition LogMacros.h:270
#define UE_SOURCE_LOCATION
Definition PreprocessorHelpers.h:71
UE_INTRINSIC_CAST UE_REWRITE constexpr std::remove_reference_t< T > && MoveTemp(T &&Obj) noexcept
Definition UnrealTemplate.h:520
Definition ParticleHandle.h:213
static UE_INTERNAL void ComputeParticleSimFilterDataFromShapes(const TParticle &Particle, FCollisionFilterData &OutSimFilterData)
FGeometryParticleHandle * GetGeometryParticleHandle_PhysicsThread() const
Definition ParticleHandle.h:229
bool PrePreSimFilter(const void *SimData) const
Definition ParticleHandle.h:262
Definition CollisionContext.h:83
void Reset()
Definition CollisionContext.h:92
Definition CollisionContext.h:17
Definition CollisionConstraintFlags.h:33
A handle to a contact constraint.
Definition PBDCollisionConstraintHandle.h:49
const FPBDCollisionConstraint & GetContact() const
Definition PBDCollisionConstraints.h:406
Private::FCollisionSortKey GetCollisionSortKey() const
Definition PBDCollisionConstraint.h:847
Definition PBDRigidsSOAs.h:269
const TParticleView< FPBDRigidParticles > & GetActiveDynamicMovingKinematicParticlesView() const
Definition PBDRigidsSOAs.h:950
const TParticleView< FPBDRigidParticles > & GetNonDisabledDynamicView() const
Definition PBDRigidsSOAs.h:926
Definition SpatialAccelerationBroadPhase.h:335
ISpatialAcceleration< FAccelerationStructureHandle, FReal, 3 > FAccelerationStructure
Definition SpatialAccelerationBroadPhase.h:337
void SetSpatialAcceleration(const FAccelerationStructure *InSpatialAcceleration)
Definition SpatialAccelerationBroadPhase.h:347
void ComputeParticlesOverlaps(ViewType &OverlapView, FReal Dt, const SpatialAccelerationType &InSpatialAcceleration, Private::FCollisionConstraintAllocator *Allocator, const FCollisionDetectorSettings &Settings)
This function is the outer loop of collision detection. It loops over the particles view and do the b...
Definition SpatialAccelerationBroadPhase.h:618
FSpatialAccelerationBroadPhase(const FPBDRigidsSOAs &InParticles)
Definition SpatialAccelerationBroadPhase.h:339
void ProduceCollisions(FReal Dt)
Definition SpatialAccelerationBroadPhase.h:438
void ProduceOverlaps(FReal Dt, const T_SPATIALACCELERATION &InSpatialAcceleration, Private::FCollisionConstraintAllocator *Allocator, const FCollisionDetectorSettings &Settings, IResimCacheBase *ResimCache)
Definition SpatialAccelerationBroadPhase.h:681
FIgnoreCollisionManager & GetIgnoreCollisionManager()
Definition SpatialAccelerationBroadPhase.h:719
int32 GetNumMidPhases() const
Definition SpatialAccelerationBroadPhase.h:727
void GatherConstraints(bool bIsDeterministic)
Definition SpatialAccelerationBroadPhase.h:470
void ProduceOverlaps(FReal Dt, Private::FCollisionConstraintAllocator *Allocator, const FCollisionDetectorSettings &Settings, IResimCacheBase *ResimCache)
Definition SpatialAccelerationBroadPhase.h:355
int32 GetNumBroadPhasePairs() const
Definition SpatialAccelerationBroadPhase.h:722
Definition ResimCacheBase.h:11
Definition ISpatialAccelerationCollection.h:23
Definition ISpatialAcceleration.h:267
Definition SpatialAccelerationBroadPhase.h:218
FBroadPhaseContext()
Definition SpatialAccelerationBroadPhase.h:220
TArray< FBroadPhaseOverlap > Overlaps
Definition SpatialAccelerationBroadPhase.h:231
TArray< FParticlePairMidPhase * > MidPhases
Definition SpatialAccelerationBroadPhase.h:232
void Reset()
Definition SpatialAccelerationBroadPhase.h:224
FCollisionContext CollisionContext
Definition SpatialAccelerationBroadPhase.h:233
Definition SpatialAccelerationBroadPhase.h:183
int32 SearchParticleIndex
Definition SpatialAccelerationBroadPhase.h:210
FGeometryParticleHandle * Particles[2]
Definition SpatialAccelerationBroadPhase.h:209
FBroadPhaseOverlap()
Definition SpatialAccelerationBroadPhase.h:185
bool bCollisionsEnabled
Definition SpatialAccelerationBroadPhase.h:211
FBroadPhaseOverlap(FGeometryParticleHandle *Particle0, FGeometryParticleHandle *Particle1, const int32 InSearchParticleIndex)
Definition SpatialAccelerationBroadPhase.h:189
void ApplyFilter(const FIgnoreCollisionManager &IgnoreCollisionManager, const bool bIsResimming)
Definition SpatialAccelerationBroadPhase.h:196
An allocator and container of collision constraints that supports reuse of constraints from the previ...
Definition CollisionConstraintAllocator.h:234
Definition CollisionConstraintAllocator.h:28
Definition AABBTree.h:786
Definition BoundingVolume.h:118
Definition ParticleHandle.h:436
const TPBDRigidParticleHandleImp< T, d, bPersistent > * CastToRigidParticle() const
Definition ParticleHandle.h:1697
Definition Handles.h:93
Definition ParticleHandle.h:987
Definition ParticleIterator.h:639
static CORE_API FTaskGraphInterface & Get()
Definition TaskGraph.cpp:1794
Definition Array.h:670
UE_REWRITE SizeType Num() const
Definition Array.h:1144
void Reset(SizeType NewSize=0)
Definition Array.h:2246
UE_REWRITE bool IsEmpty() const
Definition Array.h:1133
UE_NODEBUG UE_FORCEINLINE_HINT SizeType Add(ElementType &&Item)
Definition Array.h:2696
UE_FORCEINLINE_HINT void Reserve(SizeType Number)
Definition Array.h:3016
Definition AsyncInitBodyHelper.cpp:9
int32 NumWorkerCollisionFactor
Definition SpatialAccelerationBroadPhase.cpp:8
int32 ChaosOneWayInteractionPairCollisionMode
Definition PBDRigidsEvolutionGBF.cpp:177
bool bChaosMidPhaseRedistributionEnabled
Definition PBDRigidsEvolutionGBF.cpp:238
bool ParticlePairCollisionAllowed(const FGeometryParticleHandle *Particle1, const FGeometryParticleHandle *Particle2, const FIgnoreCollisionManager &IgnoreCollisionManager, const bool bIsResimming, bool &bOutSwapOrder)
Definition SpatialAccelerationBroadPhase.h:43
Definition SkeletalMeshComponent.h:307
TPBDRigidParticleHandle< FReal, 3 > FPBDRigidParticleHandle
Definition ParticleHandleFwd.h:60
CHAOS_API bool bDisableCollisionParallelFor
Definition Parallel.cpp:20
FRealDouble FReal
Definition Real.h:22
CHAOS_API int32 GSingleThreadedPhysics
Definition Parallel.cpp:10
bool PrePreFilterHelper(const FAccelerationStructureHandle &Payload, const Private::FSimOverlapVisitor &Visitor)
Definition SpatialAccelerationBroadPhase.h:323
uint8 SpatialAccelerationType
Definition ISpatialAcceleration.h:178
TGeometryParticleHandle< FReal, 3 > FGeometryParticleHandle
Definition ParticleHandleFwd.h:24
CHAOS_API int32 MaxNumWorkers
Definition Parallel.cpp:13
bool ParticlePairBroadPhaseFilter(const FGeometryParticleHandle *Particle1, const FGeometryParticleHandle *Particle2, const FIgnoreCollisionManager *IgnoreCollisionManager)
Definition CollisionFilter.h:22
bool AreParticlesInPreferredOrder(const FGeometryParticleHandle *Particle0, const FGeometryParticleHandle *Particle1)
Definition CollisionKeys.h:28
TAABB< FReal, 3 > FAABB3
Definition ImplicitObject.h:34
CHAOS_API int32 SmallBatchSize
Definition Parallel.cpp:14
bool ShouldSwapParticleOrder(const bool bIsDynamicOrSleeping0, const bool bIsDynamicOrSleeping1, const bool bIsParticle0Preferred)
Definition CollisionKeys.h:37
Definition OverriddenPropertySet.cpp:45
TTask< TInvokeResult_T< TaskBodyType > > Launch(const TCHAR *DebugName, TaskBodyType &&TaskBody, ETaskPriority Priority=ETaskPriority::Normal, EExtendedTaskPriority ExtendedPriority=EExtendedTaskPriority::None, ETaskFlags Flags=ETaskFlags::None)
Definition Task.h:266
bool Wait(const TaskCollectionType &Tasks, FTimespan InTimeout=FTimespan::MaxValue())
Definition Task.h:381
@ false
Definition radaudio_common.h:23
U16 Index
Definition radfft.cpp:71
Definition ISpatialAcceleration.h:14
Definition GeometryParticlesfwd.h:87
Definition SpatialAccelerationBroadPhase.h:240
FSimOverlapVisitor(FGeometryParticleHandle *ParticleHandle, Private::FBroadPhaseContext &InContext)
Definition SpatialAccelerationBroadPhase.h:241
const void * GetQueryPayload() const
Definition SpatialAccelerationBroadPhase.h:290
UE_INTERNAL bool PrePreFilter(const FAccelerationStructureHandle &Payload) const
Definition SpatialAccelerationBroadPhase.h:300
const void * GetQueryData() const
Definition SpatialAccelerationBroadPhase.h:281
bool ShouldIgnore(const TSpatialVisitorData< FAccelerationStructureHandle > &Instance) const
Definition SpatialAccelerationBroadPhase.h:285
bool VisitSweep(TSpatialVisitorData< FAccelerationStructureHandle >, FQueryFastData &CurData)
Definition SpatialAccelerationBroadPhase.h:269
const void * GetSimData() const
Definition SpatialAccelerationBroadPhase.h:283
bool VisitOverlap(const TSpatialVisitorData< FAccelerationStructureHandle > &Instance)
Definition SpatialAccelerationBroadPhase.h:259
bool VisitRaycast(TSpatialVisitorData< FAccelerationStructureHandle >, FQueryFastData &CurData)
Definition SpatialAccelerationBroadPhase.h:275
bool HasBlockingHit() const
Definition SpatialAccelerationBroadPhase.h:295
Definition AABBTree.h:260
Definition ParticleIterator.h:323
int32 Size() const
Definition ParticleIterator.h:351
Definition ISpatialAcceleration.h:99
Definition ChaosVDContextProvider.h:233
Definition CollisionFilterData.h:46
static constexpr UE_FORCEINLINE_HINT T DivideAndRoundUp(T Dividend, T Divisor)
Definition UnrealMathUtility.h:694