// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "GeometryCollection/GeometryCollectionSQAccelerator.h" #include "Physics/Experimental/PhysScene_Chaos.h" #include "Chaos/Box.h" #include "Chaos/Capsule.h" #include "Chaos/ImplicitObjectTransformed.h" #include "Chaos/Sphere.h" #include "Chaos/PBDRigidParticles.h" #include "GeometryCollection/ManagedArray.h" #include "GeometryCollection/GeometryCollectionComponent.h" #include "GeometryCollection/GeometryCollectionActor.h" #include "Components/BoxComponent.h" #include "ChaosSolversModule.h" #include "ChaosStats.h" #include "PBDRigidsSolver.h" #include "SolverObjects/GeometryCollectionPhysicsObject.h" #if INCLUDE_CHAOS && !WITH_CHAOS_NEEDS_TO_BE_FIXED DECLARE_CYCLE_STAT(TEXT("LowLevelSweep"), STAT_LowLevelSweep, STATGROUP_Chaos); DECLARE_CYCLE_STAT(TEXT("LowLevelRaycast"), STAT_LowLevelRaycast, STATGROUP_Chaos); DECLARE_CYCLE_STAT(TEXT("LowLevelOverlap"), STAT_LowLevelOverlap, STATGROUP_Chaos); bool IsValidIndexAndTransform(const FGeometryCollectionResults& PhysResult, const Chaos::TPBDRigidParticles& Particles, const TManagedArray& TransformArray, const TArray& DisabledFlags, const int32 RigidBodyIdx, const bool bCanBeDisabled) { if(RigidBodyIdx == -1) { //todo(ocohen): managed to avoid this invalid index, but need to investigate a bit more into whether we can always assume it's valid return false; } if (PhysResult.BaseIndex == -1) { //todo(mlentine): Why is this possible? return false; } const int32 LocalBodyIndex = RigidBodyIdx - PhysResult.BaseIndex; if(LocalBodyIndex < 0 || LocalBodyIndex >= PhysResult.NumParticlesAdded) { // Ignore collisions for other components - need to make this even faster (subset of potential intersections [maybe array view?]) return false; } const FTransform& CurrentTransform = TransformArray[LocalBodyIndex]; const FVector CurrentTranslation = CurrentTransform.GetTranslation(); //@todo(mlentine): In theory this is no longer needed if (!bCanBeDisabled && DisabledFlags[LocalBodyIndex]) { //disabled particles can actually have stale geometry in them and are clearly not useful anyway return false; } if (static_cast(RigidBodyIdx) >= Particles.Size()) { // @todo(mlentine): Is this a possible situation? return false; } if (!(ensure(!FMath::IsNaN(CurrentTranslation[0])) && ensure(!FMath::IsNaN(CurrentTranslation[1])) && ensure(!FMath::IsNaN(CurrentTranslation[2])))) { return false; } return true; } bool LowLevelRaycastSingleElement(int32 InParticleIndex, const Chaos::FPBDRigidsSolver* InSolver, const Chaos::TClusterBuffer& ClusterBuffer, const FGeometryCollectionPhysicsObject* InObject, const FVector& Start, const FVector& Dir, float DeltaMag, bool bCanBeDisabled, EHitFlags OutputFlags, FHitRaycast& OutHit) { using namespace Chaos; // Preconditions from Raycast - shouldn't get in here without valid case checkSlow(InSolver); checkSlow(InObject); const FGeometryCollectionResults& PhysResult = InObject->GetPhysicsResults().GetGameDataForRead(); const TManagedArray& RigidBodyIdArray = PhysResult.RigidBodyIds; const TManagedArray& TransformArray = PhysResult.Transforms; const TArray& DisabledFlags = PhysResult.DisabledStates; { const TPBDRigidParticles& Particles = InSolver->GetRigidParticles(); if(!IsValidIndexAndTransform(PhysResult, Particles, TransformArray, DisabledFlags, InParticleIndex, bCanBeDisabled)) { return false; } const int32 LocalBodyIndex = InParticleIndex - PhysResult.BaseIndex; const TRigidTransform& TM = PhysResult.ParticleToWorldTransforms[LocalBodyIndex]; if (!(ensure(!FMath::IsNaN(TM.GetTranslation().X)) && ensure(!FMath::IsNaN(TM.GetTranslation().Y)) && ensure(!FMath::IsNaN(TM.GetTranslation().Z)))) { return false; } const TVector StartLocal = TM.InverseTransformPositionNoScale(Start); const TVector DirLocal = TM.InverseTransformVectorNoScale(Dir); const TVector EndLocal = StartLocal + DirLocal * DeltaMag; const TImplicitObject* Object = ClusterBuffer.GeometryPtrs[InParticleIndex].Get(); //todo(ocohen): can this ever be null? if (!Object) { return false; } Pair, bool> Result = Object->FindClosestIntersection(StartLocal, EndLocal, /*Thickness=*/0.f); if(Result.Second) //todo(ocohen): once we do more than just a bool we need to get the closest point { #if WITH_PHYSX //todo(ocohen): check output flags? const float Distance = (Result.First - StartLocal).Size(); if(OutHit.distance == PX_MAX_REAL || Distance < OutHit.distance) { OutHit.distance = Distance; //todo(ocohen): assuming physx structs for now OutHit.position = U2PVector(TM.TransformPositionNoScale(Result.First)); const TVector LocalNormal = Object->Normal(Result.First); OutHit.normal = U2PVector(TM.TransformVectorNoScale(LocalNormal)); SetFlags(OutHit, EHitFlags::Distance | EHitFlags::Normal | EHitFlags::Position); } return true; #endif } } return false; } static TAutoConsoleVariable CVarMaxSweepSteps( TEXT("p.MaxSweepSteps"), 3, TEXT("Number of steps during a sweep"), ECVF_Default); bool LowLevelSweepSingleElement(int32 InParticleIndex, const Chaos::FPBDRigidsSolver* InSolver, const Chaos::TClusterBuffer& ClusterBuffer, const FGeometryCollectionPhysicsObject* InObject, const Chaos::TImplicitObject& QueryGeom, const Chaos::TParticles& CollisionParticles, const FTransform& StartPose, const FVector& Dir, float DeltaMag, const bool bCanBeDisabled, FHitSweep& OutHit) { using namespace Chaos; checkSlow(InSolver); checkSlow(InObject); const FGeometryCollectionResults& PhysResult = InObject->GetPhysicsResults().GetGameDataForRead(); const TManagedArray& RigidBodyIdArray = PhysResult.RigidBodyIds; const TManagedArray& TransformArray = PhysResult.Transforms; const TArray& DisabledFlags = PhysResult.DisabledStates; const TPBDRigidParticles& Particles = InSolver->GetRigidParticles(); if(!IsValidIndexAndTransform(PhysResult, Particles, TransformArray, DisabledFlags, InParticleIndex, bCanBeDisabled)) { return false; } const int32 LocalBodyIndex = InParticleIndex - PhysResult.BaseIndex; const TRigidTransform& TM = PhysResult.ParticleToWorldTransforms[LocalBodyIndex]; const TImplicitObject* Object = ClusterBuffer.GeometryPtrs[InParticleIndex].Get(); if (!Object) { return false; } const TVector DirLocal = TM.InverseTransformVectorNoScale(Dir); bool bFound = false; for(uint32 i = 0; i < CollisionParticles.Size(); ++i) { const TVector StartLocal = TM.InverseTransformPositionNoScale(StartPose.TransformPositionNoScale(CollisionParticles.X(i))); const TVector EndLocal = StartLocal + DirLocal * DeltaMag; Pair, bool> Result = Object->FindClosestIntersection(StartLocal, EndLocal, /*Thickness=*/0.f); if(Result.Second) { #if WITH_PHYSX const float Distance = (Result.First - StartLocal).Size(); if(!bFound || Distance < OutHit.distance) { OutHit.distance = Distance; //todo(ocohen): assuming physx structs for now OutHit.position = U2PVector(TM.TransformPositionNoScale(Result.First)); const TVector LocalNormal = Object->Normal(Result.First); OutHit.normal = U2PVector(TM.TransformVectorNoScale(LocalNormal)); SetFlags(OutHit, EHitFlags::Distance | EHitFlags::Normal | EHitFlags::Position); } bFound = true; #endif } } return bFound; } bool LowLevelOverlap(const UGeometryCollectionComponent& GeomCollectionComponent, const TArray& InPotentialIntersections, const Chaos::TClusterBuffer& ClusterBuffer, const Chaos::TImplicitObject& QueryGeom, const FTransform& GeomPose, FHitOverlap& OutHit) { using namespace Chaos; SCOPE_CYCLE_COUNTER(STAT_LowLevelOverlap); const FGeometryCollectionPhysicsObject* PhysObject = GeomCollectionComponent.GetPhysicsObject(); if (!ensure(PhysObject)) { return false; } const FGeometryCollectionResults& PhysResult = PhysObject->GetPhysicsResults().GetGameDataForRead(); const TManagedArray& RigidBodyIdArray = PhysResult.RigidBodyIds; const TManagedArray& TransformArray = PhysResult.Transforms; const TArray& DisabledFlags = PhysResult.DisabledStates; bool bFound = false; if (Chaos::FPBDRigidsSolver* Solver = GeomCollectionComponent.ChaosSolverActor != nullptr ? GeomCollectionComponent.ChaosSolverActor->GetSolver() : GeomCollectionComponent.GetOwner()->GetWorld()->PhysicsScene_Chaos->GetSolver()) { const TPBDRigidParticles& Particles = Solver->GetRigidParticles(); //todo(ocohen): should these just get passed in instead of hopping through scene? check(QueryGeom.HasBoundingBox()); // We do not support unbounded query objects PhysicsParallelFor(InPotentialIntersections.Num(), [&](int32 PotentialIdx) { int32 RigidBodyIdx = InPotentialIntersections[PotentialIdx]; if (!IsValidIndexAndTransform(PhysResult, Particles, TransformArray, DisabledFlags, RigidBodyIdx, false)) { return; } const int32 LocalBodyIndex = RigidBodyIdx - PhysResult.BaseIndex; const TRigidTransform& TM = PhysResult.ParticleToWorldTransforms[LocalBodyIndex]; const TImplicitObject* Object = ClusterBuffer.GeometryPtrs[RigidBodyIdx].Get(); if (!Object) { return; } // Need to do narrow phase Pair, bool> Result = QueryGeom.FindDeepestIntersection(Object, Particles.CollisionParticles(RigidBodyIdx).Get(), TRigidTransform(TM) * TRigidTransform(GeomPose).Inverse(), 0); if (Result.Second) { bFound = true; } }); } return bFound; } int32 UseSlowSQ = 0; FAutoConsoleVariableRef CVarUseSlowSQ(TEXT("p.UseSlowSQ"), UseSlowSQ, TEXT("")); void FGeometryCollectionSQAccelerator::Raycast(const FVector& Start, const FVector& Dir, const float DeltaMagnitude, FPhysicsHitCallback& HitBuffer, EHitFlags OutputFlags, FQueryFlags QueryFlags, const FCollisionFilterData& QueryFilter, const FQueryFilterData& QueryFilterData, ICollisionQueryFilterCallbackBase& QueryCallback) const { SCOPE_CYCLE_COUNTER(STAT_GCRaycast); FChaosScopeSolverLock SolverScopeLock; using namespace Chaos; FChaosSolversModule* Module = FChaosSolversModule::GetModule(); TMap> SolverIntersectionSets; Chaos::TSpatialRay Ray(Start, Start + Dir * DeltaMagnitude); #if WITH_PHYSX const TArray& Solvers = Module->GetSolvers(); for(const Chaos::FPBDRigidsSolver* Solver : Solvers) { if (!Solver) { continue; } TArray IntersectionSet = Solver->GetSpatialAcceleration()->FindAllIntersections(Ray); Solver->ReleaseSpatialAcceleration(); const Chaos::TClusterBuffer& Buffer = Solver->GetRigidClustering().GetBufferedData(); const Chaos::FPBDRigidsSolver::FSolverObjectReverseMapping& ObjectMap = Solver->GetSolverObjectReverseMapping_GameThread(); FHitRaycast Hit; int32 IntersectionSetSize = IntersectionSet.Num(); for(int32 i = 0; i < IntersectionSet.Num(); ++i) { const int32 IntersectParticleIndex = IntersectionSet[i]; const SolverObjectWrapper& ObjectWrapper = ObjectMap.SolverObjectReverseMappingArray[IntersectParticleIndex]; if (!ObjectWrapper.SolverObject) { const TImplicitObject* Object = Buffer.GeometryPtrs[IntersectParticleIndex].Get(); // Ignore the ground plane if (IntersectParticleIndex == 0 && Object->GetType(true) == ImplicitObjectType::Plane) { continue; } if (Object && !UseSlowSQ && Object->IsUnderlyingUnion()) { const TImplicitObjectUnion* Union = static_cast*>(Object); //hack: this is terrible because we have no buffered transform so could be off, but most of the time these things are static { const TRigidTransform* TMPtr = Buffer.ClusterParentTransforms.Find(IntersectParticleIndex); if (ensure(TMPtr)) { if (!(ensure(!FMath::IsNaN(TMPtr->GetTranslation().X)) && ensure(!FMath::IsNaN(TMPtr->GetTranslation().Y)) && ensure(!FMath::IsNaN(TMPtr->GetTranslation().Z)))) { continue; } const TVector StartLocal = TMPtr->InverseTransformPositionNoScale(Start); const TVector DirLocal = TMPtr->InverseTransformVectorNoScale(Dir); const TVector EndLocal = StartLocal + DirLocal * DeltaMagnitude; Chaos::TSpatialRay LocalRay(StartLocal, EndLocal); const TArray IntersectingChildren = Union->FindAllIntersectingChildren(LocalRay); IntersectionSet.Append(IntersectingChildren); } else { UE_LOG(LogChaos, Warning, TEXT("SQ: Could not find a valid transform for a cluster parent for faster child intersections.")); } } } else { if (ensure(Buffer.MChildren.Contains(IntersectParticleIndex))) { const TArray& Children = Buffer.MChildren[IntersectParticleIndex]; for (const uint32 Child : Children) { IntersectionSet.Add(Child); } } } continue; } if(ObjectWrapper.Type == ESolverObjectType::GeometryCollectionType && ensure(ObjectWrapper.SolverObject)) { LowLevelRaycastSingleElement(IntersectParticleIndex, Solver, Buffer, static_cast(ObjectWrapper.SolverObject), Start, Dir, DeltaMagnitude, i >= IntersectionSetSize, OutputFlags, Hit); // If we registered a hit if(Hit.distance != PX_MAX_REAL && ensure(ObjectWrapper.SolverObject)) { #if !WITH_IMMEDIATE_PHYSX && PHYSICS_INTERFACE_PHYSX //todo(ocohen):hack placeholder while we convert over to non physx API UGeometryCollectionComponent* Component = Cast(ObjectWrapper.SolverObject->GetOwner()); check(Component); if (Component->IsRegistered()) { const FPhysicsActorHandle& ActorHandle = Component->DummyBodyInstance.GetPhysicsActorHandle(); PxRigidActor* PRigidActor = ActorHandle.SyncActor; uint32 PNumShapes = PRigidActor->getNbShapes(); TArray PShapes; PShapes.AddZeroed(PNumShapes); PRigidActor->getShapes(PShapes.GetData(), sizeof(PShapes[0]) * PNumShapes); SetActor(Hit, ActorHandle.SyncActor); SetShape(Hit, PShapes[0]); } #else check(false); //this can't actually return nullptr since higher up API assumes both shape and actor exists in the low level SetActor(Hit, nullptr); SetShape(Hit, nullptr); //todo(ocohen): what do we return for apeiron? #endif Insert(HitBuffer, Hit, true); //for now assume all blocking hits } } } Solver->GetRigidClustering().ReleaseBufferedData(); Solver->ReleaseSolverObjectReverseMapping(); } #endif } DECLARE_CYCLE_STAT(TEXT("Sweep Broadphase"), STAT_SQSweepBroadPhase, STATGROUP_Chaos); DECLARE_CYCLE_STAT(TEXT("Sweep Narrowphase"), STAT_SQSweepNarrowPhase, STATGROUP_Chaos); //@todo(mlentine): Avoid duplicated code between this and overlap void FGeometryCollectionSQAccelerator::Sweep(const FPhysicsGeometry& QueryGeom, const FTransform& StartTM, const FVector& Dir, const float DeltaMag, FPhysicsHitCallback& HitBuffer, EHitFlags OutputFlags, FQueryFlags QueryFlags, const FCollisionFilterData& QueryFilter, const FQueryFilterData& QueryFilterData, ICollisionQueryFilterCallbackBase& QueryCallback) const { SCOPE_CYCLE_COUNTER(STAT_GCSweep); FChaosScopeSolverLock SolverScopeLock; using namespace Chaos; #if WITH_PHYSX TMap> SolverIntersectionSets; // Getter for intersections from the mapping above auto GetIntersectionsFunc = [&](const Chaos::FPBDRigidsSolver* InSolver, const Chaos::TParticles& InCollisionParticles, float InDeltaMag, const FTransform& InPose) -> TArray { SCOPE_CYCLE_COUNTER(STAT_SQSweepBroadPhase) TArray PotentialIntersections; if(InSolver->Enabled()) { const Chaos::ISpatialAcceleration* SpacialAcceleration = InSolver->GetSpatialAcceleration(); const int32 NumCollisionParticles = InCollisionParticles.Size(); for(int32 ParticleIndex = 0; ParticleIndex < NumCollisionParticles; ++ParticleIndex) { const Chaos::TVector RayStart = InPose.TransformPositionNoScale(InCollisionParticles.X(ParticleIndex)); const Chaos::TVector RayEnd = RayStart + Dir * InDeltaMag; PotentialIntersections.Append(SpacialAcceleration->FindAllIntersections(Chaos::TSpatialRay(RayStart, RayEnd))); } InSolver->ReleaseSpatialAcceleration(); PotentialIntersections.Sort(); for(int32 i = PotentialIntersections.Num() - 1; i > 0; i--) { if(PotentialIntersections[i] == PotentialIntersections[i - 1]) { PotentialIntersections.RemoveAtSwap(i, 1, false); } } } return PotentialIntersections; }; // Need somewhere to store our translated shape, similar to the PhysX geom holder struct FLocalImplicitStorage { FLocalImplicitStorage() : Capsule(FVector::ZeroVector, FVector::ZeroVector, 0.0f) , Sphere(FVector::ZeroVector, 0.0f) , Box(FVector::ZeroVector, FVector::ZeroVector) {} Chaos::TCapsule Capsule; Chaos::TSphere Sphere; Chaos::TBox Box; }; FLocalImplicitStorage ImplicitStorage; Chaos::TImplicitObject* Implicit = nullptr; bool bHit = false; FHitSweep Hit; PxGeometryHolder Holder(QueryGeom); Chaos::TParticles CollisionParticles; if(Holder.getType() == PxGeometryType::eCAPSULE) { physx::PxCapsuleGeometry& PxCapsule = Holder.capsule(); float Radius = PxCapsule.radius; float HalfHeight = PxCapsule.halfHeight; Chaos::TVector x1(-HalfHeight, 0, 0); Chaos::TVector x2(HalfHeight, 0, 0); Chaos::TCapsule Capsule(x1, x2, Radius); ImplicitStorage.Capsule = MoveTemp(Capsule); Implicit = &ImplicitStorage.Capsule; { CollisionParticles.AddParticles(14); CollisionParticles.X(0) = Chaos::TVector(HalfHeight + Radius, 0, 0); CollisionParticles.X(1) = Chaos::TVector(-HalfHeight - Radius, 0, 0); CollisionParticles.X(2) = Chaos::TVector(HalfHeight, Radius, Radius); CollisionParticles.X(3) = Chaos::TVector(HalfHeight, -Radius, Radius); CollisionParticles.X(4) = Chaos::TVector(HalfHeight, -Radius, -Radius); CollisionParticles.X(5) = Chaos::TVector(HalfHeight, Radius, -Radius); CollisionParticles.X(6) = Chaos::TVector(0, Radius, Radius); CollisionParticles.X(7) = Chaos::TVector(0, -Radius, Radius); CollisionParticles.X(8) = Chaos::TVector(0, -Radius, -Radius); CollisionParticles.X(9) = Chaos::TVector(0, Radius, -Radius); CollisionParticles.X(10) = Chaos::TVector(-HalfHeight, Radius, Radius); CollisionParticles.X(11) = Chaos::TVector(-HalfHeight, -Radius, Radius); CollisionParticles.X(12) = Chaos::TVector(-HalfHeight, -Radius, -Radius); CollisionParticles.X(13) = Chaos::TVector(-HalfHeight, Radius, -Radius); } } else if(Holder.getType() == PxGeometryType::eSPHERE) { physx::PxSphereGeometry& PxSphere = Holder.sphere(); float Radius = PxSphere.radius; Chaos::TSphere Sphere(Chaos::TVector(0), Radius); ImplicitStorage.Sphere = MoveTemp(Sphere); Implicit = &ImplicitStorage.Sphere; { CollisionParticles.AddParticles(6); CollisionParticles.X(0) = Chaos::TVector(Radius, 0, 0); CollisionParticles.X(1) = Chaos::TVector(-Radius, 0, 0); CollisionParticles.X(2) = Chaos::TVector(0, Radius, Radius); CollisionParticles.X(3) = Chaos::TVector(0, -Radius, Radius); CollisionParticles.X(4) = Chaos::TVector(0, -Radius, -Radius); CollisionParticles.X(5) = Chaos::TVector(0, Radius, -Radius); } } else if(Holder.getType() == PxGeometryType::eBOX) { physx::PxBoxGeometry& PxBox = Holder.box(); Chaos::TVector x1(-P2UVector(PxBox.halfExtents)); Chaos::TVector x2(-x1); Chaos::TBox Box(x1, x2); ImplicitStorage.Box = MoveTemp(Box); Implicit = &ImplicitStorage.Box; { CollisionParticles.AddParticles(8); CollisionParticles.X(0) = Chaos::TVector(x1.X, x1.Y, x1.Z); CollisionParticles.X(1) = Chaos::TVector(x1.X, x1.Y, x2.Z); CollisionParticles.X(2) = Chaos::TVector(x1.X, x2.Y, x1.Z); CollisionParticles.X(3) = Chaos::TVector(x2.X, x1.Y, x1.Z); CollisionParticles.X(4) = Chaos::TVector(x2.X, x2.Y, x2.Z); CollisionParticles.X(5) = Chaos::TVector(x2.X, x2.Y, x1.Z); CollisionParticles.X(6) = Chaos::TVector(x2.X, x1.Y, x2.Z); CollisionParticles.X(7) = Chaos::TVector(x1.X, x2.Y, x2.Z); } } else { // We don't support anything else for sweeps ensureMsgf(false, TEXT("Unsupported query type used for sweep")); return; } const TArray& Solvers = FChaosSolversModule::GetModule()->GetSolvers(); for(const Chaos::FPBDRigidsSolver* Solver : Solvers) { if (!Solver) { continue; } TArray IntersectionSet = GetIntersectionsFunc(Solver, CollisionParticles, DeltaMag, StartTM); const Chaos::FPBDRigidsSolver::FSolverObjectReverseMapping& ObjectMap = Solver->GetSolverObjectReverseMapping_GameThread(); const Chaos::TClusterBuffer& Buffer = Solver->GetRigidClustering().GetBufferedData(); int32 IntersectionSetSize = IntersectionSet.Num(); for(int32 i = 0; i < IntersectionSet.Num(); ++i) { SCOPE_CYCLE_COUNTER(STAT_SQSweepNarrowPhase) const int32 ParticleIndex = IntersectionSet[i]; const SolverObjectWrapper& ObjectWrapper = ObjectMap.SolverObjectReverseMappingArray[ParticleIndex]; if (!ObjectWrapper.SolverObject) { const TImplicitObject* Object = Buffer.GeometryPtrs[ParticleIndex].Get(); // Ignore ground plane if (ParticleIndex == 0 && Object->GetType(true) == ImplicitObjectType::Plane) { continue; } if (Object && !UseSlowSQ && Object->IsUnderlyingUnion()) { const TImplicitObjectUnion* Union = static_cast*>(Object); //hack: this is terrible because we have no buffered transform so could be off, but most of the time these things are static { const TRigidTransform* TMPtr = Buffer.ClusterParentTransforms.Find(ParticleIndex); if (ensure(TMPtr)) { if (!(ensure(!FMath::IsNaN(TMPtr->GetTranslation().X)) && ensure(!FMath::IsNaN(TMPtr->GetTranslation().Y)) && ensure(!FMath::IsNaN(TMPtr->GetTranslation().Z)))) { continue; } const TVector StartLocal = TMPtr->InverseTransformPositionNoScale(StartTM.GetLocation()); const TVector DirLocal = TMPtr->InverseTransformVectorNoScale(Dir); const TVector EndLocal = StartLocal + DirLocal * DeltaMag; Chaos::TSpatialRay LocalRay(StartLocal, EndLocal); const TArray IntersectingChildren = Union->FindAllIntersectingChildren(LocalRay); IntersectionSet.Append(IntersectingChildren); } else { UE_LOG(LogChaos, Warning, TEXT("SQ: Could not find a valid transform for a cluster parent for faster child intersections.")); } } } else { if (ensure(Buffer.MChildren.Contains(ParticleIndex))) { const TArray& Children = Buffer.MChildren[ParticleIndex]; for (const uint32 Child : Children) { IntersectionSet.Add(Child); } } } continue; } if(ObjectWrapper.Type == ESolverObjectType::GeometryCollectionType && ensure(ObjectWrapper.SolverObject)) { if(LowLevelSweepSingleElement(ParticleIndex, Solver, Buffer, static_cast(ObjectWrapper.SolverObject), *Implicit, CollisionParticles, StartTM, Dir, DeltaMag, i >= IntersectionSetSize, Hit)) { //todo(mlentine): This is duplicated from above and should be merged //todo(ocohen):hack placeholder while we convert over to non physx API UGeometryCollectionComponent* Component = Cast(ObjectWrapper.SolverObject->GetOwner()); check(Component); if (Component->IsRegistered()) { #if !WITH_IMMEDIATE_PHYSX && PHYSICS_INTERFACE_PHYSX const FPhysicsActorHandle& ActorHandle = Component->DummyBodyInstance.GetPhysicsActorHandle(); PxRigidActor* PRigidActor = ActorHandle.SyncActor; uint32 PNumShapes = PRigidActor->getNbShapes(); TArray PShapes; PShapes.AddZeroed(PNumShapes); PRigidActor->getShapes(PShapes.GetData(), sizeof(PShapes[0]) * PNumShapes); SetActor(Hit, ActorHandle.SyncActor); SetShape(Hit, PShapes[0]); #else check(false); //this can't actually return nullptr since higher up API assumes both shape and actor exists in the low level SetActor(Hit, nullptr); SetShape(Hit, nullptr); //todo(ocohen): what do we return for apeiron? #endif Insert(HitBuffer, Hit, true); //for now assume all blocking hits } } } } Solver->GetRigidClustering().ReleaseBufferedData(); Solver->ReleaseSolverObjectReverseMapping(); } #endif } bool LowLevelOverlapSingleElement(int32 InParticleIndex, const Chaos::FPBDRigidsSolver* InSolver, const Chaos::TClusterBuffer& ClusterBuffer, const FGeometryCollectionPhysicsObject* InObject, const Chaos::TImplicitObject& QueryGeom, const FTransform& InPose, FHitOverlap& OutHit) { using namespace Chaos; checkSlow(InSolver); checkSlow(InObject); const FGeometryCollectionResults& PhysResult = InObject->GetPhysicsResults().GetGameDataForRead(); const TManagedArray& RigidBodyIdArray = PhysResult.RigidBodyIds; const TManagedArray& TransformArray = PhysResult.Transforms; const TArray& DisabledFlags = PhysResult.DisabledStates; const TPBDRigidParticles& Particles = InSolver->GetRigidParticles(); if(!IsValidIndexAndTransform(PhysResult, Particles, TransformArray, DisabledFlags, InParticleIndex, false)) { return false; } const int32 LocalBodyIndex = InParticleIndex - PhysResult.BaseIndex; const TRigidTransform& TM = PhysResult.ParticleToWorldTransforms[LocalBodyIndex]; const TImplicitObject* Object = ClusterBuffer.GeometryPtrs[InParticleIndex].Get(); if(!Object) { return false; } Pair, bool> Result = QueryGeom.FindDeepestIntersection(Object, Particles.CollisionParticles(InParticleIndex).Get(), TRigidTransform(TM) * TRigidTransform(InPose).Inverse(), 0); return Result.Second; } void FGeometryCollectionSQAccelerator::Overlap(const FPhysicsGeometry& QueryGeom, const FTransform& GeomPose, FPhysicsHitCallback& HitBuffer, FQueryFlags QueryFlags, const FCollisionFilterData& QueryFilter, const FQueryFilterData& QueryFilterData, ICollisionQueryFilterCallbackBase& QueryCallback) const { return; //todo: This is currently broken because it doesn'thandle cluster unions. Need to fix function - disabling entirely for now SCOPE_CYCLE_COUNTER(STAT_GCOverlap); FChaosScopeSolverLock SolverScopeLock; #if WITH_PHYSX TMap> SolverIntersectionSets; // Getter for intersections from the mapping above auto GetIntersectionsFunc = [&](const Chaos::FPBDRigidsSolver* InSolver, Chaos::TImplicitObject* InGeometry, const FTransform& InPose) -> TArray* { TArray* CurrentIntersections = SolverIntersectionSets.Find(InSolver); if(!CurrentIntersections) { // This is safe to access here, it's buffered from the physics thread SolverIntersectionSets.Add(InSolver, InSolver->GetSpatialAcceleration()->FindAllIntersections(InGeometry->BoundingBox().TransformedBox(GeomPose))); InSolver->ReleaseSpatialAcceleration(); } return SolverIntersectionSets.Find(InSolver); }; // Need somewhere to store our translated shape, similar to the PhysX geom holder struct FLocalImplicitStorage { FLocalImplicitStorage() : Capsule(FVector::ZeroVector, FVector::ZeroVector, 0.0f) , Sphere(FVector::ZeroVector, 0.0f) , Box(FVector::ZeroVector, FVector::ZeroVector) {} Chaos::TCapsule Capsule; Chaos::TSphere Sphere; Chaos::TBox Box; }; FLocalImplicitStorage ImplicitStorage; bool bHit = false; FHitOverlap Hit; PxGeometryHolder Holder(QueryGeom); Chaos::TImplicitObject* Implicit = nullptr; if(Holder.getType() == PxGeometryType::eCAPSULE) { physx::PxCapsuleGeometry& PxCapsule = Holder.capsule(); float Radius = PxCapsule.radius; float HalfHeight = PxCapsule.halfHeight - Radius; Chaos::TVector x1(-HalfHeight, 0, 0); Chaos::TVector x2(HalfHeight, 0, 0); Chaos::TCapsule Capsule(x1, x2, Radius); ImplicitStorage.Capsule = MoveTemp(Capsule); Implicit = &ImplicitStorage.Capsule; } else if(Holder.getType() == PxGeometryType::eSPHERE) { physx::PxSphereGeometry& PxSphere = Holder.sphere(); float Radius = PxSphere.radius; Chaos::TSphere Sphere(Chaos::TVector(0), Radius); ImplicitStorage.Sphere = MoveTemp(Sphere); Implicit = &ImplicitStorage.Sphere; } else if(Holder.getType() == PxGeometryType::eBOX) { physx::PxBoxGeometry& PxBox = Holder.box(); Chaos::TVector x1(-P2UVector(PxBox.halfExtents)); Chaos::TVector x2(-x1); Chaos::TBox Box(x1, x2); ImplicitStorage.Box = MoveTemp(Box); Implicit = &ImplicitStorage.Box; } else { ensureMsgf(false, TEXT("Unsupported query type used for overlap")); return; } FChaosSolversModule* Module = FChaosSolversModule::GetModule(); const TArray Solvers = Module->GetSolvers(); for(const Chaos::FPBDRigidsSolver* Solver : Solvers) { // Collect all intersections for this solver TArray IntersectionSet = Solver->GetSpatialAcceleration()->FindAllIntersections(Implicit->BoundingBox().TransformedBox(GeomPose)); Solver->ReleaseSpatialAcceleration(); const Chaos::FPBDRigidsSolver::FSolverObjectReverseMapping& ObjectMap = Solver->GetSolverObjectReverseMapping_GameThread(); const Chaos::TClusterBuffer& ClusterBuffer = Solver->GetRigidClustering().GetBufferedData(); for(int32 i = 0; i < IntersectionSet.Num(); ++i) { const int32 IntersectParticleIndex = IntersectionSet[i]; const SolverObjectWrapper& ObjectWrapper = ObjectMap.SolverObjectReverseMappingArray[IntersectParticleIndex]; if(ObjectWrapper.Type == ESolverObjectType::GeometryCollectionType && ensure(ObjectWrapper.SolverObject)) { if(LowLevelOverlapSingleElement(IntersectParticleIndex, Solver, ClusterBuffer, static_cast(ObjectWrapper.SolverObject), *Implicit, GeomPose, Hit)) { //todo(mlentine): This is duplicated from above and should be merged //todo(ocohen):hack placeholder while we convert over to non physx API UGeometryCollectionComponent* Component = Cast(ObjectWrapper.SolverObject->GetOwner()); check(Component); if (Component->IsRegistered()) { #if !WITH_IMMEDIATE_PHYSX && PHYSICS_INTERFACE_PHYSX const FPhysicsActorHandle& ActorHandle = Component->DummyBodyInstance.GetPhysicsActorHandle(); PxRigidActor* PRigidActor = ActorHandle.SyncActor; uint32 PNumShapes = PRigidActor->getNbShapes(); TArray PShapes; PShapes.AddZeroed(PNumShapes); PRigidActor->getShapes(PShapes.GetData(), sizeof(PShapes[0]) * PNumShapes); SetActor(Hit, ActorHandle.SyncActor); SetShape(Hit, PShapes[0]); #else check(false); //this can't actually return nullptr since higher up API assumes both shape and actor exists in the low level SetActor(Hit, nullptr); SetShape(Hit, nullptr); //todo(ocohen): what do we return for apeiron? #endif InsertOverlap(HitBuffer, Hit); } } } } Solver->GetRigidClustering().ReleaseBufferedData(); Solver->ReleaseSolverObjectReverseMapping(); } #endif } void FGeometryCollectionSQAccelerator::AddComponent(UGeometryCollectionComponent* Component) { if (ensure(Component)) { // UE_LOG(LogTemp, Log, TEXT("SQAccel: Adding %s (Outer %s)"), *GetNameSafe(Component), *GetNameSafe(Component->GetOuter())); Components.Add(Component); } } void FGeometryCollectionSQAccelerator::RemoveComponent(UGeometryCollectionComponent* Component) { if (ensure(Component)) { // UE_LOG(LogTemp, Log, TEXT("SQAccel: Removing %s (Outer %s)"), *GetNameSafe(Component), *GetNameSafe(Component->GetOuter())); Components.Remove(Component); } } #endif