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

410 lines
20 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "GeometryCollection/GeometryCollectionTestMassProperties.h"
#include "Chaos/TriangleMesh.h"
#include "Chaos/MassProperties.h"
#include "Math/Box.h"
#include "GeometryCollection/GeometryCollection.h"
#include "GeometryCollection/GeometryCollectionAlgo.h"
#include "GeometryCollection/GeometryCollectionUtility.h"
#include "GeometryCollection/GeometryCollectionTestFramework.h"
#include "GeometryCollection/GeometryCollectionTestUtility.h"
#include "GeometryCollection/TransformCollection.h"
#include "HeadlessChaosTestUtility.h"
namespace GeometryCollectionTest
{
using namespace ChaosTest;
GTEST_TEST(AllTraits, GeometryCollection_MassProperties_Compute)
{
using namespace Chaos;
Chaos::FParticles Vertices;
Vertices.AddParticles(8);
Vertices.SetX(0, FVec3(-1, 1, -1));
Vertices.SetX(1, FVec3(1, 1, -1));
Vertices.SetX(2, FVec3(1, -1, -1));
Vertices.SetX(3, FVec3(-1, -1, -1));
Vertices.SetX(4, FVec3(-1, 1, 1));
Vertices.SetX(5, FVec3(1, 1, 1));
Vertices.SetX(6, FVec3(1, -1, 1));
Vertices.SetX(7, FVec3(-1, -1, 1));
// @todo(chaos): breaking : this trips an ensure in the test, why?
for (int i = 0; i < 8; i++) {
Vertices.SetX(i, Vertices.GetX(i) * FVector(1, 2, 3));
Vertices.SetX(i, Vertices.GetX(i) + FVector(1, 2, 3));
}
TArray<Chaos::TVec3<int32>> Faces;
Faces.SetNum(12);
Faces[0] = TVec3<int32>(0,1,2);
Faces[1] = TVec3<int32>(0,2,3);
Faces[2] = TVec3<int32>(2,1,6);
Faces[3] = TVec3<int32>(1,5,6);
Faces[4] = TVec3<int32>(2,6,7);
Faces[5] = TVec3<int32>(3,2,7);
Faces[6] = TVec3<int32>(4,7,3);
Faces[7] = TVec3<int32>(4,0,3);
Faces[8] = TVec3<int32>(4,1,0);
Faces[9] = TVec3<int32>(4,5,1);
Faces[10] = TVec3<int32>(5,4,7);
Faces[11] = TVec3<int32>(5,7,6);
Chaos::FTriangleMesh Surface(MoveTemp(Faces));
FMassProperties MassProperties;
MassProperties.Mass = 1.f;
//Chaos::FMassProperties MassProperties = Chaos::CalculateMassProperties(Vertices, Surface.GetElements(), 1.f);
{
const auto& SurfaceElements = Surface.GetElements();
CalculateVolumeAndCenterOfMass(Vertices, SurfaceElements, MassProperties.Volume, MassProperties.CenterOfMass);
for (int32 Idx = 0; Idx < 8; ++Idx)
{
Vertices.SetX(Idx, Vertices.GetX(Idx) - MassProperties.CenterOfMass);
}
check(MassProperties.Mass > 0);
check(MassProperties.Volume > SMALL_NUMBER);
// @todo(chaos) : breaking
CalculateInertiaAndRotationOfMass(Vertices, SurfaceElements, MassProperties.Mass / MassProperties.Volume, MassProperties.CenterOfMass, MassProperties.InertiaTensor, MassProperties.RotationOfMass);
}
EXPECT_EQ(MassProperties.Mass, 1.f);
EXPECT_TRUE(MassProperties.CenterOfMass.Equals(FVector(1, 2, 3)));
// This is just measured data to let us know when it changes. Ideally this would be derived.
FVector EulerAngle = MassProperties.RotationOfMass.Euler();
EXPECT_TRUE(MassProperties.RotationOfMass.Euler().Equals(FVector(115.8153, -12.4347, 1.9705)));
EXPECT_TRUE(FMath::IsNearlyEqual((FReal)MassProperties.InertiaTensor.M[0][0], static_cast<FReal>(14.9866095), (FReal)KINDA_SMALL_NUMBER));
EXPECT_TRUE(FMath::IsNearlyEqual((FReal)MassProperties.InertiaTensor.M[1][1], static_cast<FReal>(1.40656376), (FReal)KINDA_SMALL_NUMBER));
EXPECT_TRUE(FMath::IsNearlyEqual((FReal)MassProperties.InertiaTensor.M[2][2], static_cast<FReal>(13.7401619), (FReal)KINDA_SMALL_NUMBER));
}
GTEST_TEST(AllTraits, GeometryCollection_MassProperties_Cube)
{
using namespace Chaos;
FVector GlobalTranslation(0); FQuat GlobalRotation = FQuat::MakeFromEuler(FVector(0));
CreationParameters Params; Params.SimplicialType = ESimplicialType::Chaos_Simplicial_Box; Params.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_Box;
Params.GeomTransform = FTransform(GlobalRotation, GlobalTranslation); Params.NestedTransforms = { FTransform::Identity, FTransform::Identity, FTransform::Identity };
FGeometryCollectionWrapper* Collection = TNewSimulationObject<GeometryType::GeometryCollectionWithSingleRigid>::Init(Params)->template As<FGeometryCollectionWrapper>();
TArray<FTransform> Transform;
GeometryCollectionAlgo::GlobalMatrices(Collection->RestCollection->Transform, Collection->RestCollection->Parent, Transform);
// group ?
const TManagedArray<bool>& Visible = Collection->RestCollection->Visible;
// VerticesGroup
const TManagedArray<FVector3f>& Vertex = Collection->RestCollection->Vertex;
// GeometryGroup
const int32 NumGeometries = Collection->RestCollection->NumElements(FGeometryCollection::GeometryGroup);
const TManagedArray<int32>& VertexCount = Collection->RestCollection->VertexCount;
const TManagedArray<int32>& VertexStart = Collection->RestCollection->VertexStart;
const TManagedArray<int32>& FaceCount = Collection->RestCollection->FaceCount;
const TManagedArray<int32>& FaceStart = Collection->RestCollection->FaceStart;
const TManagedArray<int32>& TransformIndex = Collection->RestCollection->TransformIndex;
const TManagedArray<FIntVector>& Indices = Collection->RestCollection->Indices;
const TManagedArray<int32>& BoneMap = Collection->RestCollection->BoneMap;
int GeometryIndex = 0;
TUniquePtr<FTriangleMesh> TriMesh(
CreateTriangleMesh(
FaceStart[GeometryIndex],
FaceCount[GeometryIndex],
Visible,
Indices,
false));
//TArray<Chaos::TVec3<int32>> Faces;
//Faces.SetNum(Indices.Num());
//for (int i = 0; i < Indices.Num(); i++) { Faces[i] = TVec3<int32>(Indices[i][0], Indices[i][1], Indices[i][2]); }
//Chaos::FTriangleMesh TriMesh(MoveTemp(Faces));
TArray<FMassProperties> MassPropertiesArray;
MassPropertiesArray.AddUninitialized(NumGeometries);
FMassProperties& MassProperties = MassPropertiesArray[GeometryIndex];
MassProperties.CenterOfMass = FVector(0);
MassProperties.Mass = 1.0;
FParticles MassSpaceParticles;
MassSpaceParticles.AddParticles(Vertex.Num());
for (int32 Idx = 0; Idx < Vertex.Num(); ++Idx)
{
MassSpaceParticles.SetX(Idx, Transform[BoneMap[Idx]].TransformPosition(FVector(Vertex[Idx])));
}
CalculateVolumeAndCenterOfMass(MassSpaceParticles, TriMesh->GetElements(), MassProperties.Volume, MassProperties.CenterOfMass);
EXPECT_NEAR(MassProperties.Volume - 8.0, 0.0f, KINDA_SMALL_NUMBER);
EXPECT_NEAR(MassProperties.CenterOfMass.X - GlobalTranslation[0], 0.0f, KINDA_SMALL_NUMBER);
EXPECT_NEAR(MassProperties.CenterOfMass.Y - GlobalTranslation[1], 0.0f, KINDA_SMALL_NUMBER);
EXPECT_NEAR(MassProperties.CenterOfMass.Z - GlobalTranslation[2], 0.0f, KINDA_SMALL_NUMBER);
for (int32 Idx = 0; Idx < Vertex.Num(); ++Idx)
{
MassSpaceParticles.SetX(Idx, MassSpaceParticles.GetX(Idx) - MassProperties.CenterOfMass);
}
FReal Density = 1.0;
FVec3 ZeroVec(0);
CalculateInertiaAndRotationOfMass(MassSpaceParticles, TriMesh->GetSurfaceElements(), Density, ZeroVec, MassProperties.InertiaTensor, MassProperties.RotationOfMass);
EXPECT_EQ(MassProperties.Mass, 1.f);
EXPECT_TRUE((MassProperties.CenterOfMass - GlobalTranslation).Size() < SMALL_NUMBER);
// This is just measured data to let us know when it changes. Ideally this would be derived.
EXPECT_TRUE((MassProperties.RotationOfMass.Euler() - FVector(115.8153, -12.4347, 1.9705)).Size() > KINDA_SMALL_NUMBER);
EXPECT_TRUE(FMath::IsNearlyEqual((FReal)MassProperties.InertiaTensor.M[0][0], static_cast<FReal>(4.99521351), (FReal)KINDA_SMALL_NUMBER));
EXPECT_TRUE(FMath::IsNearlyEqual((FReal)MassProperties.InertiaTensor.M[1][1], static_cast<FReal>(4.07145357), (FReal)KINDA_SMALL_NUMBER));
EXPECT_TRUE(FMath::IsNearlyEqual((FReal)MassProperties.InertiaTensor.M[2][2], static_cast<FReal>(4.26666689), (FReal)KINDA_SMALL_NUMBER));
}
GTEST_TEST(AllTraits, GeometryCollection_MassProperties_Sphere)
{
using namespace Chaos;
FVector GlobalTranslation(10); FQuat GlobalRotation = FQuat::MakeFromEuler(FVector(0)); FVector Scale(1);
CreationParameters Params; Params.SimplicialType = ESimplicialType::Chaos_Simplicial_Sphere; Params.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_Sphere;
Params.GeomTransform = FTransform(GlobalRotation, GlobalTranslation, Scale); Params.NestedTransforms = { FTransform::Identity, FTransform::Identity, FTransform::Identity };
FGeometryCollectionWrapper* Collection = TNewSimulationObject<GeometryType::GeometryCollectionWithSingleRigid>::Init(Params)->template As<FGeometryCollectionWrapper>();
TArray<FTransform> Transform;
GeometryCollectionAlgo::GlobalMatrices(Collection->RestCollection->Transform, Collection->RestCollection->Parent, Transform);
// group ?
const TManagedArray<bool>& Visible = Collection->RestCollection->Visible;
// VerticesGroup
const TManagedArray<FVector3f>& Vertex = Collection->RestCollection->Vertex;
// GeometryGroup
const int32 NumGeometries = Collection->RestCollection->NumElements(FGeometryCollection::GeometryGroup);
const TManagedArray<int32>& VertexCount = Collection->RestCollection->VertexCount;
const TManagedArray<int32>& VertexStart = Collection->RestCollection->VertexStart;
const TManagedArray<int32>& FaceCount = Collection->RestCollection->FaceCount;
const TManagedArray<int32>& FaceStart = Collection->RestCollection->FaceStart;
const TManagedArray<int32>& TransformIndex = Collection->RestCollection->TransformIndex;
const TManagedArray<FIntVector>& Indices = Collection->RestCollection->Indices;
const TManagedArray<int32>& BoneMap = Collection->RestCollection->BoneMap;
int GeometryIndex = 0;
TUniquePtr<FTriangleMesh> TriMesh(
CreateTriangleMesh(
FaceStart[GeometryIndex],
FaceCount[GeometryIndex],
Visible,
Indices,
false));
TArray<FMassProperties> MassPropertiesArray;
MassPropertiesArray.AddUninitialized(NumGeometries);
FMassProperties& MassProperties = MassPropertiesArray[GeometryIndex];
FParticles MassSpaceParticles;
MassSpaceParticles.AddParticles(Vertex.Num());
for (int32 Idx = 0; Idx < Vertex.Num(); ++Idx)
{
MassSpaceParticles.SetX(Idx, Transform[BoneMap[Idx]].TransformPosition(FVector(Vertex[Idx])));
}
CalculateVolumeAndCenterOfMass(MassSpaceParticles, TriMesh->GetElements(), MassProperties.Volume, MassProperties.CenterOfMass);
// Since we're intersecting triangles with a sphere, where the vertices of the
// triangle vertices are on the sphere surface, we're missing some volume. Thus,
// we'd expect the volume of the triangulation to approach the analytic volume as
// the number of polygons goes to infinity (MakeSphereElement() currently does
// 16x16 divisions in U and V).
const FReal AnalyticVolume = (4.0/3) * (22.0/7) * Scale[0] * Scale[0] * Scale[0];
EXPECT_NEAR(MassProperties.Volume - AnalyticVolume, 0.0f, 0.2); // this should be 4.19047642
EXPECT_NEAR(MassProperties.CenterOfMass.X - GlobalTranslation[0], 0.0f, KINDA_SMALL_NUMBER);
EXPECT_NEAR(MassProperties.CenterOfMass.Y - GlobalTranslation[1], 0.0f, KINDA_SMALL_NUMBER);
EXPECT_NEAR(MassProperties.CenterOfMass.Z - GlobalTranslation[2], 0.0f, KINDA_SMALL_NUMBER);
for (int32 Idx = 0; Idx < Vertex.Num(); ++Idx)
{
MassSpaceParticles.SetX(Idx, MassSpaceParticles.GetX(Idx) - MassProperties.CenterOfMass);
}
FReal Density = 0.01;
FVec3 ZeroVec(0);
CalculateInertiaAndRotationOfMass(MassSpaceParticles, TriMesh->GetSurfaceElements(), Density, ZeroVec, MassProperties.InertiaTensor, MassProperties.RotationOfMass);
// todo(chaos) : Check this.
// This is just measured data to let us know when it changes. Ideally this would be derived.
FVector EulerAngle = MassProperties.RotationOfMass.Euler();
//EXPECT_TRUE((MassProperties.RotationOfMass.Euler() - FVector(115.8153, -12.4347, 1.9705)).Size() > SMALL_NUMBER);
//EXPECT_TRUE(MassProperties.InertiaTensor.M[0][0] - 4.99521351 < SMALL_NUMBER);
//EXPECT_TRUE(MassProperties.InertiaTensor.M[1][1] - 4.07145357 < SMALL_NUMBER);
//EXPECT_TRUE(MassProperties.InertiaTensor.M[2][2] - 4.26666689 < SMALL_NUMBER);
}
GTEST_TEST(AllTraits, GeometryCollection_MassProperties_Tetrahedron)
{
using namespace Chaos;
FVector GlobalTranslation(0); FQuat GlobalRotation = FQuat::MakeFromEuler(FVector(0));
CreationParameters Params; Params.SimplicialType = ESimplicialType::Chaos_Simplicial_Tetrahedron; Params.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_Sphere;
Params.GeomTransform = FTransform(GlobalRotation, GlobalTranslation); Params.NestedTransforms = { FTransform::Identity, FTransform::Identity, FTransform::Identity };
FGeometryCollectionWrapper* Collection = TNewSimulationObject<GeometryType::GeometryCollectionWithSingleRigid>::Init(Params)->template As<FGeometryCollectionWrapper>();
TArray<FTransform> Transform;
GeometryCollectionAlgo::GlobalMatrices(Collection->RestCollection->Transform, Collection->RestCollection->Parent, Transform);
// group ?
const TManagedArray<bool>& Visible = Collection->RestCollection->Visible;
// VerticesGroup
const TManagedArray<FVector3f>& Vertex = Collection->RestCollection->Vertex;
// GeometryGroup
const int32 NumGeometries = Collection->RestCollection->NumElements(FGeometryCollection::GeometryGroup);
const TManagedArray<int32>& VertexCount = Collection->RestCollection->VertexCount;
const TManagedArray<int32>& VertexStart = Collection->RestCollection->VertexStart;
const TManagedArray<int32>& FaceCount = Collection->RestCollection->FaceCount;
const TManagedArray<int32>& FaceStart = Collection->RestCollection->FaceStart;
const TManagedArray<int32>& TransformIndex = Collection->RestCollection->TransformIndex;
const TManagedArray<FIntVector>& Indices = Collection->RestCollection->Indices;
const TManagedArray<int32>& BoneMap = Collection->RestCollection->BoneMap;
int GeometryIndex = 0;
TUniquePtr<FTriangleMesh> TriMesh(
CreateTriangleMesh(
FaceStart[GeometryIndex],
FaceCount[GeometryIndex],
Visible,
Indices,
false));
TArray<FMassProperties> MassPropertiesArray;
MassPropertiesArray.AddUninitialized(NumGeometries);
FMassProperties& MassProperties = MassPropertiesArray[GeometryIndex];
MassProperties.Mass = 1.0;
MassProperties.CenterOfMass = FVector(0);
FParticles MassSpaceParticles;
MassSpaceParticles.AddParticles(Vertex.Num());
for (int32 Idx = 0; Idx < Vertex.Num(); ++Idx)
{
MassSpaceParticles.SetX(Idx, Transform[BoneMap[Idx]].TransformPosition(FVector(Vertex[Idx])));
}
CalculateVolumeAndCenterOfMass(MassSpaceParticles, TriMesh->GetElements(), MassProperties.Volume, MassProperties.CenterOfMass);
EXPECT_NEAR(MassProperties.Volume - 2.666666, 0.0f, KINDA_SMALL_NUMBER); // Tetrahedron with edge lengths 2.8284
EXPECT_NEAR(MassProperties.CenterOfMass.X - GlobalTranslation[0], 0.0f, KINDA_SMALL_NUMBER);
EXPECT_NEAR(MassProperties.CenterOfMass.Y - GlobalTranslation[1], 0.0f, KINDA_SMALL_NUMBER);
EXPECT_NEAR(MassProperties.CenterOfMass.Z - GlobalTranslation[2], 0.0f, KINDA_SMALL_NUMBER);
for (int32 Idx = 0; Idx < Vertex.Num(); ++Idx)
{
MassSpaceParticles.SetX(Idx, MassSpaceParticles.GetX(Idx) - MassProperties.CenterOfMass);
}
FReal Density = 0.01;
FVec3 ZeroVec(0);
CalculateInertiaAndRotationOfMass(MassSpaceParticles, TriMesh->GetSurfaceElements(), Density, ZeroVec, MassProperties.InertiaTensor, MassProperties.RotationOfMass);
// todo(chaos) : Check this.
// This is just measured data to let us know when it changes. Ideally this would be derived.
FVector EulerAngle = MassProperties.RotationOfMass.Euler();
//EXPECT_TRUE((MassProperties.RotationOfMass.Euler() - FVector(115.8153, -12.4347, 1.9705)).Size() > SMALL_NUMBER);
//EXPECT_TRUE(MassProperties.InertiaTensor.M[0][0] - 4.99521351 < SMALL_NUMBER);
//EXPECT_TRUE(MassProperties.InertiaTensor.M[1][1] - 4.07145357 < SMALL_NUMBER);
//EXPECT_TRUE(MassProperties.InertiaTensor.M[2][2] - 4.26666689 < SMALL_NUMBER);
}
GTEST_TEST(AllTraits, GeometryCollection_MassProperties_ScaledSphere)
{
// This test has points that are scaled, rotated and translated within mass space.
// So the resulting surface is not about the center of mass and needs to be
// moved for simulation.
using namespace Chaos;
FVector GlobalTranslation(10); FQuat GlobalRotation = FQuat::MakeFromEuler(FVector(45,0,0));
CreationParameters Params; Params.SimplicialType = ESimplicialType::Chaos_Simplicial_Sphere; Params.ImplicitType = EImplicitTypeEnum::Chaos_Implicit_Sphere;
Params.GeomTransform = FTransform(GlobalRotation,GlobalTranslation, FVector(1, 5, 11)); Params.NestedTransforms = { FTransform::Identity, FTransform::Identity, FTransform::Identity };
FGeometryCollectionWrapper* Collection = TNewSimulationObject<GeometryType::GeometryCollectionWithSingleRigid>::Init(Params)->template As<FGeometryCollectionWrapper>();
TArray<FTransform> Transform;
GeometryCollectionAlgo::GlobalMatrices(Collection->RestCollection->Transform, Collection->RestCollection->Parent, Transform);
// group ?
const TManagedArray<bool>& Visible = Collection->RestCollection->Visible;
// VerticesGroup
TManagedArray<FVector3f>& Vertex = Collection->RestCollection->Vertex;
// GeometryGroup
const int32 NumGeometries = Collection->RestCollection->NumElements(FGeometryCollection::GeometryGroup);
const TManagedArray<int32>& VertexCount = Collection->RestCollection->VertexCount;
const TManagedArray<int32>& VertexStart = Collection->RestCollection->VertexStart;
const TManagedArray<int32>& FaceCount = Collection->RestCollection->FaceCount;
const TManagedArray<int32>& FaceStart = Collection->RestCollection->FaceStart;
const TManagedArray<int32>& TransformIndex = Collection->RestCollection->TransformIndex;
const TManagedArray<FIntVector>& Indices = Collection->RestCollection->Indices;
const TManagedArray<int32>& BoneMap = Collection->RestCollection->BoneMap;
int GeometryIndex = 0;
TUniquePtr<FTriangleMesh> TriMesh(
CreateTriangleMesh(
FaceStart[GeometryIndex],
FaceCount[GeometryIndex],
Visible,
Indices,
false));
TArray<FMassProperties> MassPropertiesArray;
MassPropertiesArray.AddUninitialized(NumGeometries);
FMassProperties& MassProperties = MassPropertiesArray[GeometryIndex];
TArray<FVector> SomeVec;
FParticles MassSpaceParticles;
MassSpaceParticles.AddParticles(Vertex.Num());
for (int32 Idx = 0; Idx < Vertex.Num(); ++Idx)
{
FVector VertexPoint = FVector(Vertex[Idx]);
MassSpaceParticles.SetX(Idx, Transform[BoneMap[Idx]].TransformPosition(FVector(Vertex[Idx])));
FVector MassSpacePoint = FVector(MassSpaceParticles.GetX(Idx)[0], MassSpaceParticles.GetX(Idx)[1], MassSpaceParticles.GetX(Idx)[2]);
SomeVec.Add(MassSpacePoint);
}
FBox Bounds(SomeVec);
CalculateVolumeAndCenterOfMass(MassSpaceParticles, TriMesh->GetElements(), MassProperties.Volume, MassProperties.CenterOfMass);
EXPECT_NEAR(MassProperties.CenterOfMass.X - GlobalTranslation[0], 0.0f, KINDA_SMALL_NUMBER);
EXPECT_NEAR(MassProperties.CenterOfMass.Y - GlobalTranslation[1], 0.0f, KINDA_SMALL_NUMBER);
EXPECT_NEAR(MassProperties.CenterOfMass.Z - GlobalTranslation[2], 0.0f, KINDA_SMALL_NUMBER);
for (int32 Idx = 0; Idx < Vertex.Num(); ++Idx)
{
MassSpaceParticles.SetX(Idx, MassSpaceParticles.GetX(Idx) - MassProperties.CenterOfMass);
}
FReal Density = 0.01;
FVec3 ZeroVec(0);
CalculateInertiaAndRotationOfMass(MassSpaceParticles, TriMesh->GetSurfaceElements(), Density, ZeroVec, MassProperties.InertiaTensor, MassProperties.RotationOfMass);
FVector RotationEulerClamped = MassProperties.RotationOfMass.Euler();
if (RotationEulerClamped[0] < 0.)
{
RotationEulerClamped[0] += 180.f;
}
// rotational alignment.
EXPECT_NEAR(RotationEulerClamped[0], 135., KINDA_SMALL_NUMBER);
EXPECT_NEAR(RotationEulerClamped[1], 0., KINDA_SMALL_NUMBER);
EXPECT_NEAR(RotationEulerClamped[2], 0., KINDA_SMALL_NUMBER);
// X dominate inertia tensor
EXPECT_GT(MassProperties.InertiaTensor.M[0][0], MassProperties.InertiaTensor.M[2][2]);
EXPECT_GT(MassProperties.InertiaTensor.M[0][0], MassProperties.InertiaTensor.M[1][1]);
}
}