Files
UnrealEngine/Engine/Source/Programs/HeadlessChaos/Private/HeadlessChaosTestDetectCollision.cpp
2025-05-18 13:04:45 +08:00

381 lines
18 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "HeadlessChaos.h"
#include "HeadlessChaosCollisionConstraints.h"
#include "HeadlessChaosTestUtility.h"
#include "Chaos/Collision/CapsuleConvexContactPoint.h"
#include "Chaos/Collision/ContactPoint.h"
#include "Chaos/Collision/SphereConvexContactPoint.h"
#include "Chaos/CollisionOneShotManifolds.h"
#include "Chaos/GJK.h"
#include "Chaos/Sphere.h"
#include "Chaos/Utilities.h"
#include "Modules/ModuleManager.h"
namespace ChaosTest
{
using namespace Chaos;
void CheckContactPoint(const FContactPoint& ContactPoint, const FVec3& ShapeContactPos0, const FVec3& ShapeContactPos1, const FVec3& ShapeContactNormal, const int32 NormalOwnerIndex, const FVec3 Normal, const FReal Phi)
{
const FReal DistanceTolerance = 1.e-3f;
const FReal NormalTolerance = 1.e-3f;
EXPECT_NEAR(ContactPoint.ShapeContactPoints[0].X, ShapeContactPos0.X, DistanceTolerance);
EXPECT_NEAR(ContactPoint.ShapeContactPoints[0].Y, ShapeContactPos0.Y, DistanceTolerance);
EXPECT_NEAR(ContactPoint.ShapeContactPoints[0].Z, ShapeContactPos0.Z, DistanceTolerance);
EXPECT_NEAR(ContactPoint.ShapeContactPoints[1].X, ShapeContactPos1.X, DistanceTolerance);
EXPECT_NEAR(ContactPoint.ShapeContactPoints[1].Y, ShapeContactPos1.Y, DistanceTolerance);
EXPECT_NEAR(ContactPoint.ShapeContactPoints[1].Z, ShapeContactPos1.Z, DistanceTolerance);
EXPECT_NEAR(ContactPoint.ShapeContactNormal.X, ShapeContactNormal.X, NormalTolerance);
EXPECT_NEAR(ContactPoint.ShapeContactNormal.Y, ShapeContactNormal.Y, NormalTolerance);
EXPECT_NEAR(ContactPoint.ShapeContactNormal.Z, ShapeContactNormal.Z, NormalTolerance);
EXPECT_NEAR(ContactPoint.Phi, Phi, DistanceTolerance);
}
//
//
// SPHERE - CONVEX TESTS
//
//
// Sphere-Convex(with margin) far separated when a face is the nearest feature
GTEST_TEST(DetectCollisionTests, TestSphereConvex_Separated_Face)
{
const FReal SphereRadius = 50.0f;
const FVec3 BoxSize = FVec3(1000, 1000, 50);
const FReal BoxMargin = 10.0f;
const FVec3 SpherePos = FVec3(0, 0, 70);
const FVec3 ConvexPos = FVec3(0, 0, -25);
const FImplicitSphere3 Sphere(FVec3(0,0,0), SphereRadius);
const FImplicitConvex3 Convex = CreateConvexBox(BoxSize, BoxMargin);
const FRigidTransform3 SphereTransform = FRigidTransform3(SpherePos, FRotation3::FromIdentity());
const FRigidTransform3 ConvexTransform = FRigidTransform3(ConvexPos, FRotation3::FromIdentity());
{
FContactPoint ContactPoint = SphereConvexContactPoint(Sphere, SphereTransform, Convex, ConvexTransform);
EXPECT_TRUE(ContactPoint.IsSet());
if (ContactPoint.IsSet())
{
CheckContactPoint(ContactPoint, FVec3(0, 0, -SphereRadius), FVec3(0, 0, 0.5f * BoxSize.Z), FVec3(0,0,1), 1, FVec3(0,0,1), SpherePos.Z - SphereRadius);
}
}
}
// Sphere-Convex(with margin) far separated when an edge is the nearest feature.
GTEST_TEST(DetectCollisionTests, TestSphereConvex_Separated_Edge)
{
const FReal SphereRadius = 50.0f;
const FVec3 BoxSize = FVec3(1000, 1000, 50);
const FReal BoxMargin = 10.0f;
const FVec3 SpherePos = FVec3(600, 0, 70);
const FVec3 ConvexPos = FVec3(0, 0, -25);
const FImplicitSphere3 Sphere(FVec3(0, 0, 0), SphereRadius);
const FImplicitConvex3 Convex = CreateConvexBox(BoxSize, BoxMargin);
const FRigidTransform3 SphereTransform = FRigidTransform3(SpherePos, FRotation3::FromIdentity());
const FRigidTransform3 ConvexTransform = FRigidTransform3(ConvexPos, FRotation3::FromIdentity());
FContactPoint ContactPoint = SphereConvexContactPoint(Sphere, SphereTransform, Convex, ConvexTransform);
EXPECT_TRUE(ContactPoint.IsSet());
if (ContactPoint.IsSet())
{
const FVec3 SphereOffset = FVec3(SpherePos.X - 0.5f * BoxSize.X, 0, SpherePos.Z);
const FVec3 ExpectedNormal = SphereOffset.GetSafeNormal();
const FReal ExpectedDistance = SphereOffset.Size() - Sphere.GetRadiusf();
CheckContactPoint(ContactPoint, -SphereRadius * ExpectedNormal, 0.5f * FVec3(BoxSize.X, 0, BoxSize.Z), ExpectedNormal, 1, ExpectedNormal, ExpectedDistance);
}
}
// Sphere-Convex(with margin) far separated when a vertex is the nearest feature.
GTEST_TEST(DetectCollisionTests, TestSphereConvex_Separated_Vertex)
{
const FReal SphereRadius = 50.0f;
const FVec3 BoxSize = FVec3(1000, 1000, 50);
const FReal BoxMargin = 10.0f;
const FVec3 SpherePos = FVec3(600, 540, 70);
const FVec3 ConvexPos = FVec3(0, 0, -25);
const FImplicitSphere3 Sphere(FVec3(0, 0, 0), SphereRadius);
const FImplicitConvex3 Convex = CreateConvexBox(BoxSize, BoxMargin);
const FRigidTransform3 SphereTransform = FRigidTransform3(SpherePos, FRotation3::FromIdentity());
const FRigidTransform3 ConvexTransform = FRigidTransform3(ConvexPos, FRotation3::FromIdentity());
FContactPoint ContactPoint = SphereConvexContactPoint(Sphere, SphereTransform, Convex, ConvexTransform);
EXPECT_TRUE(ContactPoint.IsSet());
if (ContactPoint.IsSet())
{
const FVec3 SphereOffset = FVec3(SpherePos.X - 0.5f * BoxSize.X, SpherePos.Y - 0.5f * BoxSize.Y, SpherePos.Z);
const FVec3 ExpectedNormal = SphereOffset.GetSafeNormal();
const FReal ExpectedDistance = SphereOffset.Size() - Sphere.GetRadiusf();
CheckContactPoint(ContactPoint, -SphereRadius * ExpectedNormal, 0.5f * FVec3(BoxSize.X, BoxSize.Y, BoxSize.Z), ExpectedNormal, 1, ExpectedNormal, ExpectedDistance);
}
}
// Sphere-Convex(with margin) far separated when a vertex is the nearest feature.
GTEST_TEST(DetectCollisionTests, TestSphereConvex_Scaled_Separated_Vertex)
{
const FReal SphereRadius = 50.0f;
const FVec3 BoxSize = FVec3(1000, 1000, 50);
const FReal BoxMargin = 10.0f;
const FVec3 SpherePos = FVec3(600, 540, 70);
const FVec3 ConvexPos = FVec3(0, 0, -25);
const FImplicitSphere3 Sphere(FVec3(0, 0, 0), SphereRadius);
const TImplicitObjectScaled<FImplicitConvex3> Convex = CreateScaledConvexBox(FVec3(1,1,1), BoxSize, BoxMargin);
const FRigidTransform3 SphereTransform = FRigidTransform3(SpherePos, FRotation3::FromIdentity());
const FRigidTransform3 ConvexTransform = FRigidTransform3(ConvexPos, FRotation3::FromIdentity());
FContactPoint ContactPoint = SphereConvexContactPoint(Sphere, SphereTransform, Convex, ConvexTransform);
EXPECT_TRUE(ContactPoint.IsSet());
if (ContactPoint.IsSet())
{
const FVec3 SphereOffset = FVec3(SpherePos.X - 0.5f * BoxSize.X, SpherePos.Y - 0.5f * BoxSize.Y, SpherePos.Z);
const FVec3 ExpectedNormal = SphereOffset.GetSafeNormal();
const FReal ExpectedDistance = SphereOffset.Size() - Sphere.GetRadiusf();
CheckContactPoint(ContactPoint, -SphereRadius * ExpectedNormal, 0.5f * FVec3(BoxSize.X, BoxSize.Y, BoxSize.Z), ExpectedNormal, 1, ExpectedNormal, ExpectedDistance);
}
}
// Sphere-Convex(with margin) deep contact
GTEST_TEST(DetectCollisionTests, TestSphereConvex_DeepContact)
{
const FReal SphereRadius = 50.0f;
const FVec3 BoxSize = FVec3(1000, 1000, 50);
const FReal BoxMargin = 10.0f;
const FVec3 SpherePos = FVec3(400, 20, 10);
const FVec3 ConvexPos = FVec3(0, 0, -25);
const FImplicitSphere3 Sphere(FVec3(0, 0, 0), SphereRadius);
const FImplicitConvex3 Convex = CreateConvexBox(BoxSize, BoxMargin);
const FRigidTransform3 SphereTransform = FRigidTransform3(SpherePos, FRotation3::FromIdentity());
const FRigidTransform3 ConvexTransform = FRigidTransform3(ConvexPos, FRotation3::FromIdentity());
FContactPoint ContactPoint = SphereConvexContactPoint(Sphere, SphereTransform, Convex, ConvexTransform);
EXPECT_TRUE(ContactPoint.IsSet());
if (ContactPoint.IsSet())
{
CheckContactPoint(ContactPoint, FVec3(0, 0, -SphereRadius), FVec3(SpherePos.X, SpherePos.Y, 0.5f * BoxSize.Z), FVec3(0, 0, 1), 1, FVec3(0, 0, 1), SpherePos.Z - SphereRadius);
}
}
// Sphere-Convex(with margin) and touching core shapes
GTEST_TEST(DetectCollisionTests, TestSphereConvex_Touching_Face)
{
const FReal SphereRadius = 50.0f;
const FVec3 BoxSize = FVec3(1000, 1000, 50);
const FReal BoxMargin = 10.0f;
const FVec3 SpherePos = FVec3(0, 0, -50);
const FVec3 ConvexPos = FVec3(0, 0, -25);
const FImplicitSphere3 Sphere(FVec3(0, 0, 0), SphereRadius);
const FImplicitConvex3 Convex = CreateConvexBox(BoxSize, BoxMargin);
const FRigidTransform3 SphereTransform = FRigidTransform3(SpherePos, FRotation3::FromIdentity());
const FRigidTransform3 ConvexTransform = FRigidTransform3(ConvexPos, FRotation3::FromIdentity());
FContactPoint ContactPoint = SphereConvexContactPoint(Sphere, SphereTransform, Convex, ConvexTransform);
EXPECT_TRUE(ContactPoint.IsSet());
if (ContactPoint.IsSet())
{
CheckContactPoint(ContactPoint, FVec3(0, 0, SphereRadius), FVec3(SpherePos.X, SpherePos.Y, -0.5f * BoxSize.Z), FVec3(0, 0, -1), 1, FVec3(0, 0, -1), SpherePos.Z + SphereRadius - BoxSize.Z);
}
}
// Sphere-Convex(with margin) and core shapes separated by a small epsilon (less than GJK epsilon)
GTEST_TEST(DetectCollisionTests, TestSphereConvex_LessEpsilon_Face)
{
const FReal SphereRadius = 50.0f;
const FVec3 BoxSize = FVec3(1000, 1000, 50);
const FReal BoxMargin = 10.0f;
const FVec3 SpherePos = FVec3(0, 0, -50.0005f); // Note: implicit knowledge of default epsilon of GJKDistance (1.e-3f)
const FVec3 ConvexPos = FVec3(0, 0, -25);
const FImplicitSphere3 Sphere(FVec3(0, 0, 0), SphereRadius);
const FImplicitConvex3 Convex = CreateConvexBox(BoxSize, BoxMargin);
const FRigidTransform3 SphereTransform = FRigidTransform3(SpherePos, FRotation3::FromIdentity());
const FRigidTransform3 ConvexTransform = FRigidTransform3(ConvexPos, FRotation3::FromIdentity());
FContactPoint ContactPoint = SphereConvexContactPoint(Sphere, SphereTransform, Convex, ConvexTransform);
EXPECT_TRUE(ContactPoint.IsSet());
if (ContactPoint.IsSet())
{
CheckContactPoint(ContactPoint, FVec3(0, 0, SphereRadius), FVec3(SpherePos.X, SpherePos.Y, -0.5f * BoxSize.Z), FVec3(0, 0, -1), 1, FVec3(0, 0, -1), SpherePos.Z + SphereRadius - BoxSize.Z);
}
}
// Sphere-Convex(with margin) and core shapes separated by a small epsilon (just greater than GJK epsilon)
GTEST_TEST(DetectCollisionTests, TestSphereConvex_GreaterEpsilon_Face)
{
const FReal SphereRadius = 50.0f;
const FVec3 BoxSize = FVec3(1000, 1000, 50);
const FReal BoxMargin = 10.0f;
const FVec3 SpherePos = FVec3(0, 0, -50.0015f); // Note: implicit knowledge of default epsilon of GJKDistance (1.e-3f)
const FVec3 ConvexPos = FVec3(0, 0, -25);
const FImplicitSphere3 Sphere(FVec3(0, 0, 0), SphereRadius);
const FImplicitConvex3 Convex = CreateConvexBox(BoxSize, BoxMargin);
const FRigidTransform3 SphereTransform = FRigidTransform3(SpherePos, FRotation3::FromIdentity());
const FRigidTransform3 ConvexTransform = FRigidTransform3(ConvexPos, FRotation3::FromIdentity());
FContactPoint ContactPoint = SphereConvexContactPoint(Sphere, SphereTransform, Convex, ConvexTransform);
EXPECT_TRUE(ContactPoint.IsSet());
if (ContactPoint.IsSet())
{
CheckContactPoint(ContactPoint, FVec3(0, 0, SphereRadius), FVec3(SpherePos.X, SpherePos.Y, -0.5f * BoxSize.Z), FVec3(0, 0, -1), 1, FVec3(0, 0, -1), -BoxSize.Z - (SpherePos.Z + SphereRadius));
}
}
//
//
// CAPSULE - CONVEX TESTS
//
//
// Capsule-Convex far separated when a face is the nearest feature
GTEST_TEST(DetectCollisionTests, TestCapsuleConvex_Separated_Parallel_Face)
{
const FReal CapsuleRadius = 50.0f;
const FReal CapsuleSegmentLength = 100.0f;
const FVec3 BoxSize = FVec3(1000, 1000, 50);
const FReal BoxMargin = 10.0f;
const FVec3 CapsulePos = FVec3(0, 0, 70);
const FVec3 ConvexPos = FVec3(0, 0, -25);
const FImplicitCapsule3 Capsule(FVec3(-0.5f * CapsuleSegmentLength, 0.0f, 0.0f), FVec3(0.5f * CapsuleSegmentLength, 0.0f, 0.0f), CapsuleRadius);
const TImplicitObjectScaled<FImplicitConvex3> Convex = CreateScaledConvexBox(FVec3(1, 1, 1), BoxSize, BoxMargin);
const FRigidTransform3 CapsuleTransform = FRigidTransform3(CapsulePos, FRotation3::FromIdentity());
const FRigidTransform3 ConvexTransform = FRigidTransform3(ConvexPos, FRotation3::FromIdentity());
FContactPoint ContactPoint = CapsuleConvexContactPoint(Capsule, CapsuleTransform, Convex, ConvexTransform);
EXPECT_TRUE(ContactPoint.IsSet());
if (ContactPoint.IsSet())
{
CheckContactPoint(ContactPoint, FVec3(0, 0, -CapsuleRadius), FVec3(0, 0, 0.5f * BoxSize.Z), FVec3(0, 0, 1), 1, FVec3(0, 0, 1), CapsulePos.Z - CapsuleRadius);
}
}
// Capsule-Convex far separated when the nearest features are an edge on the convex and the middle of the capsule segment (capsule is rotated)
GTEST_TEST(DetectCollisionTests, TestCapsuleConvex_Separated_EdgeEdge)
{
const FReal CapsuleRadius = 50.0f;
const FReal CapsuleSegmentLength = 100.0f;
const FVec3 BoxSize = FVec3(1000, 1000, 50);
const FReal BoxMargin = 10.0f;
const FVec3 CapsulePos = FVec3(600, 0, 100);
const FVec3 ConvexPos = FVec3(0, 0, -25);
const FImplicitCapsule3 Capsule(FVec3(-0.5f * CapsuleSegmentLength, 0.0f, 0.0f), FVec3(0.5f * CapsuleSegmentLength, 0.0f, 0.0f), CapsuleRadius);
const TImplicitObjectScaled<FImplicitConvex3> Convex = CreateScaledConvexBox(FVec3(1, 1, 1), BoxSize, BoxMargin);
const FRigidTransform3 CapsuleTransform = FRigidTransform3(CapsulePos, FRotation3::FromAxisAngle(FVec3(0,1,0), FMath::DegreesToRadians(45)));
const FRigidTransform3 ConvexTransform = FRigidTransform3(ConvexPos, FRotation3::FromIdentity());
FContactPoint ContactPoint = CapsuleConvexContactPoint(Capsule, CapsuleTransform, Convex, ConvexTransform);
EXPECT_TRUE(ContactPoint.IsSet());
if (ContactPoint.IsSet())
{
const FVec3 CapsuleOffset = CapsulePos - FVec3(0.5f * BoxSize.X, 0.0f, 0.0f);
const FVec3 ExpectedNormal = CapsuleOffset.GetSafeNormal();
const FReal ExpectedPhi = CapsuleOffset.Size() - CapsuleRadius;
CheckContactPoint(ContactPoint, FVec3(0, 0, -CapsuleRadius), FVec3(0.5f * BoxSize.X, 0, 0.5f * BoxSize.Z), ExpectedNormal, 1, ExpectedNormal, ExpectedPhi);
}
}
// Capsule-Convex deep contact when the nearest features are an edge on the convex and the middle of the capsule segment (capsule is rotated)
GTEST_TEST(DetectCollisionTests, TestCapsuleConvex_DeepContact_EdgeEdge)
{
const FReal CapsuleRadius = 50.0f;
const FReal CapsuleSegmentLength = 100.0f;
const FVec3 BoxSize = FVec3(1000, 1000, 50);
const FReal BoxMargin = 10.0f;
const FVec3 CapsulePos = FVec3(480, 0, -20);
const FVec3 ConvexPos = FVec3(0, 0, -25);
const FImplicitCapsule3 Capsule(FVec3(-0.5f * CapsuleSegmentLength, 0.0f, 0.0f), FVec3(0.5f * CapsuleSegmentLength, 0.0f, 0.0f), CapsuleRadius);
const TImplicitObjectScaled<FImplicitConvex3> Convex = CreateScaledConvexBox(FVec3(1, 1, 1), BoxSize, BoxMargin);
const FRigidTransform3 CapsuleTransform = FRigidTransform3(CapsulePos, FRotation3::FromAxisAngle(FVec3(0, 1, 0), FMath::DegreesToRadians(45)));
const FRigidTransform3 ConvexTransform = FRigidTransform3(ConvexPos, FRotation3::FromIdentity());
FContactPoint ContactPoint = CapsuleConvexContactPoint(Capsule, CapsuleTransform, Convex, ConvexTransform);
EXPECT_TRUE(ContactPoint.IsSet());
if (ContactPoint.IsSet())
{
const FVec3 CapsuleOffset = CapsulePos - FVec3(0.5f * BoxSize.X, 0.0f, 0.0f);
const FVec3 ExpectedNormal = -CapsuleOffset.GetSafeNormal();
const FReal ExpectedPhi = -(CapsuleOffset.Size() + CapsuleRadius);
CheckContactPoint(ContactPoint, FVec3(0, 0, -CapsuleRadius), FVec3(0.5f * BoxSize.X, 0, 0.5f * BoxSize.Z), ExpectedNormal, 1, ExpectedNormal, ExpectedPhi);
}
}
// Capsule-Convex OneShot Manifold when the convex lands on the end of the capsule and the convex face points exactly down the axis
// This exposed a bug in ConstructCapsuleConvexOneShotManifold where it would try to add a cylinder face contact resulting in a NaN
// from the call to GetUnsafeNormal.
GTEST_TEST(DetectCollisionTests, TestCapsuleConvex_Manifold_OpposingFaceNormalAxis)
{
const FRealSingle CapsuleRadius = 50.0f;
const FRealSingle CapsuleSegmentLength = 100.0f;
const FVec3f BoxSize = FVec3(100, 100, 100);
const FRealSingle BoxMargin = 0.0f;
const FRealSingle CullDistance = 10.0f;
const FImplicitCapsule3 Capsule(FVec3(0.0f, 0.0f, -0.5f * CapsuleSegmentLength), FVec3(0.0f, 0.0f, 0.5f * CapsuleSegmentLength), CapsuleRadius);
const FImplicitConvex3 Convex = CreateConvexBox(-0.5f * BoxSize, 0.5f * BoxSize, BoxMargin);
{
const FVec3f CapsulePos = FVec3(0, 0, -50);
const FVec3f ConvexPos = FVec3(0, 0, 100);
const FRigidTransform3 CapsuleTransform = FRigidTransform3(CapsulePos, FRotation3::FromIdentity());
const FRigidTransform3 ConvexTransform = FRigidTransform3(ConvexPos, FRotation3::FromIdentity());
FContactPointManifold ManifoldPoints;
Collisions::ConstructCapsuleConvexOneShotManifold(Capsule, CapsuleTransform, Convex, ConvexTransform, CullDistance, ManifoldPoints);
EXPECT_EQ(ManifoldPoints.Num(), 1);
if (ManifoldPoints.Num() == 1)
{
EXPECT_EQ(ManifoldPoints[0].ContactType, EContactPointType::VertexPlane);
EXPECT_NEAR(ManifoldPoints[0].ShapeContactNormal.Z, -1.0f, UE_KINDA_SMALL_NUMBER);
EXPECT_NEAR(ManifoldPoints[0].ShapeContactPoints[0].Z, 100.0f, UE_KINDA_SMALL_NUMBER);
EXPECT_NEAR(ManifoldPoints[0].ShapeContactPoints[1].Z, -50.0f, UE_KINDA_SMALL_NUMBER);
EXPECT_NEAR(ManifoldPoints[0].Phi, 0.0f, UE_KINDA_SMALL_NUMBER);
}
}
{
const FVec3f CapsulePos = FVec3(0, 0, 50);
const FVec3f ConvexPos = FVec3(0, 0, -100);
const FRigidTransform3 CapsuleTransform = FRigidTransform3(CapsulePos, FRotation3::FromIdentity());
const FRigidTransform3 ConvexTransform = FRigidTransform3(ConvexPos, FRotation3::FromIdentity());
FContactPointManifold ManifoldPoints;
Collisions::ConstructCapsuleConvexOneShotManifold(Capsule, CapsuleTransform, Convex, ConvexTransform, CullDistance, ManifoldPoints);
EXPECT_EQ(ManifoldPoints.Num(), 1);
if (ManifoldPoints.Num() == 1)
{
EXPECT_EQ(ManifoldPoints[0].ContactType, EContactPointType::VertexPlane);
EXPECT_NEAR(ManifoldPoints[0].ShapeContactNormal.Z, 1.0f, UE_KINDA_SMALL_NUMBER);
EXPECT_NEAR(ManifoldPoints[0].ShapeContactPoints[0].Z, -100.0f, UE_KINDA_SMALL_NUMBER);
EXPECT_NEAR(ManifoldPoints[0].ShapeContactPoints[1].Z, 50.0f, UE_KINDA_SMALL_NUMBER);
EXPECT_NEAR(ManifoldPoints[0].Phi, 0.0f, UE_KINDA_SMALL_NUMBER);
}
}
}
}