void FKConvexElem::AddCachedSolidConvexGeom(TArray<FDynamicMeshVertex>& VertexBuffer, TArray<int32>& IndexBuffer, const FColor VertexColor) { #if WITH_PHYSX if(ConvexMesh) { int32 StartVertOffset = VertexBuffer.Num(); // get PhysX data const PxVec3* PVertices = ConvexMesh->getVertices(); const PxU8* PIndexBuffer = ConvexMesh->getIndexBuffer(); PxU32 NbPolygons = ConvexMesh->getNbPolygons(); for(PxU32 i=0;i<NbPolygons;i++) { PxHullPolygon Data; bool bStatus = ConvexMesh->getPolygonData(i, Data); check(bStatus); const PxU8* indices = PIndexBuffer + Data.mIndexBase; // create tangents from the first and second vertices of each polygon const FVector TangentX = P2UVector(PVertices[indices[1]]-PVertices[indices[0]]).SafeNormal(); const FVector TangentZ = FVector(Data.mPlane[0], Data.mPlane[1], Data.mPlane[2]).SafeNormal(); const FVector TangentY = (TangentX ^ TangentZ).SafeNormal(); // add vertices for(PxU32 j=0;j<Data.mNbVerts;j++) { int32 VertIndex = indices[j]; FDynamicMeshVertex Vert1; Vert1.Position = P2UVector(PVertices[VertIndex]); Vert1.Color = VertexColor; Vert1.SetTangents( TangentX, TangentY, TangentZ ); VertexBuffer.Add(Vert1); } // Add indices PxU32 nbTris = Data.mNbVerts - 2; for(PxU32 j=0;j<nbTris;j++) { IndexBuffer.Add(StartVertOffset+0); IndexBuffer.Add(StartVertOffset+j+2); IndexBuffer.Add(StartVertOffset+j+1); } StartVertOffset += Data.mNbVerts; } } else { UE_LOG(LogPhysics, Log, TEXT("FKConvexElem::AddCachedSolidConvexGeom : No ConvexMesh, so unable to draw.")); } #endif // WITH_PHYSX }
void UDestructibleComponent::SetChunkVisible( int32 ChunkIndex, bool bVisible ) { #if WITH_APEX // Bone 0 is a dummy root bone const int32 BoneIndex = ChunkIdxToBoneIdx(ChunkIndex); if( bVisible ) { UnHideBone(BoneIndex); if (NULL != ApexDestructibleActor) { physx::PxShape** PShapes; const physx::PxU32 PShapeCount = ApexDestructibleActor->getChunkPhysXShapes(PShapes, ChunkIndex); if (PShapeCount > 0) { const physx::PxMat44 ChunkPoseRT = ApexDestructibleActor->getChunkPose(ChunkIndex); // Unscaled const physx::PxTransform Transform(ChunkPoseRT); SetChunkWorldRT(ChunkIndex, P2UQuat(Transform.q), P2UVector(Transform.p)); } } } else { HideBone(BoneIndex, PBO_None); } // Mark the transform as dirty, so the bounds are updated and sent to the render thread MarkRenderTransformDirty(); // New bone positions need to be sent to render thread MarkRenderDynamicDataDirty(); #endif }
void FConstraintInstance::GetConstraintForce(FVector& OutLinearForce, FVector& OutAngularForce) { OutLinearForce = FVector::ZeroVector; OutAngularForce = FVector::ZeroVector; #if WITH_PHYSX ExecuteOnUnbrokenJointReadOnly([&] (const PxD6Joint* Joint) { PxVec3 PxOutLinearForce; PxVec3 PxOutAngularForce; Joint->getConstraint()->getForce(PxOutLinearForce, PxOutAngularForce); OutLinearForce = P2UVector(PxOutLinearForce); OutAngularForce = P2UVector(PxOutAngularForce); }); #endif }
static FVector FindHeightFieldOpposingNormal(const PxLocationHit& PHit, const FVector& TraceDirectionDenorm, const FVector InNormal) { if (IsInvalidFaceIndex(PHit.faceIndex)) { return InNormal; } PxHeightFieldGeometry PHeightFieldGeom; const bool bReadGeomSuccess = PHit.shape->getHeightFieldGeometry(PHeightFieldGeom); check(bReadGeomSuccess); //we should only call this function when we have a heightfield if (PHeightFieldGeom.heightField) { const PxU32 TriIndex = PHit.faceIndex; const PxTransform PShapeWorldPose = PxShapeExt::getGlobalPose(*PHit.shape, *PHit.actor); PxTriangle Tri; PxMeshQuery::getTriangle(PHeightFieldGeom, PShapeWorldPose, TriIndex, Tri); PxVec3 TriNormal; Tri.normal(TriNormal); return P2UVector(TriNormal); } return InNormal; }
/** Get the position of this constraint in world space. */ FVector FConstraintInstance::GetConstraintLocation() { #if WITH_PHYSX PxD6Joint* Joint = (PxD6Joint*) ConstraintData; if (!Joint) { return FVector::ZeroVector; } PxRigidActor* JointActor0, *JointActor1; Joint->getActors(JointActor0, JointActor1); PxVec3 JointPos(0); // get the first anchor point in global frame if(JointActor0) { JointPos = JointActor0->getGlobalPose().transform(Joint->getLocalPose(PxJointActorIndex::eACTOR0).p); } // get the second archor point in global frame if(JointActor1) { JointPos += JointActor1->getGlobalPose().transform(Joint->getLocalPose(PxJointActorIndex::eACTOR1).p); } JointPos *= 0.5f; return P2UVector(JointPos); #else return FVector::ZeroVector; #endif }
FBoxSphereBounds UDestructibleComponent::CalcBounds(const FTransform& LocalToWorld) const { #if WITH_APEX if( ApexDestructibleActor == NULL ) { // Fallback if we don't have physics return Super::CalcBounds(LocalToWorld); } const PxBounds3& PBounds = ApexDestructibleActor->getBounds(); return FBoxSphereBounds( FBox( P2UVector(PBounds.minimum), P2UVector(PBounds.maximum) ) ); #else // #if WITH_APEX return Super::CalcBounds(LocalToWorld); #endif // #if WITH_APEX }
void SDestructibleMeshEditorViewport::RefreshViewport() { // Update chunk visibilities #if WITH_APEX #if WITH_EDITORONLY_DATA if (DestructibleMesh != NULL && DestructibleMesh->FractureSettings != NULL && DestructibleMesh->ApexDestructibleAsset != NULL && PreviewComponent->IsRegistered()) { const NxRenderMeshAsset* ApexRenderMeshAsset = DestructibleMesh->ApexDestructibleAsset->getRenderMeshAsset(); if (ApexRenderMeshAsset != NULL) { NxExplicitHierarchicalMesh& EHM = DestructibleMesh->FractureSettings->ApexDestructibleAssetAuthoring->getExplicitHierarchicalMesh(); if (DestructibleMesh->ApexDestructibleAsset->getPartIndex(0) < ApexRenderMeshAsset->getPartCount()) { const PxBounds3& Level0Bounds = ApexRenderMeshAsset->getBounds(DestructibleMesh->ApexDestructibleAsset->getPartIndex(0)); const PxVec3 Level0Center = !Level0Bounds.isEmpty() ? Level0Bounds.getCenter() : PxVec3(0.0f); for (uint32 ChunkIndex = 0; ChunkIndex < DestructibleMesh->ApexDestructibleAsset->getChunkCount(); ++ChunkIndex) { const uint32 PartIndex = DestructibleMesh->ApexDestructibleAsset->getPartIndex(ChunkIndex); if (PartIndex >= ApexRenderMeshAsset->getPartCount()) { continue; } uint32 ChunkDepth = 0; for (int32 ParentIndex = DestructibleMesh->ApexDestructibleAsset->getChunkParentIndex(ChunkIndex); ParentIndex >= 0; ParentIndex = DestructibleMesh->ApexDestructibleAsset->getChunkParentIndex(ParentIndex)) { ++ChunkDepth; } const bool bChunkVisible = ChunkDepth == PreviewDepth; PreviewComponent->SetChunkVisible(ChunkIndex, bChunkVisible); if (bChunkVisible) { const PxBounds3& ChunkBounds = ApexRenderMeshAsset->getBounds(PartIndex); const PxVec3 ChunkCenter = !ChunkBounds.isEmpty() ? ChunkBounds.getCenter() : PxVec3(0.0f); const PxVec3 Displacement = ExplodeAmount*(ChunkCenter - Level0Center); PreviewComponent->SetChunkWorldRT(ChunkIndex, FQuat(0.0f, 0.0f, 0.0f, 1.0f), P2UVector(Displacement)); } } PreviewComponent->BoundsScale = 100; // Send bounds to render thread at end of frame PreviewComponent->UpdateComponentToWorld(); // Send bones to render thread right now, so the invalidated display is rerendered with // uptodate information PreviewComponent->DoDeferredRenderUpdates_Concurrent(); } } } #endif // WITH_EDITORONLY_DATA #endif // WITH_APEX // Invalidate the viewport's display. SceneViewport->InvalidateDisplay(); }
void UDestructibleComponent::RefreshBoneTransforms() { #if WITH_APEX if(ApexDestructibleActor != NULL && SkeletalMesh) { UDestructibleMesh* TheDestructibleMesh = GetDestructibleMesh(); // Save a pointer to the APEX NxDestructibleAsset physx::NxDestructibleAsset* ApexDestructibleAsset = TheDestructibleMesh->ApexDestructibleAsset; check(ApexDestructibleAsset); { // Lock here so we don't encounter race conditions with the destruction processing FPhysScene* PhysScene = World->GetPhysicsScene(); check(PhysScene); const uint32 SceneType = (BodyInstance.bUseAsyncScene && PhysScene->HasAsyncScene()) ? PST_Async : PST_Sync; PxScene* PScene = PhysScene->GetPhysXScene(SceneType); check(PScene); SCOPED_SCENE_WRITE_LOCK(PScene); SCOPED_SCENE_READ_LOCK(PScene); // Try to acquire event buffer const physx::NxDestructibleChunkEvent* EventBuffer; physx::PxU32 EventBufferSize; if (ApexDestructibleActor->acquireChunkEventBuffer(EventBuffer, EventBufferSize)) { // Buffer acquired while (EventBufferSize--) { const physx::NxDestructibleChunkEvent& Event = *EventBuffer++; // Right now the only events are visibility changes. So as an optimization we won't check for the event type. // if (Event.event & physx::NxDestructibleChunkEvent::VisibilityChanged) const bool bVisible = (Event.event & physx::NxDestructibleChunkEvent::ChunkVisible) != 0; SetChunkVisible(Event.chunkIndex, bVisible); } // Release buffer (will be cleared) ApexDestructibleActor->releaseChunkEventBuffer(); } } // Update poses for visible chunks const physx::PxU16* VisibleChunks = ApexDestructibleActor->getVisibleChunks(); physx::PxU16 VisibleChunkCount = ApexDestructibleActor->getNumVisibleChunks(); while (VisibleChunkCount--) { const physx::PxU16 ChunkIndex = *VisibleChunks++; // BRGTODO : Make a direct method to access the Px objects' quats const physx::PxMat44 ChunkPoseRT = ApexDestructibleActor->getChunkPose(ChunkIndex); // Unscaled const physx::PxTransform Transform(ChunkPoseRT); SetChunkWorldRT(ChunkIndex, P2UQuat(Transform.q), P2UVector(Transform.p)); } // Send bones to render thread at end of frame MarkRenderDynamicDataDirty(); } #endif // #if WITH_APEX }
FMatrix PTransform2UMatrix(const PxTransform& PTM) { FQuat UQuat = P2UQuat(PTM.q); FVector UPos = P2UVector(PTM.p); FMatrix Result = FQuatRotationTranslationMatrix(UQuat, UPos); return Result; }
FTransform P2UTransform(const PxTransform& PTM) { FQuat UQuat = P2UQuat(PTM.q); FVector UPos = P2UVector(PTM.p); FTransform Result = FTransform(UQuat, UPos); return Result; }
static void BatchPxRenderBufferLines(class ULineBatchComponent& LineBatcherToUse, const PxRenderBuffer& DebugData) { int32 NumPoints = DebugData.getNbPoints(); if (NumPoints > 0) { const PxDebugPoint* Points = DebugData.getPoints(); for (int32 i = 0; i<NumPoints; i++) { LineBatcherToUse.DrawPoint(P2UVector(Points->pos), FColor((uint32)Points->color), 2, SDPG_World); Points++; } } // Build a list of all the lines we want to draw TArray<FBatchedLine> DebugLines; // Add all the 'lines' from PhysX int32 NumLines = DebugData.getNbLines(); if (NumLines > 0) { const PxDebugLine* Lines = DebugData.getLines(); for (int32 i = 0; i<NumLines; i++) { new(DebugLines)FBatchedLine(P2UVector(Lines->pos0), P2UVector(Lines->pos1), FColor((uint32)Lines->color0), 0.f, 0.0f, SDPG_World); Lines++; } } // Add all the 'triangles' from PhysX int32 NumTris = DebugData.getNbTriangles(); if (NumTris > 0) { const PxDebugTriangle* Triangles = DebugData.getTriangles(); for (int32 i = 0; i<NumTris; i++) { new(DebugLines)FBatchedLine(P2UVector(Triangles->pos0), P2UVector(Triangles->pos1), FColor((uint32)Triangles->color0), 0.f, 0.0f, SDPG_World); new(DebugLines)FBatchedLine(P2UVector(Triangles->pos1), P2UVector(Triangles->pos2), FColor((uint32)Triangles->color1), 0.f, 0.0f, SDPG_World); new(DebugLines)FBatchedLine(P2UVector(Triangles->pos2), P2UVector(Triangles->pos0), FColor((uint32)Triangles->color2), 0.f, 0.0f, SDPG_World); Triangles++; } } // Draw them all in one call. if (DebugLines.Num() > 0) { LineBatcherToUse.DrawLines(DebugLines); } }
static FVector FindBoxOpposingNormal(const PxLocationHit& PHit, const FVector& TraceDirectionDenorm, const FVector InNormal) { // We require normal info for our algorithm. const bool bNormalData = (PHit.flags & PxHitFlag::eNORMAL); if (!bNormalData) { return InNormal; } PxBoxGeometry PxBoxGeom; const bool bReadGeomSuccess = PHit.shape->getBoxGeometry(PxBoxGeom); check(bReadGeomSuccess); // This function should only be used for box geometry const PxTransform LocalToWorld = PxShapeExt::getGlobalPose(*PHit.shape, *PHit.actor); // Find which faces were included in the contact normal, and for multiple faces, use the one most opposing the sweep direction. const PxVec3 ContactNormalLocal = LocalToWorld.rotateInv(PHit.normal); const float* ContactNormalLocalPtr = &ContactNormalLocal.x; const PxVec3 TraceDirDenormWorld = U2PVector(TraceDirectionDenorm); const float* TraceDirDenormWorldPtr = &TraceDirDenormWorld.x; const PxVec3 TraceDirDenormLocal = LocalToWorld.rotateInv(TraceDirDenormWorld); const float* TraceDirDenormLocalPtr = &TraceDirDenormLocal.x; PxVec3 BestLocalNormal(ContactNormalLocal); float* BestLocalNormalPtr = &BestLocalNormal.x; float BestOpposingDot = FLT_MAX; for (int32 i=0; i < 3; i++) { // Select axis of face to compare to, based on normal. if (ContactNormalLocalPtr[i] > KINDA_SMALL_NUMBER) { const float TraceDotFaceNormal = TraceDirDenormLocalPtr[i]; // TraceDirDenormLocal.dot(BoxFaceNormal) if (TraceDotFaceNormal < BestOpposingDot) { BestOpposingDot = TraceDotFaceNormal; BestLocalNormal = PxVec3(0.f); BestLocalNormalPtr[i] = 1.f; } } else if (ContactNormalLocalPtr[i] < -KINDA_SMALL_NUMBER) { const float TraceDotFaceNormal = -TraceDirDenormLocalPtr[i]; // TraceDirDenormLocal.dot(BoxFaceNormal) if (TraceDotFaceNormal < BestOpposingDot) { BestOpposingDot = TraceDotFaceNormal; BestLocalNormal = PxVec3(0.f); BestLocalNormalPtr[i] = -1.f; } } } // Fill in result const PxVec3 WorldNormal = LocalToWorld.rotate(BestLocalNormal); return P2UVector(WorldNormal); }
FVector UVehicleWheel::GetPhysicsLocation() { if ( WheelShape ) { PxVec3 PLocation = VehicleSim->PVehicle->getRigidDynamicActor()->getGlobalPose().transform( WheelShape->getLocalPose() ).p; return P2UVector( PLocation ); } return FVector(0.0f); }
// NB: ElemTM is assumed to have no scaling in it! void FKConvexElem::DrawElemWire(FPrimitiveDrawInterface* PDI, const FTransform& ElemTM, const FVector& Scale3D, const FColor Color) { #if WITH_PHYSX FTransform LocalToWorld = ElemTM; LocalToWorld.SetScale3D(Scale3D); PxConvexMesh* Mesh = ConvexMesh; if(Mesh) { // Draw each triangle that makes up the convex hull PxU32 NbVerts = Mesh->getNbVertices(); const PxVec3* Vertices = Mesh->getVertices(); TArray<FVector> TransformedVerts; TransformedVerts.AddUninitialized(NbVerts); for(PxU32 i=0; i<NbVerts; i++) { TransformedVerts[i] = LocalToWorld.TransformPosition( P2UVector(Vertices[i]) ); } const PxU8* PIndexBuffer = Mesh->getIndexBuffer(); PxU32 NbPolygons = Mesh->getNbPolygons(); for(PxU32 i=0;i<NbPolygons;i++) { PxHullPolygon Data; bool bStatus = Mesh->getPolygonData(i, Data); check(bStatus); const PxU8* PIndices = PIndexBuffer + Data.mIndexBase; for(PxU16 j=0;j<Data.mNbVerts;j++) { // Get the verts that make up this line. int32 I0 = PIndices[j]; int32 I1 = PIndices[j+1]; // Loop back last and first vertices if(j==Data.mNbVerts - 1) { I1 = PIndices[0]; } PDI->DrawLine( TransformedVerts[I0], TransformedVerts[I1], Color, SDPG_World ); } } } else { UE_LOG(LogPhysics, Log, TEXT("FKConvexElem::DrawElemWire : No ConvexMesh, so unable to draw.")); } #endif // WITH_PHYSX }
void UDestructibleComponent::OnDamageEvent(const NxApexDamageEventReportData& InDamageEvent) { FVector HitPosition = P2UVector(InDamageEvent.hitPosition); FVector HitDirection = P2UVector(InDamageEvent.hitDirection); OnComponentFracture.Broadcast(HitPosition, HitDirection); if (ADestructibleActor * DestructibleActor = Cast<ADestructibleActor>(GetOwner())) { DestructibleActor->OnActorFracture.Broadcast(HitPosition, HitDirection); } SpawnFractureEffectsFromDamageEvent(InDamageEvent); // After receiving damage, no longer receive decals. if (bReceivesDecals) { bReceivesDecals = false; MarkRenderStateDirty(); } }
float FKConvexElem::GetVolume(const FVector& Scale) const { float Volume = 0.0f; #if WITH_PHYSX if (ConvexMesh != NULL) { // Preparation for convex mesh scaling implemented in another changelist FTransform ScaleTransform = FTransform(FQuat::Identity, FVector::ZeroVector, Scale); int32 NumPolys = ConvexMesh->getNbPolygons(); PxHullPolygon PolyData; const PxVec3* Vertices = ConvexMesh->getVertices(); const PxU8* Indices = ConvexMesh->getIndexBuffer(); for (int32 PolyIdx = 0; PolyIdx < NumPolys; ++PolyIdx) { if (ConvexMesh->getPolygonData(PolyIdx, PolyData)) { for (int32 VertIdx = 2; VertIdx < PolyData.mNbVerts; ++ VertIdx) { // Grab triangle indices that we hit int32 I0 = Indices[PolyData.mIndexBase + 0]; int32 I1 = Indices[PolyData.mIndexBase + (VertIdx - 1)]; int32 I2 = Indices[PolyData.mIndexBase + VertIdx]; Volume += SignedVolumeOfTriangle(ScaleTransform.TransformPosition(P2UVector(Vertices[I0])), ScaleTransform.TransformPosition(P2UVector(Vertices[I1])), ScaleTransform.TransformPosition(P2UVector(Vertices[I2]))); } } } } #endif // WITH_PHYSX return Volume; }
void FDestructibleMeshEditorViewportClient::Draw( const FSceneView* View,FPrimitiveDrawInterface* PDI ) { FEditorViewportClient::Draw(View, PDI); #if WITH_APEX const bool DrawChunkMarker = true; UDestructibleComponent* Comp = PreviewDestructibleComp.Get(); if (Comp) { if (Comp->DestructibleMesh != NULL && Comp->DestructibleMesh->FractureSettings != NULL) { if (Comp->DestructibleMesh->ApexDestructibleAsset != NULL) { NxDestructibleAsset* Asset = Comp->DestructibleMesh->ApexDestructibleAsset; const NxRenderMeshAsset* RenderMesh = Asset->getRenderMeshAsset(); for (uint32 i=0; i < Asset->getChunkCount(); ++i) { int32 PartIdx = Asset->getPartIndex(i); int32 BoneIdx = i+1; if ( SelectedChunkIndices.Contains(i) ) { PxBounds3 PBounds = RenderMesh->getBounds(PartIdx); FVector Center = P2UVector(PBounds.getCenter()) + Comp->GetBoneLocation(Comp->GetBoneName(BoneIdx)); FVector Extent = P2UVector(PBounds.getExtents()); FBox Bounds(Center - Extent, Center + Extent); DrawWireBox(PDI, Bounds, FColor::Blue, SDPG_World); } } } } } #endif // WITH_APEX }
void UDestructibleComponent::SpawnFractureEffectsFromDamageEvent(const NxApexDamageEventReportData& InDamageEvent) { // Use the component's fracture effects if the override is selected, otherwise use fracture effects from the asset TArray<FFractureEffect>& UseFractureEffects = (bFractureEffectOverride || !SkeletalMesh) ? FractureEffects : CastChecked<UDestructibleMesh>(SkeletalMesh)->FractureEffects; UDestructibleMesh* TheDestructibleMesh = GetDestructibleMesh(); if (!TheDestructibleMesh) { return; } // We keep track of the handled parent chunks here TArray<int32> HandledParents; for(physx::PxU32 eventN = 0; eventN < InDamageEvent.fractureEventListSize; ++eventN) { const NxApexChunkData& chunkData = InDamageEvent.fractureEventList[eventN]; if( chunkData.depth < (physx::PxU32)UseFractureEffects.Num() ) { // We can get the root chunk here as well, so make sure that the parent index is 0, even for the root chunk int32 ParentIdx = FMath::Max(TheDestructibleMesh->ApexDestructibleAsset->getChunkParentIndex(chunkData.index), 0); // We can test a number of flags - we'll play an effect if the chunk was destroyed // As we only get the fractured event here for chunks that come free, we spawn fracture // effects only once per unique parent if((chunkData.flags & NxApexChunkFlag::FRACTURED) && !HandledParents.Contains(ParentIdx)) { FVector Position = P2UVector(chunkData.worldBounds.getCenter()); FFractureEffect& FractureEffect = UseFractureEffects[chunkData.depth]; if( FractureEffect.Sound != NULL ) { // Spawn sound UGameplayStatics::PlaySoundAtLocation( this, FractureEffect.Sound, Position ); } if( FractureEffect.ParticleSystem != NULL ) { // Spawn particle system UParticleSystemComponent* ParticleSystemComponent = UGameplayStatics::SpawnEmitterAtLocation( this, FractureEffect.ParticleSystem, Position ); // Disable shadows, since destructibles tend to generate a lot of these if (ParticleSystemComponent != NULL) { ParticleSystemComponent->CastShadow = false; } } HandledParents.Add(ParentIdx); } } } }
void FConstraintInstance::GetConstraintForce(FVector& OutLinearForce, FVector& OutAngularForce) { #if WITH_PHYSX if (PxD6Joint* Joint = GetUnbrokenJoint()) { PxVec3 PxOutLinearForce; PxVec3 PxOutAngularForce; Joint->getConstraint()->getForce(PxOutLinearForce, PxOutAngularForce); OutLinearForce = P2UVector(PxOutLinearForce); OutAngularForce = P2UVector(PxOutAngularForce); } else { OutLinearForce = FVector::ZeroVector; OutAngularForce = FVector::ZeroVector; } #else OutLinearForce = FVector::ZeroVector; OutAngularForce = FVector::ZeroVector; #endif }
void SetupDriveHelper(const UWheeledVehicleMovementComponent4W* VehicleData, const PxVehicleWheelsSimData* PWheelsSimData, PxVehicleDriveSimData4W& DriveData) { PxVehicleDifferential4WData DifferentialSetup; GetVehicleDifferential4WSetup(VehicleData->DifferentialSetup, DifferentialSetup); DriveData.setDiffData(DifferentialSetup); PxVehicleEngineData EngineSetup; GetVehicleEngineSetup(VehicleData->EngineSetup, EngineSetup); DriveData.setEngineData(EngineSetup); PxVehicleClutchData ClutchSetup; ClutchSetup.mStrength = VehicleData->ClutchStrength; DriveData.setClutchData(ClutchSetup); FVector WheelCentreOffsets[4]; WheelCentreOffsets[PxVehicleDrive4WWheelOrder::eFRONT_LEFT] = P2UVector(PWheelsSimData->getWheelCentreOffset(PxVehicleDrive4WWheelOrder::eFRONT_LEFT)); WheelCentreOffsets[PxVehicleDrive4WWheelOrder::eFRONT_RIGHT] = P2UVector(PWheelsSimData->getWheelCentreOffset(PxVehicleDrive4WWheelOrder::eFRONT_RIGHT)); WheelCentreOffsets[PxVehicleDrive4WWheelOrder::eREAR_LEFT] = P2UVector(PWheelsSimData->getWheelCentreOffset(PxVehicleDrive4WWheelOrder::eREAR_LEFT)); WheelCentreOffsets[PxVehicleDrive4WWheelOrder::eREAR_RIGHT] = P2UVector(PWheelsSimData->getWheelCentreOffset(PxVehicleDrive4WWheelOrder::eREAR_RIGHT)); PxVehicleAckermannGeometryData AckermannSetup; AckermannSetup.mAccuracy = VehicleData->AckermannAccuracy; AckermannSetup.mAxleSeparation = FMath::Abs(WheelCentreOffsets[PxVehicleDrive4WWheelOrder::eFRONT_LEFT].X - WheelCentreOffsets[PxVehicleDrive4WWheelOrder::eREAR_LEFT].X); AckermannSetup.mFrontWidth = FMath::Abs(WheelCentreOffsets[PxVehicleDrive4WWheelOrder::eFRONT_RIGHT].Y - WheelCentreOffsets[PxVehicleDrive4WWheelOrder::eFRONT_LEFT].Y); AckermannSetup.mRearWidth = FMath::Abs(WheelCentreOffsets[PxVehicleDrive4WWheelOrder::eREAR_RIGHT].Y - WheelCentreOffsets[PxVehicleDrive4WWheelOrder::eREAR_LEFT].Y); DriveData.setAckermannGeometryData(AckermannSetup); PxVehicleGearsData GearSetup; GetVehicleGearSetup(VehicleData->GearSetup, GearSetup); DriveData.setGearsData(GearSetup); PxVehicleAutoBoxData AutoBoxSetup; GetVehicleAutoBoxSetup(VehicleData->AutoBoxSetup, AutoBoxSetup); DriveData.setAutoBoxData(AutoBoxSetup); }
static FVector FindConvexMeshOpposingNormal(const PxLocationHit& PHit, const FVector& TraceDirectionDenorm, const FVector InNormal) { if (IsInvalidFaceIndex(PHit.faceIndex)) { return InNormal; } PxConvexMeshGeometry PConvexMeshGeom; bool bSuccess = PHit.shape->getConvexMeshGeometry(PConvexMeshGeom); check(bSuccess); //should only call this function when we have a convex mesh if (PConvexMeshGeom.convexMesh) { check(PHit.faceIndex < PConvexMeshGeom.convexMesh->getNbPolygons()); const PxU32 PolyIndex = PHit.faceIndex; PxHullPolygon PPoly; bool bSuccessData = PConvexMeshGeom.convexMesh->getPolygonData(PolyIndex, PPoly); if (bSuccessData) { // Account for non-uniform scale in local space normal. const PxVec3 PPlaneNormal(PPoly.mPlane[0], PPoly.mPlane[1], PPoly.mPlane[2]); const PxVec3 PLocalPolyNormal = TransformNormalToShapeSpace(PConvexMeshGeom.scale, PPlaneNormal.getNormalized()); // Convert to world space const PxTransform PShapeWorldPose = PxShapeExt::getGlobalPose(*PHit.shape, *PHit.actor); const PxVec3 PWorldPolyNormal = PShapeWorldPose.rotate(PLocalPolyNormal); const FVector OutNormal = P2UVector(PWorldPolyNormal); #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) if (!OutNormal.IsNormalized()) { UE_LOG(LogPhysics, Warning, TEXT("Non-normalized Normal (Hit shape is ConvexMesh): %s (LocalPolyNormal:%s)"), *OutNormal.ToString(), *P2UVector(PLocalPolyNormal).ToString()); UE_LOG(LogPhysics, Warning, TEXT("WorldTransform \n: %s"), *P2UTransform(PShapeWorldPose).ToString()); } #endif return OutNormal; } } return InNormal; }
static bool FindHeightFieldOpposingNormal(const PxLocationHit& PHit, const FVector& TraceDirectionDenorm, FVector& OutNormal) { PxHeightFieldGeometry PHeightFieldGeom; bool bSuccess = PHit.shape->getHeightFieldGeometry(PHeightFieldGeom); check(bSuccess); //we should only call this function when we have a heightfield if (PHeightFieldGeom.heightField) { const PxU32 TriIndex = PHit.faceIndex; const PxTransform PShapeWorldPose = PxShapeExt::getGlobalPose(*PHit.shape, *PHit.actor); PxTriangle Tri; PxMeshQuery::getTriangle(PHeightFieldGeom, PShapeWorldPose, TriIndex, Tri); PxVec3 TriNormal; Tri.normal(TriNormal); OutNormal = P2UVector(TriNormal); return true; } return false; }
static bool ComputeInflatedMTD_Internal(const float MtdInflation, const PxLocationHit& PHit, FHitResult& OutResult, const PxTransform& QueryTM, const PxGeometry& Geom, const PxTransform& PShapeWorldPose) { PxGeometry* InflatedGeom = NULL; PxVec3 PxMtdNormal(0.f); PxF32 PxMtdDepth = 0.f; const PxGeometry& POtherGeom = PHit.shape->getGeometry().any(); const bool bMtdResult = PxGeometryQuery::computePenetration(PxMtdNormal, PxMtdDepth, Geom, QueryTM, POtherGeom, PShapeWorldPose); if (bMtdResult) { if (PxMtdNormal.isFinite()) { OutResult.ImpactNormal = P2UVector(PxMtdNormal); OutResult.PenetrationDepth = FMath::Max(FMath::Abs(PxMtdDepth) - MtdInflation, 0.f) + KINDA_SMALL_NUMBER; return true; } else { UE_LOG(LogPhysics, Verbose, TEXT("Warning: ComputeInflatedMTD_Internal: MTD returned NaN :( normal: (X:%f, Y:%f, Z:%f)"), PxMtdNormal.x, PxMtdNormal.y, PxMtdNormal.z); } } return false; }
/** Util to convert an overlapped shape into a sweep hit result, returns whether it was a blocking hit. */ static bool ConvertOverlappedShapeToImpactHit(const UWorld* World, const PxLocationHit& PHit, const FVector& StartLoc, const FVector& EndLoc, FHitResult& OutResult, const PxGeometry& Geom, const PxTransform& QueryTM, const PxFilterData& QueryFilter, bool bReturnPhysMat) { SCOPE_CYCLE_COUNTER(STAT_CollisionConvertOverlapToHit); const PxShape* PShape = PHit.shape; const PxRigidActor* PActor = PHit.actor; const uint32 FaceIdx = PHit.faceIndex; // See if this is a 'blocking' hit PxFilterData PShapeFilter = PShape->getQueryFilterData(); PxSceneQueryHitType::Enum HitType = FPxQueryFilterCallback::CalcQueryHitType(QueryFilter, PShapeFilter); const bool bBlockingHit = (HitType == PxSceneQueryHitType::eBLOCK); OutResult.bBlockingHit = bBlockingHit; // Time of zero because initially overlapping OutResult.bStartPenetrating = true; OutResult.Time = 0.f; OutResult.Distance = 0.f; // Return start location as 'safe location' OutResult.Location = P2UVector(QueryTM.p); OutResult.ImpactPoint = OutResult.Location; // @todo not really sure of a better thing to do here... OutResult.TraceStart = StartLoc; OutResult.TraceEnd = EndLoc; const bool bFiniteNormal = PHit.normal.isFinite(); const bool bValidNormal = (PHit.flags & PxHitFlag::eNORMAL) && bFiniteNormal; // Use MTD result if possible. We interpret the MTD vector as both the direction to move and the opposing normal. if (bValidNormal) { OutResult.ImpactNormal = P2UVector(PHit.normal); OutResult.PenetrationDepth = FMath::Abs(PHit.distance); } else { // Fallback normal if we can't find it with MTD or otherwise. OutResult.ImpactNormal = FVector::UpVector; OutResult.PenetrationDepth = 0.f; if (!bFiniteNormal) { UE_LOG(LogPhysics, Verbose, TEXT("Warning: ConvertOverlappedShapeToImpactHit: MTD returned NaN :( normal: (X:%f, Y:%f, Z:%f)"), PHit.normal.x, PHit.normal.y, PHit.normal.z); } } #if DRAW_OVERLAPPING_TRIS if (CVarShowInitialOverlaps.GetValueOnAnyThread() != 0 && World && World->IsGameWorld()) { FVector DummyNormal(0.f); const PxTransform PShapeWorldPose = PxShapeExt::getGlobalPose(*PShape, *PActor); FindOverlappedTriangleNormal(World, Geom, QueryTM, PShape, PShapeWorldPose, DummyNormal, 0.f, true); } #endif if (bBlockingHit) { // Zero-distance hits are often valid hits and we can extract the hit normal. // For invalid normals we can try other methods as well (get overlapping triangles). if (PHit.distance == 0.f || !bValidNormal) { const PxTransform PShapeWorldPose = PxShapeExt::getGlobalPose(*PShape, *PActor); // Try MTD with a small inflation for better accuracy, then a larger one in case the first one fails due to precision issues. static const float SmallMtdInflation = 0.250f; static const float LargeMtdInflation = 1.750f; if (ComputeInflatedMTD(SmallMtdInflation, PHit, OutResult, QueryTM, Geom, PShapeWorldPose) || ComputeInflatedMTD(LargeMtdInflation, PHit, OutResult, QueryTM, Geom, PShapeWorldPose)) { // Success } else { static const float SmallOverlapInflation = 0.250f; if (FindOverlappedTriangleNormal(World, Geom, QueryTM, PShape, PShapeWorldPose, OutResult.ImpactNormal, 0.f, false) || FindOverlappedTriangleNormal(World, Geom, QueryTM, PShape, PShapeWorldPose, OutResult.ImpactNormal, SmallOverlapInflation, false)) { // Success } else { // MTD failed, use point distance. This is not ideal. // Note: faceIndex seems to be unreliable for convex meshes in these cases, so not using FindGeomOpposingNormal() for them here. PxGeometry& PGeom = PShape->getGeometry().any(); PxVec3 PClosestPoint; const float Distance = PxGeometryQuery::pointDistance(QueryTM.p, PGeom, PShapeWorldPose, &PClosestPoint); if (Distance < KINDA_SMALL_NUMBER) { UE_LOG(LogCollision, Verbose, TEXT("Warning: ConvertOverlappedShapeToImpactHit: Query origin inside shape, giving poor MTD.")); PClosestPoint = PxShapeExt::getWorldBounds(*PShape, *PActor).getCenter(); } OutResult.ImpactNormal = (OutResult.Location - P2UVector(PClosestPoint)).GetSafeNormal(); } } } } else { // non blocking hit (overlap). if (!bValidNormal) { OutResult.ImpactNormal = (StartLoc - EndLoc).GetSafeNormal(); ensure(OutResult.ImpactNormal.IsNormalized()); } } OutResult.Normal = OutResult.ImpactNormal; SetHitResultFromShapeAndFaceIndex(PShape, PActor, FaceIdx, OutResult, bReturnPhysMat); return bBlockingHit; }
FVector FindBestOverlappingNormal(const UWorld* World, const PxGeometry& Geom, const PxTransform& QueryTM, const GeomType& ShapeGeom, const PxTransform& PShapeWorldPose, PxU32* HitTris, int32 NumTrisHit, bool bCanDrawOverlaps = false) { #if DRAW_OVERLAPPING_TRIS const float Lifetime = 5.f; bCanDrawOverlaps &= World && World->IsGameWorld() && World->PersistentLineBatcher && (World->PersistentLineBatcher->BatchedLines.Num() < 2048); if (bCanDrawOverlaps) { TArray<FOverlapResult> Overlaps; DrawGeomOverlaps(World, Geom, QueryTM, Overlaps, Lifetime); } const FLinearColor LineColor = FLinearColor::Green; const FLinearColor NormalColor = FLinearColor::Red; const FLinearColor PointColor = FLinearColor::Yellow; #endif // DRAW_OVERLAPPING_TRIS // Track the best triangle plane distance float BestPlaneDist = -BIG_NUMBER; FVector BestPlaneNormal(0, 0, 1); // Iterate over triangles for (int32 TriIdx = 0; TriIdx < NumTrisHit; TriIdx++) { PxTriangle Tri; PxMeshQuery::getTriangle(ShapeGeom, PShapeWorldPose, HitTris[TriIdx], Tri); const FVector A = P2UVector(Tri.verts[0]); const FVector B = P2UVector(Tri.verts[1]); const FVector C = P2UVector(Tri.verts[2]); FVector TriNormal = ((B - A) ^ (C - A)); TriNormal = TriNormal.GetSafeNormal(); const FPlane TriPlane(A, TriNormal); const FVector QueryCenter = P2UVector(QueryTM.p); const float DistToPlane = TriPlane.PlaneDot(QueryCenter); if (DistToPlane > BestPlaneDist) { BestPlaneDist = DistToPlane; BestPlaneNormal = TriNormal; } #if DRAW_OVERLAPPING_TRIS if (bCanDrawOverlaps && (World->PersistentLineBatcher->BatchedLines.Num() < 2048)) { static const float LineThickness = 0.9f; static const float NormalThickness = 0.75f; static const float PointThickness = 5.0f; World->PersistentLineBatcher->DrawLine(A, B, LineColor, SDPG_Foreground, LineThickness, Lifetime); World->PersistentLineBatcher->DrawLine(B, C, LineColor, SDPG_Foreground, LineThickness, Lifetime); World->PersistentLineBatcher->DrawLine(C, A, LineColor, SDPG_Foreground, LineThickness, Lifetime); const FVector Centroid((A + B + C) / 3.f); World->PersistentLineBatcher->DrawLine(Centroid, Centroid + (35.0f*TriNormal), NormalColor, SDPG_Foreground, NormalThickness, Lifetime); World->PersistentLineBatcher->DrawPoint(Centroid + (35.0f*TriNormal), NormalColor, PointThickness, SDPG_Foreground, Lifetime); World->PersistentLineBatcher->DrawPoint(A, PointColor, PointThickness, SDPG_Foreground, Lifetime); World->PersistentLineBatcher->DrawPoint(B, PointColor, PointThickness, SDPG_Foreground, Lifetime); World->PersistentLineBatcher->DrawPoint(C, PointColor, PointThickness, SDPG_Foreground, Lifetime); } #endif // DRAW_OVERLAPPING_TRIS } return BestPlaneNormal; }
void ConvertQueryImpactHit(const UWorld* World, const PxLocationHit& PHit, FHitResult& OutResult, float CheckLength, const PxFilterData& QueryFilter, const FVector& StartLoc, const FVector& EndLoc, const PxGeometry* const Geom, const PxTransform& QueryTM, bool bReturnFaceIndex, bool bReturnPhysMat) { SCOPE_CYCLE_COUNTER(STAT_ConvertQueryImpactHit); checkSlow(PHit.flags & PxHitFlag::eDISTANCE); const bool bInitialOverlap = PHit.hadInitialOverlap(); if (bInitialOverlap && Geom != nullptr) { ConvertOverlappedShapeToImpactHit(World, PHit, StartLoc, EndLoc, OutResult, *Geom, QueryTM, QueryFilter, bReturnPhysMat); return; } // See if this is a 'blocking' hit const PxFilterData PShapeFilter = PHit.shape->getQueryFilterData(); const PxSceneQueryHitType::Enum HitType = FPxQueryFilterCallback::CalcQueryHitType(QueryFilter, PShapeFilter); OutResult.bBlockingHit = (HitType == PxSceneQueryHitType::eBLOCK); OutResult.bStartPenetrating = bInitialOverlap; // calculate the hit time const float HitTime = PHit.distance/CheckLength; OutResult.Time = HitTime; OutResult.Distance = PHit.distance; // figure out where the the "safe" location for this shape is by moving from the startLoc toward the ImpactPoint const FVector TraceStartToEnd = EndLoc - StartLoc; const FVector SafeLocationToFitShape = StartLoc + (HitTime * TraceStartToEnd); OutResult.Location = SafeLocationToFitShape; const bool bUsePxPoint = ((PHit.flags & PxHitFlag::ePOSITION) && !bInitialOverlap); OutResult.ImpactPoint = bUsePxPoint ? P2UVector(PHit.position) : StartLoc; // Caution: we may still have an initial overlap, but with null Geom. This is the case for RayCast results. const bool bUsePxNormal = ((PHit.flags & PxHitFlag::eNORMAL) && !bInitialOverlap); FVector Normal = bUsePxNormal ? P2UVector(PHit.normal).GetSafeNormal() : -TraceStartToEnd.GetSafeNormal(); OutResult.Normal = Normal; OutResult.ImpactNormal = Normal; OutResult.TraceStart = StartLoc; OutResult.TraceEnd = EndLoc; #if ENABLE_CHECK_HIT_NORMAL CheckHitResultNormal(OutResult, TEXT("Invalid Normal from ConvertQueryImpactHit"), StartLoc, EndLoc, Geom); #endif // ENABLE_CHECK_HIT_NORMAL if (bUsePxNormal && !Normal.IsNormalized()) { // TraceStartToEnd should never be zero, because of the length restriction in the raycast and sweep tests. Normal = -TraceStartToEnd.GetSafeNormal(); OutResult.Normal = Normal; OutResult.ImpactNormal = Normal; } const PxGeometryType::Enum SweptGeometryType = Geom ? Geom->getType() : PxGeometryType::eINVALID; OutResult.ImpactNormal = FindGeomOpposingNormal(SweptGeometryType, PHit, TraceStartToEnd, Normal); // Fill in Actor, Component, material, etc. SetHitResultFromShapeAndFaceIndex(PHit.shape, PHit.actor, PHit.faceIndex, OutResult, bReturnPhysMat); if( PHit.shape->getGeometryType() == PxGeometryType::eHEIGHTFIELD) { // Lookup physical material for heightfields if (bReturnPhysMat && PHit.faceIndex != InvalidQueryHit.faceIndex) { PxMaterial* HitMaterial = PHit.shape->getMaterialFromInternalFaceIndex(PHit.faceIndex); if (HitMaterial != NULL) { OutResult.PhysMaterial = FPhysxUserData::Get<UPhysicalMaterial>(HitMaterial->userData); } } } else if(bReturnFaceIndex && PHit.shape->getGeometryType() == PxGeometryType::eTRIANGLEMESH) { PxTriangleMeshGeometry PTriMeshGeom; if( PHit.shape->getTriangleMeshGeometry(PTriMeshGeom) && PTriMeshGeom.triangleMesh != NULL && PHit.faceIndex < PTriMeshGeom.triangleMesh->getNbTriangles() ) { OutResult.FaceIndex = PTriMeshGeom.triangleMesh->getTrianglesRemap()[PHit.faceIndex]; } } }
static FVector FindTriMeshOpposingNormal(const PxLocationHit& PHit, const FVector& TraceDirectionDenorm, const FVector InNormal) { if (IsInvalidFaceIndex(PHit.faceIndex)) { return InNormal; } PxTriangleMeshGeometry PTriMeshGeom; bool bSuccess = PHit.shape->getTriangleMeshGeometry(PTriMeshGeom); check(bSuccess); //this function should only be called when we have a trimesh if (PTriMeshGeom.triangleMesh) { check(PHit.faceIndex < PTriMeshGeom.triangleMesh->getNbTriangles()); const PxU32 TriIndex = PHit.faceIndex; const void* Triangles = PTriMeshGeom.triangleMesh->getTriangles(); // Grab triangle indices that we hit int32 I0, I1, I2; if (PTriMeshGeom.triangleMesh->getTriangleMeshFlags() & PxTriangleMeshFlag::eHAS_16BIT_TRIANGLE_INDICES) { PxU16* P16BitIndices = (PxU16*)Triangles; I0 = P16BitIndices[(TriIndex * 3) + 0]; I1 = P16BitIndices[(TriIndex * 3) + 1]; I2 = P16BitIndices[(TriIndex * 3) + 2]; } else { PxU32* P32BitIndices = (PxU32*)Triangles; I0 = P32BitIndices[(TriIndex * 3) + 0]; I1 = P32BitIndices[(TriIndex * 3) + 1]; I2 = P32BitIndices[(TriIndex * 3) + 2]; } // Get verts we hit (local space) const PxVec3* PVerts = PTriMeshGeom.triangleMesh->getVertices(); const PxVec3 V0 = PVerts[I0]; const PxVec3 V1 = PVerts[I1]; const PxVec3 V2 = PVerts[I2]; // Find normal of triangle (local space), and account for non-uniform scale const PxVec3 PTempNormal = ((V1 - V0).cross(V2 - V0)).getNormalized(); const PxVec3 PLocalTriNormal = TransformNormalToShapeSpace(PTriMeshGeom.scale, PTempNormal); // Convert to world space const PxTransform PShapeWorldPose = PxShapeExt::getGlobalPose(*PHit.shape, *PHit.actor); const PxVec3 PWorldTriNormal = PShapeWorldPose.rotate(PLocalTriNormal); FVector OutNormal = P2UVector(PWorldTriNormal); if (PTriMeshGeom.meshFlags & PxMeshGeometryFlag::eDOUBLE_SIDED) { //double sided mesh so we need to consider direction of query const float sign = FVector::DotProduct(OutNormal, TraceDirectionDenorm) > 0.f ? -1.f : 1.f; OutNormal *= sign; } #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) if (!OutNormal.IsNormalized()) { UE_LOG(LogPhysics, Warning, TEXT("Non-normalized Normal (Hit shape is TriangleMesh): %s (V0:%s, V1:%s, V2:%s)"), *OutNormal.ToString(), *P2UVector(V0).ToString(), *P2UVector(V1).ToString(), *P2UVector(V2).ToString()); UE_LOG(LogPhysics, Warning, TEXT("WorldTransform \n: %s"), *P2UTransform(PShapeWorldPose).ToString()); } #endif return OutNormal; } return InNormal; }
EConvertQueryResult ConvertQueryImpactHit(const UWorld* World, const PxLocationHit& PHit, FHitResult& OutResult, float CheckLength, const PxFilterData& QueryFilter, const FVector& StartLoc, const FVector& EndLoc, const PxGeometry* const Geom, const PxTransform& QueryTM, bool bReturnFaceIndex, bool bReturnPhysMat) { SCOPE_CYCLE_COUNTER(STAT_ConvertQueryImpactHit); #if WITH_EDITOR if(bReturnFaceIndex && World->IsGameWorld()) { if(!ensure(UPhysicsSettings::Get()->bSuppressFaceRemapTable == false)) { UE_LOG(LogPhysics, Error, TEXT("A scene query is relying on face indices, but bSuppressFaceRemapTable is true.")); bReturnFaceIndex = false; } } #endif checkSlow(PHit.flags & PxHitFlag::eDISTANCE); const bool bInitialOverlap = PHit.hadInitialOverlap(); if (bInitialOverlap && Geom != nullptr) { ConvertOverlappedShapeToImpactHit(World, PHit, StartLoc, EndLoc, OutResult, *Geom, QueryTM, QueryFilter, bReturnPhysMat); return EConvertQueryResult::Valid; } // See if this is a 'blocking' hit const PxFilterData PShapeFilter = PHit.shape->getQueryFilterData(); const PxQueryHitType::Enum HitType = FPxQueryFilterCallback::CalcQueryHitType(QueryFilter, PShapeFilter); OutResult.bBlockingHit = (HitType == PxQueryHitType::eBLOCK); OutResult.bStartPenetrating = bInitialOverlap; // calculate the hit time const float HitTime = PHit.distance/CheckLength; OutResult.Time = HitTime; OutResult.Distance = PHit.distance; // figure out where the the "safe" location for this shape is by moving from the startLoc toward the ImpactPoint const FVector TraceStartToEnd = EndLoc - StartLoc; const FVector SafeLocationToFitShape = StartLoc + (HitTime * TraceStartToEnd); OutResult.Location = SafeLocationToFitShape; const bool bUsePxPoint = ((PHit.flags & PxHitFlag::ePOSITION) && !bInitialOverlap); if (bUsePxPoint && !PHit.position.isFinite()) { #if ENABLE_NAN_DIAGNOSTIC SetHitResultFromShapeAndFaceIndex(PHit.shape, PHit.actor, PHit.faceIndex, OutResult, bReturnPhysMat); UE_LOG(LogCore, Error, TEXT("ConvertQueryImpactHit() NaN details:\n>> Actor:%s (%s)\n>> Component:%s\n>> Item:%d\n>> BoneName:%s\n>> Time:%f\n>> Distance:%f\n>> Location:%s\n>> bIsBlocking:%d\n>> bStartPenetrating:%d"), *GetNameSafe(OutResult.GetActor()), OutResult.Actor.IsValid() ? *OutResult.GetActor()->GetPathName() : TEXT("no path"), *GetNameSafe(OutResult.GetComponent()), OutResult.Item, *OutResult.BoneName.ToString(), OutResult.Time, OutResult.Distance, *OutResult.Location.ToString(), OutResult.bBlockingHit ? 1 : 0, OutResult.bStartPenetrating ? 1 : 0); #endif // ENABLE_NAN_DIAGNOSTIC OutResult.Reset(); logOrEnsureNanError(TEXT("ConvertQueryImpactHit() received NaN/Inf for position: %.2f %.2f %.2f"), PHit.position.x, PHit.position.y, PHit.position.z); return EConvertQueryResult::Invalid; } OutResult.ImpactPoint = bUsePxPoint ? P2UVector(PHit.position) : StartLoc; // Caution: we may still have an initial overlap, but with null Geom. This is the case for RayCast results. const bool bUsePxNormal = ((PHit.flags & PxHitFlag::eNORMAL) && !bInitialOverlap); if (bUsePxNormal && !PHit.normal.isFinite()) { #if ENABLE_NAN_DIAGNOSTIC SetHitResultFromShapeAndFaceIndex(PHit.shape, PHit.actor, PHit.faceIndex, OutResult, bReturnPhysMat); UE_LOG(LogCore, Error, TEXT("ConvertQueryImpactHit() NaN details:\n>> Actor:%s (%s)\n>> Component:%s\n>> Item:%d\n>> BoneName:%s\n>> Time:%f\n>> Distance:%f\n>> Location:%s\n>> bIsBlocking:%d\n>> bStartPenetrating:%d"), *GetNameSafe(OutResult.GetActor()), OutResult.Actor.IsValid() ? *OutResult.GetActor()->GetPathName() : TEXT("no path"), *GetNameSafe(OutResult.GetComponent()), OutResult.Item, *OutResult.BoneName.ToString(), OutResult.Time, OutResult.Distance, *OutResult.Location.ToString(), OutResult.bBlockingHit ? 1 : 0, OutResult.bStartPenetrating ? 1 : 0); #endif // ENABLE_NAN_DIAGNOSTIC OutResult.Reset(); logOrEnsureNanError(TEXT("ConvertQueryImpactHit() received NaN/Inf for normal: %.2f %.2f %.2f"), PHit.normal.x, PHit.normal.y, PHit.normal.z); return EConvertQueryResult::Invalid; } FVector Normal = bUsePxNormal ? P2UVector(PHit.normal).GetSafeNormal() : -TraceStartToEnd.GetSafeNormal(); OutResult.Normal = Normal; OutResult.ImpactNormal = Normal; OutResult.TraceStart = StartLoc; OutResult.TraceEnd = EndLoc; #if ENABLE_CHECK_HIT_NORMAL CheckHitResultNormal(OutResult, TEXT("Invalid Normal from ConvertQueryImpactHit"), StartLoc, EndLoc, Geom); #endif // ENABLE_CHECK_HIT_NORMAL if (bUsePxNormal && !Normal.IsNormalized()) { // TraceStartToEnd should never be zero, because of the length restriction in the raycast and sweep tests. Normal = -TraceStartToEnd.GetSafeNormal(); OutResult.Normal = Normal; OutResult.ImpactNormal = Normal; } const PxGeometryType::Enum SweptGeometryType = Geom ? Geom->getType() : PxGeometryType::eINVALID; OutResult.ImpactNormal = FindGeomOpposingNormal(SweptGeometryType, PHit, TraceStartToEnd, Normal); // Fill in Actor, Component, material, etc. SetHitResultFromShapeAndFaceIndex(PHit.shape, PHit.actor, PHit.faceIndex, OutResult, bReturnPhysMat); PxGeometryType::Enum PGeomType = PHit.shape->getGeometryType(); if(PGeomType == PxGeometryType::eHEIGHTFIELD) { // Lookup physical material for heightfields if (bReturnPhysMat && PHit.faceIndex != InvalidQueryHit.faceIndex) { PxMaterial* HitMaterial = PHit.shape->getMaterialFromInternalFaceIndex(PHit.faceIndex); if (HitMaterial != NULL) { OutResult.PhysMaterial = FPhysxUserData::Get<UPhysicalMaterial>(HitMaterial->userData); } } } else if (bReturnFaceIndex && PGeomType == PxGeometryType::eTRIANGLEMESH) { PxTriangleMeshGeometry PTriMeshGeom; if( PHit.shape->getTriangleMeshGeometry(PTriMeshGeom) && PTriMeshGeom.triangleMesh != NULL && PHit.faceIndex < PTriMeshGeom.triangleMesh->getNbTriangles() ) { if (const PxU32* TriangleRemap = PTriMeshGeom.triangleMesh->getTrianglesRemap()) { OutResult.FaceIndex = TriangleRemap[PHit.faceIndex]; } } } return EConvertQueryResult::Valid; }
void FPhysXSimEventCallback::onContact(const PxContactPairHeader& PairHeader, const PxContactPair* Pairs, PxU32 NumPairs) { // Check actors are not destroyed if( PairHeader.flags & (PxContactPairHeaderFlag::eREMOVED_ACTOR_0 | PxContactPairHeaderFlag::eREMOVED_ACTOR_1) ) { UE_LOG(LogPhysics, Log, TEXT("%d onContact(): Actors have been deleted!"), GFrameCounter ); return; } const PxActor* PActor0 = PairHeader.actors[0]; const PxActor* PActor1 = PairHeader.actors[1]; check(PActor0 && PActor1); const PxRigidBody* PRigidBody0 = PActor0->is<PxRigidBody>(); const PxRigidBody* PRigidBody1 = PActor1->is<PxRigidBody>(); const FBodyInstance* BodyInst0 = FPhysxUserData::Get<FBodyInstance>(PActor0->userData); const FBodyInstance* BodyInst1 = FPhysxUserData::Get<FBodyInstance>(PActor1->userData); bool bEitherDestructible = false; // check if it's a destructible actor if (BodyInst0 == NULL) { if (const FDestructibleChunkInfo* DestructibleChunkInfo = FPhysxUserData::Get<FDestructibleChunkInfo>(PActor0->userData)) { bEitherDestructible = true; BodyInst0 = DestructibleChunkInfo->OwningComponent.IsValid() ? &DestructibleChunkInfo->OwningComponent->BodyInstance : NULL; } } if (BodyInst1 == NULL) { if (const FDestructibleChunkInfo* DestructibleChunkInfo = FPhysxUserData::Get<FDestructibleChunkInfo>(PActor1->userData)) { bEitherDestructible = true; BodyInst1 = DestructibleChunkInfo->OwningComponent.IsValid() ? &DestructibleChunkInfo->OwningComponent->BodyInstance : NULL; } } //if nothing valid just exit //if a destructible mesh you can get chunks that hit other chunks from the same body... this causes a lot of spam and doesn't seem like a very useful notification so I'm turning it off if(BodyInst0 == NULL || BodyInst1 == NULL || BodyInst0 == BodyInst1) { return; } //destruction applies damage when it hits something. Unfortunately it relies on the same flag that generates onContact. //We only want onContact events to happen if the user actually selected bNotifyRigidBodyCollision so we have to check if this is the case if (bEitherDestructible) { if (BodyInst0->bNotifyRigidBodyCollision == false && BodyInst1->bNotifyRigidBodyCollision == false) { return; } } TArray<FCollisionNotifyInfo>& PendingCollisionNotifies = OwningScene->GetPendingCollisionNotifies(SceneType); uint32 PreAddingCollisionNotify = PendingCollisionNotifies.Num() - 1; TArray<int32> PairNotifyMapping = FBodyInstance::AddCollisionNotifyInfo(BodyInst0, BodyInst1, Pairs, NumPairs, PendingCollisionNotifies); // Iterate through contact points for(uint32 PairIdx=0; PairIdx<NumPairs; PairIdx++) { int32 NotifyIdx = PairNotifyMapping[PairIdx]; if (NotifyIdx == -1) //the body instance this pair belongs to is not listening for events { continue; } FCollisionNotifyInfo * NotifyInfo = &PendingCollisionNotifies[NotifyIdx]; FCollisionImpactData* ImpactInfo = &(NotifyInfo->RigidCollisionData); const PxContactPair* Pair = Pairs + PairIdx; // Get the two shapes that are involved in the collision const PxShape* Shape0 = Pair->shapes[0]; check(Shape0); const PxShape* Shape1 = Pair->shapes[1]; check(Shape1); // Get materials PxMaterial* Material0 = nullptr; UPhysicalMaterial* PhysMat0 = nullptr; if(Shape0->getNbMaterials() == 1) //If we have simple geometry or only 1 material we set it here. Otherwise do it per face { Shape0->getMaterials(&Material0, 1); PhysMat0 = Material0 ? FPhysxUserData::Get<UPhysicalMaterial>(Material0->userData) : nullptr; } PxMaterial* Material1 = nullptr; UPhysicalMaterial* PhysMat1 = nullptr; if (Shape1->getNbMaterials() == 1) //If we have simple geometry or only 1 material we set it here. Otherwise do it per face { Shape1->getMaterials(&Material1, 1); PhysMat1 = Material1 ? FPhysxUserData::Get<UPhysicalMaterial>(Material1->userData) : nullptr; } // Iterate over contact points PxContactPairPoint ContactPointBuffer[16]; int32 NumContactPoints = Pair->extractContacts(ContactPointBuffer, 16); for(int32 PointIdx=0; PointIdx<NumContactPoints; PointIdx++) { const PxContactPairPoint& Point = ContactPointBuffer[PointIdx]; const PxVec3 NormalImpulse = Point.impulse.dot(Point.normal) * Point.normal; // project impulse along normal ImpactInfo->TotalNormalImpulse += P2UVector(NormalImpulse); ImpactInfo->TotalFrictionImpulse += P2UVector(Point.impulse - NormalImpulse); // friction is component not along contact normal // Get per face materials if(!Material0) //there is complex geometry or multiple materials so resolve the physical material here { if(PxMaterial* Material0PerFace = Shape0->getMaterialFromInternalFaceIndex(Point.internalFaceIndex0)) { PhysMat0 = FPhysxUserData::Get<UPhysicalMaterial>(Material0PerFace->userData); } } if (!Material1) //there is complex geometry or multiple materials so resolve the physical material here { if(PxMaterial* Material1PerFace = Shape1->getMaterialFromInternalFaceIndex(Point.internalFaceIndex1)) { PhysMat1 = FPhysxUserData::Get<UPhysicalMaterial>(Material1PerFace->userData); } } new(ImpactInfo->ContactInfos) FRigidBodyContactInfo( P2UVector(Point.position), P2UVector(Point.normal), -1.f * Point.separation, PhysMat0, PhysMat1); } } for (int32 NotifyIdx = PreAddingCollisionNotify + 1; NotifyIdx < PendingCollisionNotifies.Num(); NotifyIdx++) { FCollisionNotifyInfo * NotifyInfo = &PendingCollisionNotifies[NotifyIdx]; FCollisionImpactData* ImpactInfo = &(NotifyInfo->RigidCollisionData); // Discard pairs that don't generate any force (eg. have been rejected through a modify contact callback). if (ImpactInfo->TotalNormalImpulse.SizeSquared() < KINDA_SMALL_NUMBER) { PendingCollisionNotifies.RemoveAt(NotifyIdx); NotifyIdx--; } } }
/** Util to convert an overlapped shape into a sweep hit result, returns whether it was a blocking hit. */ static bool ConvertOverlappedShapeToImpactHit(const PxShape* PShape, const PxRigidActor* PActor, const FVector& StartLoc, const FVector& EndLoc, FHitResult& OutResult, const PxGeometry& Geom, const PxTransform& QueryTM, const PxFilterData& QueryFilter, bool bReturnPhysMat, uint32 FaceIdx) { OutResult.TraceStart = StartLoc; OutResult.TraceEnd = EndLoc; SetHitResultFromShapeAndFaceIndex(PShape, PActor, FaceIdx, OutResult, bReturnPhysMat); // Time of zero because initially overlapping OutResult.Time = 0.f; OutResult.bStartPenetrating = true; // See if this is a 'blocking' hit PxFilterData PShapeFilter = PShape->getQueryFilterData(); PxSceneQueryHitType::Enum HitType = FPxQueryFilterCallback::CalcQueryHitType(QueryFilter, PShapeFilter); OutResult.bBlockingHit = (HitType == PxSceneQueryHitType::eBLOCK); // Return start location as 'safe location' OutResult.Location = P2UVector(QueryTM.p); OutResult.ImpactPoint = OutResult.Location; // @todo not really sure of a better thing to do here... const PxTransform PShapeWorldPose = PxShapeExt::getGlobalPose(*PShape, *PActor); PxTriangleMeshGeometry PTriMeshGeom; if(PShape->getTriangleMeshGeometry(PTriMeshGeom)) { PxU32 HitTris[64]; bool bOverflow = false; int32 NumTrisHit = PxMeshQuery::findOverlapTriangleMesh(Geom, QueryTM, PTriMeshGeom, PShapeWorldPose, HitTris, 64, 0, bOverflow); #if DRAW_OVERLAPPING_TRIS TArray<FOverlapResult> Overlaps; DrawGeomOverlaps(World, Geom, QueryTM, Overlaps); TArray<FBatchedLine> Lines; const FLinearColor LineColor = FLinearColor(1.f,0.7f,0.7f); const FLinearColor NormalColor = FLinearColor(1.f,1.f,1.f); const float Lifetime = 5.f; #endif // DRAW_OVERLAPPING_TRIS // Track the best triangle plane distance float BestPlaneDist = -BIG_NUMBER; FVector BestPlaneNormal(0,0,1); FVector BestPointOnPlane(0,0,0); // Iterate over triangles for(int32 TriIdx = 0; TriIdx<NumTrisHit; TriIdx++) { PxTriangle Tri; PxMeshQuery::getTriangle(PTriMeshGeom, PShapeWorldPose, HitTris[TriIdx], Tri); const FVector A = P2UVector(Tri.verts[0]); const FVector B = P2UVector(Tri.verts[1]); const FVector C = P2UVector(Tri.verts[2]); FVector TriNormal = ((B-A) ^ (C-A)); // Use a more accurate normalization that avoids InvSqrtEst const float TriNormalSize = TriNormal.Size(); TriNormal = (TriNormalSize >= KINDA_SMALL_NUMBER ? TriNormal/TriNormalSize : FVector::ZeroVector); const FPlane TriPlane(A, TriNormal); const FVector QueryCenter = P2UVector(QueryTM.p); const float DistToPlane = TriPlane.PlaneDot(QueryCenter); if(DistToPlane > BestPlaneDist) { BestPlaneDist = DistToPlane; BestPlaneNormal = TriNormal; BestPointOnPlane = A; } #if DRAW_OVERLAPPING_TRIS Lines.Add(FBatchedLine(A, B, LineColor, Lifetime, 0.1f, SDPG_Foreground)); Lines.Add(FBatchedLine(B, C, LineColor, Lifetime, 0.1f, SDPG_Foreground)); Lines.Add(FBatchedLine(C, A, LineColor, Lifetime, 0.1f, SDPG_Foreground)); Lines.Add(FBatchedLine(A, A+(50.f*TriNormal), NormalColor, Lifetime, 0.1f, SDPG_Foreground)); #endif // DRAW_OVERLAPPING_TRIS } #if DRAW_OVERLAPPING_TRIS if ( World->PersistentLineBatcher ) { World->PersistentLineBatcher->DrawLines(Lines); } #endif // DRAW_OVERLAPPING_TRIS OutResult.ImpactNormal = BestPlaneNormal; } else { // use vector center of shape to query as good direction to move in PxGeometry& PGeom = PShape->getGeometry().any(); PxVec3 PClosestPoint; float Distance = PxGeometryQuery::pointDistance(QueryTM.p, PGeom, PShapeWorldPose, &PClosestPoint); if(Distance < KINDA_SMALL_NUMBER) { //UE_LOG(LogCollision, Warning, TEXT("ConvertOverlappedShapeToImpactHit: Query origin inside shape, giving poor MTD.")); PClosestPoint = PxShapeExt::getWorldBounds(*PShape, *PActor).getCenter(); } OutResult.ImpactNormal = (OutResult.Location - P2UVector(PClosestPoint)).SafeNormal(); } // Compute depenetration vector and distance if possible. PxVec3 PxMtdNormal(0.f); PxF32 PxMtdDepth = 0.f; PxGeometry& POtherGeom = PShape->getGeometry().any(); const bool bMtdResult = PxGeometryQuery::computePenetration(PxMtdNormal, PxMtdDepth, Geom, QueryTM, POtherGeom, PShapeWorldPose); if (bMtdResult) { const FVector MtdNormal = P2UVector(PxMtdNormal); OutResult.Normal = MtdNormal; OutResult.PenetrationDepth = FMath::Abs(PxMtdDepth) + KINDA_SMALL_NUMBER; // TODO: why are we getting negative values here from mtd sometimes? } else { OutResult.Normal = OutResult.ImpactNormal; } return OutResult.bBlockingHit; }