/** 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; }
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]; } } }
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; }
/** 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; }
void ConvertQueryImpactHit(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) { if (Geom != NULL && PHit.hadInitialOverlap()) { ConvertOverlappedShapeToImpactHit(PHit.shape, PHit.actor, StartLoc, EndLoc, OutResult, *Geom, QueryTM, QueryFilter, bReturnPhysMat, PHit.faceIndex); return; } SetHitResultFromShapeAndFaceIndex(PHit.shape, PHit.actor, PHit.faceIndex, OutResult, bReturnPhysMat); // calculate the hit time const float HitTime = PHit.distance/CheckLength; // figure out where the the "safe" location for this shape is by moving from the startLoc toward the ImpactPoint const FVector TraceDir = EndLoc - StartLoc; const FVector SafeLocationToFitShape = StartLoc + (HitTime * TraceDir); // Other info OutResult.Location = SafeLocationToFitShape; OutResult.ImpactPoint = P2UVector(PHit.position); OutResult.Normal = P2UVector(PHit.normal); OutResult.ImpactNormal = OutResult.Normal; OutResult.TraceStart = StartLoc; OutResult.TraceEnd = EndLoc; OutResult.Time = HitTime; // See if this is a 'blocking' hit PxFilterData PShapeFilter = PHit.shape->getQueryFilterData(); PxSceneQueryHitType::Enum HitType = FPxQueryFilterCallback::CalcQueryHitType(QueryFilter, PShapeFilter); OutResult.bBlockingHit = (HitType == PxSceneQueryHitType::eBLOCK); OutResult.bStartPenetrating = (PxU32)(PHit.hadInitialOverlap()); #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST || !WITH_EDITOR) CheckHitResultNormal(OutResult, TEXT("Invalid Normal from ConvertQueryImpactHit"), StartLoc, EndLoc, Geom); #endif // Special handling for swept-capsule results if(Geom && Geom->getType() == PxGeometryType::eCAPSULE) { const PxCapsuleGeometry* Capsule = static_cast<const PxCapsuleGeometry*>(Geom); // Compute better ImpactNormal. This is the same as Normal except when we hit convex/trimesh, and then its the most opposing normal of the geom at point of impact. FVector PointOnGeom(P2UVector(PHit.position)); if (FindGeomOpposingNormal(PHit, OutResult.ImpactNormal, PointOnGeom)) { ConvertConvexNormalToCapsuleNormal(Capsule->halfHeight, Capsule->radius, OutResult, &PointOnGeom); } else { ConvertConvexNormalToCapsuleNormal(Capsule->halfHeight, Capsule->radius, OutResult, NULL); OutResult.ImpactNormal = OutResult.Normal; } } else if (Geom && Geom->getType() == PxGeometryType::eSPHERE) { const PxSphereGeometry* Sphere = static_cast<const PxSphereGeometry*>(Geom); // Compute better ImpactNormal. This is the same as Normal except when we hit convex/trimesh, and then its the most opposing normal of the geom at point of impact. FVector PointOnGeom(P2UVector(PHit.position)); if (FindGeomOpposingNormal(PHit, OutResult.ImpactNormal, PointOnGeom)) { const FPlane GeomPlane(PointOnGeom, OutResult.ImpactNormal); const float DistFromPlane = FMath::Abs(GeomPlane.PlaneDot(OutResult.Location)); if (DistFromPlane >= Sphere->radius) { // Use the new (better) impact normal to compute a new impact point by projecting the sphere's location onto the geometry. OutResult.ImpactPoint = OutResult.Location - DistFromPlane * OutResult.ImpactNormal; } // Use the impact point to compute a better sphere surface normal (impact point to center of sphere). ConvertConvexNormalToSphereNormal(Sphere->radius, OutResult); } else { ConvertConvexNormalToSphereNormal(Sphere->radius, OutResult); OutResult.ImpactNormal = OutResult.Normal; } } if( PHit.shape->getGeometryType() == PxGeometryType::eHEIGHTFIELD) { // Lookup physical material for heightfields 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; PxConvexMeshGeometry PConvexMeshGeom; if( PHit.shape->getTriangleMeshGeometry(PTriMeshGeom) && PTriMeshGeom.triangleMesh != NULL && PHit.faceIndex < PTriMeshGeom.triangleMesh->getNbTriangles() ) { OutResult.FaceIndex = PTriMeshGeom.triangleMesh->getTrianglesRemap()[PHit.faceIndex]; } else { OutResult.FaceIndex = INDEX_NONE; } } else { OutResult.FaceIndex = INDEX_NONE; } }
void ConvertQueryImpactHit(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) { checkSlow(PHit.flags & PxHitFlag::eDISTANCE); if (Geom != NULL && PHit.hadInitialOverlap()) { ConvertOverlappedShapeToImpactHit(PHit, StartLoc, EndLoc, OutResult, *Geom, QueryTM, QueryFilter, bReturnPhysMat); return; } SetHitResultFromShapeAndFaceIndex(PHit.shape, PHit.actor, PHit.faceIndex, OutResult, bReturnPhysMat); // calculate the hit time const float HitTime = PHit.distance/CheckLength; // 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); // Other info OutResult.Location = SafeLocationToFitShape; OutResult.ImpactPoint = (PHit.flags & PxHitFlag::ePOSITION) ? P2UVector(PHit.position) : StartLoc; OutResult.Normal = (PHit.flags & PxHitFlag::eNORMAL) ? P2UVector(PHit.normal) : -TraceStartToEnd.GetSafeNormal(); OutResult.ImpactNormal = OutResult.Normal; OutResult.TraceStart = StartLoc; OutResult.TraceEnd = EndLoc; OutResult.Time = HitTime; // See if this is a 'blocking' hit PxFilterData PShapeFilter = PHit.shape->getQueryFilterData(); PxSceneQueryHitType::Enum HitType = FPxQueryFilterCallback::CalcQueryHitType(QueryFilter, PShapeFilter); OutResult.bBlockingHit = (HitType == PxSceneQueryHitType::eBLOCK); OutResult.bStartPenetrating = (PxU32)(PHit.hadInitialOverlap()); #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST || !WITH_EDITOR) CheckHitResultNormal(OutResult, TEXT("Invalid Normal from ConvertQueryImpactHit"), StartLoc, EndLoc, Geom); #endif PxGeometryType::Enum GeometryType = Geom ? Geom->getType() : PxGeometryType::eINVALID; // Special handling for swept-capsule results if (GeometryType == PxGeometryType::eCAPSULE || GeometryType == PxGeometryType::eSPHERE) { FindGeomOpposingNormal(PHit, TraceStartToEnd, OutResult.ImpactNormal); } if( PHit.shape->getGeometryType() == PxGeometryType::eHEIGHTFIELD) { // Lookup physical material for heightfields if (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]; } else { OutResult.FaceIndex = INDEX_NONE; } } else { OutResult.FaceIndex = INDEX_NONE; } }