// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= GeometryCollection->cpp: FGeometryCollection methods. =============================================================================*/ #include "GeometryCollection/GeometryCollectionUtility.h" #include "GeometryCollection/GeometryCollectionAlgo.h" #include "CoreMinimal.h" #include "Templates/SharedPointer.h" #include "Chaos/Real.h" DEFINE_LOG_CATEGORY_STATIC(FGeometryCollectionUtilityLogging, Log, All); namespace GeometryCollection { TSharedPtr MakeCubeElement(const FTransform& center, FVector Scale, int NumberOfMaterials) { FGeometryCollection* RestCollection = new FGeometryCollection(); int NumNewVertices = 8; // 8 vertices per cube int VerticesIndex = RestCollection->AddElements(NumNewVertices, FGeometryCollection::VerticesGroup); int NumNewIndices = 2 * 6; // two triangles per face int IndicesIndex = RestCollection->AddElements(NumNewIndices, FGeometryCollection::FacesGroup); int NumNewParticles = 1; // 1 particle for this geometry structure int ParticlesIndex = RestCollection->AddElements(NumNewParticles, FGeometryCollection::TransformGroup); TManagedArray& Vertices = RestCollection->Vertex; TManagedArray& Normals = RestCollection->Normal; TManagedArray& TangentU = RestCollection->TangentU; TManagedArray& TangentV = RestCollection->TangentV; TManagedArray& UV0 = *RestCollection->FindUVLayer(0); TManagedArray& Colors = RestCollection->Color; TManagedArray& Indices = RestCollection->Indices; TManagedArray& Visible = RestCollection->Visible; TManagedArray& MaterialIndex = RestCollection->MaterialIndex; TManagedArray& MaterialID = RestCollection->MaterialID; TManagedArray& Internal = RestCollection->Internal; TManagedArray& Transform = RestCollection->Transform; TManagedArray& SimType = RestCollection->SimulationType; TManagedArray& BoneMap = RestCollection->BoneMap; // set the particle information Transform[0] = FTransform3f(center); Transform[0].NormalizeRotation(); SimType[0] = FGeometryCollection::ESimulationTypes::FST_Rigid; // set the vertex information int32 Index = 0; FVector3f Scale3f = FVector3f(Scale); // LWC_TODO: Precision loss - Not significant as everything it applies to is already float. Vertices[0] = FVector3f(-Scale3f.X / 2.f, -Scale3f.Y / 2.f, -Scale3f.Z / 2.f); Vertices[1] = FVector3f(+Scale3f.X / 2.f, -Scale3f.Y / 2.f, -Scale3f.Z / 2.f); Vertices[2] = FVector3f(-Scale3f.X / 2.f, +Scale3f.Y / 2.f, -Scale3f.Z / 2.f); Vertices[3] = FVector3f(+Scale3f.X / 2.f, +Scale3f.Y / 2.f, -Scale3f.Z / 2.f); Vertices[4] = FVector3f(-Scale3f.X / 2.f, -Scale3f.Y / 2.f, +Scale3f.Z / 2.f); Vertices[5] = FVector3f(+Scale3f.X / 2.f, -Scale3f.Y / 2.f, +Scale3f.Z / 2.f); Vertices[6] = FVector3f(-Scale3f.X / 2.f, +Scale3f.Y / 2.f, +Scale3f.Z / 2.f); Vertices[7] = FVector3f(+Scale3f.X / 2.f, +Scale3f.Y / 2.f, +Scale3f.Z / 2.f); Normals[0] = FVector3f(-1.f, -1.f, -1.f).GetSafeNormal(); Normals[1] = FVector3f(1.f, -1.f, -1.f).GetSafeNormal(); Normals[2] = FVector3f(-1.f, 1.f, -1.f).GetSafeNormal(); Normals[3] = FVector3f(1.f, 1.f, -1.f).GetSafeNormal(); Normals[4] = FVector3f(-1.f, -1.f, 1.f).GetSafeNormal(); Normals[5] = FVector3f(1.f, -1.f, 1.f).GetSafeNormal(); Normals[6] = FVector3f(-1.f, 1.f, 1.f).GetSafeNormal(); Normals[7] = FVector3f(1.f, 1.f, 1.f).GetSafeNormal(); UV0[0] = FVector2f(0, 0); UV0[1] = FVector2f(1, 0); UV0[2] = FVector2f(0, 1); UV0[3] = FVector2f(1, 1); UV0[4] = FVector2f(0, 0); UV0[5] = FVector2f(1, 0); UV0[6] = FVector2f(0, 1); UV0[7] = FVector2f(1, 1); Colors[0] = FLinearColor::White; Colors[1] = FLinearColor::White; Colors[2] = FLinearColor::White; Colors[3] = FLinearColor::White; Colors[4] = FLinearColor::White; Colors[5] = FLinearColor::White; Colors[6] = FLinearColor::White; Colors[7] = FLinearColor::White; BoneMap[0] = 0; BoneMap[1] = 0; BoneMap[2] = 0; BoneMap[3] = 0; BoneMap[4] = 0; BoneMap[5] = 0; BoneMap[6] = 0; BoneMap[7] = 0; // set the index information // Bottom: Y = -1 Indices[0] = FIntVector(Index + 5,Index + 1,Index); Indices[1] = FIntVector(Index,Index + 4,Index + 5); // Top: Y = 1 Indices[2] = FIntVector(Index + 2,Index + 3,Index + 7); Indices[3] = FIntVector(Index + 7,Index + 6,Index + 2); // Back: Z = -1 Indices[4] = FIntVector(Index + 3,Index + 2,Index); Indices[5] = FIntVector(Index,Index + 1,Index + 3); // Front: Z = 1 Indices[6] = FIntVector(Index + 4,Index + 6,Index + 7); Indices[7] = FIntVector(Index + 7,Index + 5,Index + 4); // Left: X = -1 Indices[8] = FIntVector(Index, Index + 2,Index + 6); Indices[9] = FIntVector(Index + 6,Index + 4,Index); // Right: X = 1 Indices[10] = FIntVector(Index + 7,Index + 3,Index + 1); Indices[11] = FIntVector(Index + 1,Index + 5,Index + 7); // distribute the number of materials equally between the 12 faces check(NumberOfMaterials <= 12 && (12 % NumberOfMaterials)==0); // preferably divisible into 12 int NumberOfEachMaterial = 12 / NumberOfMaterials; for (int i = 0; i < 12; i++) { Visible[i] = true; Internal[i] = false; MaterialIndex[i] = i; MaterialID[i] = i / NumberOfEachMaterial; } for (int IndexIdx = 0; IndexIdx < 12; IndexIdx++) { FIntVector Tri = Indices[IndexIdx]; for (int idx = 0; idx < 3; idx++) { const FVector3f Normal = Normals[Tri[idx]]; const FVector3f Edge = (Vertices[Tri[(idx + 1) % 3]] - Vertices[Tri[idx]]); TangentU[Tri[idx]] = (Edge ^ Normal).GetSafeNormal(); TangentV[Tri[idx]] = (Normal ^ TangentU[Tri[idx]]).GetSafeNormal(); } } // GeometryGroup GeometryCollection::AddGeometryProperties(RestCollection); // add the material sections to simulate NumberOfMaterials on the object TManagedArray& Sections = RestCollection->Sections; // the first 6 indices are material 0 int FirstElement = RestCollection->AddElements(NumberOfMaterials, FGeometryCollection::MaterialGroup); for (int Element = 0; Element < NumberOfMaterials; Element++) { Sections[Element].MaterialID = Element; Sections[Element].FirstIndex = (Element * NumberOfEachMaterial) * 3; Sections[Element].NumTriangles = NumberOfEachMaterial; Sections[Element].MinVertexIndex = 0; Sections[Element].MaxVertexIndex = Vertices.Num() - 1; } return TSharedPtr(RestCollection); } void SetupCubeGridExample(TSharedPtr RestCollectionIn) { check(RestCollectionIn.IsValid()); float domain = 10.f; FVector Stack(domain); float numElements = powf(domain, 3); float Length = 50.f; float Seperation = .2f; float Expansion = 1.f + Seperation; FVector Stackf((float)Stack[0], (float)Stack[1], (float)Stack[2]); FVector MinCorner = -Length * Expansion / 2.f * Stackf; for (int32 i = 0; i < FMath::TruncToInt(Stack[0]); ++i) { for (int32 j = 0; j < FMath::TruncToInt(Stack[1]); ++j) { for (int32 k = 0; k < FMath::TruncToInt(Stack[2]); ++k) { FVector Delta(j % 2 == 1 ? Length / 2.f : 0.f, 0.f, j % 2 == 1 ? Length / 2.f : 0.f); FVector CenterOfMass = FVector(MinCorner[0] + Expansion * Length * static_cast(i) + Length * (Expansion / 2.f), MinCorner[0] + Expansion * Length * static_cast(j) + Length * (Expansion / 2.f), MinCorner[0] + Expansion * Length * static_cast(k) + Length * (Expansion / 2.f)) + Delta; TSharedPtr Element = MakeCubeElement(FTransform(CenterOfMass), FVector(Length) ); RestCollectionIn->AppendGeometry(*Element); } } } } void SetupTwoClusteredCubesCollection(FGeometryCollection * Collection) { int32 ParentIndex = Collection->AddElements(1, FGeometryCollection::TransformGroup); int32 TransformIndex0 = Collection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0, 0, 0.)), FVector(9, 0, 0)), FVector(1.0))); int32 TransformIndex1 = Collection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0, 0, 0.)), FVector(-9, 0, 0)), FVector(1.0))); TManagedArray& VertexCount = Collection->VertexCount; TManagedArray& VertexStart = Collection->VertexStart; TManagedArray & Vertex = Collection->Vertex; TArray ReverseMap; GeometryCollectionAlgo::BuildTransformGroupToGeometryGroupMap(*Collection, ReverseMap); for (int32 i = VertexStart[ReverseMap[TransformIndex0]]; i < VertexStart[ReverseMap[TransformIndex0]] + VertexCount[ReverseMap[TransformIndex0]]; i++) { Vertex[i] += FVector3f(1, 0, 0); } for (int32 i = VertexStart[ReverseMap[TransformIndex1]]; i < VertexStart[ReverseMap[TransformIndex1]] + VertexCount[ReverseMap[TransformIndex1]]; i++) { Vertex[i] -= FVector3f(1, 0, 0); } TManagedArray & Names = Collection->BoneName; Names[ParentIndex] = "Root"; Names[TransformIndex0] = "RGB1"; Names[TransformIndex1] = "RGB2"; TManagedArray & Parents = Collection->Parent; TManagedArray>& Children = Collection->Children; Parents[ParentIndex] = FGeometryCollection::Invalid; Children[ParentIndex].Add(TransformIndex0); Children[ParentIndex].Add(TransformIndex1); Parents[TransformIndex0] = ParentIndex; Parents[TransformIndex1] = ParentIndex; } void SetupNestedBoneCollection(FGeometryCollection* Collection) { int32 TransformIndex0 = Collection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0, 0, 90.)), FVector(0, 10, 0)), FVector(1.0))); int32 TransformIndex1 = Collection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0, 0, 45.)), FVector(0, 10, 0)), FVector(1.0))); int32 TransformIndex2 = Collection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(0, 0, 45.)), FVector(0, 10, 0)), FVector(1.0))); int32 TransformIndex3 = Collection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(90.,0., 0)), FVector(0, 10, 0)), FVector(1.0))); int32 TransformIndex4 = Collection->AppendGeometry(*GeometryCollection::MakeCubeElement(FTransform(FQuat::MakeFromEuler(FVector(45.,45., 45.)), FVector(0, 10, 0)), FVector(1.0))); // 0 // ...1 // ......2 // .........3 // ............4 (Collection->Parent)[TransformIndex0] = FGeometryCollection::Invalid; (Collection->Children)[TransformIndex0].Add(TransformIndex1); (Collection->Parent)[TransformIndex1] = TransformIndex0; (Collection->Children)[TransformIndex1].Add(TransformIndex2); (Collection->Parent)[TransformIndex2] = TransformIndex1; (Collection->Children)[TransformIndex2].Add(TransformIndex3); (Collection->Parent)[TransformIndex3] = TransformIndex2; (Collection->Children)[TransformIndex3].Add(TransformIndex4); (Collection->Parent)[TransformIndex4] = TransformIndex3; } void ComputeInnerAndOuterRadiiFromGeometryVertices(const TManagedArray& Vertices, const int32 VertexStart, const int32 VertexCount, float& OutInnerRadius, float& OutOuterRadius) { // first compute the geometry center FVector VertexSum{0.0 }; for (int32 VertexIndex = 0; VertexIndex < VertexCount; ++VertexIndex) { VertexSum += FVector(Vertices[VertexStart + VertexIndex]); } FVector3f Center{0.}; if (VertexCount > 0) { // controlled precision loss, we are now back into the range of vertices Center = FVector3f{VertexSum / static_cast(VertexCount)}; } OutInnerRadius = VertexCount ? TNumericLimits::Max() : 0.0f; OutOuterRadius = 0.0f; for (int32 VertexIndex = 0; VertexIndex < VertexCount; ++VertexIndex) { const FVector3f& Pt = Vertices[VertexStart + VertexIndex]; const float DistSq = FVector3f::DistSquared(Pt, Center); OutInnerRadius = FMath::Min(OutInnerRadius, DistSq); OutOuterRadius = FMath::Max(OutOuterRadius, DistSq); } OutInnerRadius = FMath::Sqrt(OutInnerRadius); OutOuterRadius = FMath::Sqrt(OutOuterRadius); } void AddGeometryProperties(FManagedArrayCollection* InCollection) { if (InCollection) { if (!InCollection->NumElements(FGeometryCollection::GeometryGroup)) { int32 NumVertices = InCollection->NumElements(FGeometryCollection::VerticesGroup); if (NumVertices) { // transforms group TManagedArray& Transform = InCollection->ModifyAttribute(FTransformCollection::TransformAttribute, FTransformCollection::TransformGroup); TManagedArray& TransformToGeometryIndex = InCollection->ModifyAttribute("TransformToGeometryIndex", FTransformCollection::TransformGroup); // vertices group TManagedArray& BoneMap = InCollection->ModifyAttribute("BoneMap", FGeometryCollection::VerticesGroup); TManagedArray& Vertex = InCollection->ModifyAttribute("Vertex", FGeometryCollection::VerticesGroup); // faces TManagedArray& FaceIndices = InCollection->ModifyAttribute("Indices", FGeometryCollection::FacesGroup); // geometry group TManagedArray& TransformIndex = InCollection->ModifyAttribute("TransformIndex", FGeometryCollection::GeometryGroup); TManagedArray& BoundingBox = InCollection->ModifyAttribute("BoundingBox", FGeometryCollection::GeometryGroup); TManagedArray& InnerRadius = InCollection->ModifyAttribute("InnerRadius", FGeometryCollection::GeometryGroup); TManagedArray& OuterRadius = InCollection->ModifyAttribute("OuterRadius", FGeometryCollection::GeometryGroup); TManagedArray& VertexCount = InCollection->ModifyAttribute("VertexCount", FGeometryCollection::GeometryGroup); TManagedArray& VertexStart = InCollection->ModifyAttribute("VertexStart", FGeometryCollection::GeometryGroup); TManagedArray& FaceCount = InCollection->ModifyAttribute("FaceCount", FGeometryCollection::GeometryGroup); TManagedArray& FaceStart = InCollection->ModifyAttribute("FaceStart", FGeometryCollection::GeometryGroup); // gather unique geometries TSet TransformIndexOfGeometry; for (int32 BoneIdx = 0; BoneIdx < BoneMap.Num(); BoneIdx++) { TransformIndexOfGeometry.Add(BoneMap[BoneIdx]); } // reverse map TArray ReverseMap; ReverseMap.Init(FGeometryCollection::Invalid, Transform.Num()); InCollection->AddElements(TransformIndexOfGeometry.Num(), FGeometryCollection::GeometryGroup); TArray GeometryIndices = TransformIndexOfGeometry.Array(); for (int32 Index = 0; Index < GeometryIndices.Num(); Index++) { ReverseMap[GeometryIndices[Index]] = Index; TransformToGeometryIndex[GeometryIndices[Index]] = Index; TransformIndex[Index] = GeometryIndices[Index]; BoundingBox[Index].Init(); InnerRadius[Index] = FLT_MAX; OuterRadius[Index] = -FLT_MAX; VertexStart[Index] = FGeometryCollection::Invalid; VertexCount[Index] = 0; FaceStart[Index] = FGeometryCollection::Invalid; FaceCount[Index] = 0; } // build vertex summary information TArray Center; Center.Init(FVector3f(0), GeometryIndices.Num()); int CurrentParticleIndex = FGeometryCollection::Invalid; for (int vdx = 0; vdx < Vertex.Num(); vdx++) { int32 ParticleIndex = BoneMap[vdx]; check(ReverseMap[ParticleIndex] != FGeometryCollection::Invalid); int32 GeometryIndex = ReverseMap[ParticleIndex]; if (VertexStart[GeometryIndex] == FGeometryCollection::Invalid) { // @todo(ContigiousVertices) : Files on disk are not contiguous, so until they are fixed just use the first set of vertices. //ensureMsgf(VertexCount[GeometryIndex] == 0, TEXT("Expected empty vertex count.")); VertexStart[GeometryIndex] = vdx; CurrentParticleIndex = ParticleIndex; } if (ParticleIndex == CurrentParticleIndex) { VertexCount[GeometryIndex]++; BoundingBox[GeometryIndex] += FVector(Vertex[vdx]); } // ensure contiguous particle indices // @todo(ContigiousVertices) : Files on disk are not contiguous, so until they are fixed just use the first set of vertices. // ensureMsgf(ParticleIndex == CurrentParticleIndex, TEXT("Expected contiguous particle indices in rigid body creation.")); Center[GeometryIndex] += Vertex[vdx]; } // build vertex centers for (int GeometryIndex = 0; GeometryIndex < GeometryIndices.Num(); GeometryIndex++) { if (VertexCount[GeometryIndex]) { Center[GeometryIndex] /= static_cast(VertexCount[GeometryIndex]); } } // build face summary information CurrentParticleIndex = FGeometryCollection::Invalid; for (int fdx = 0; fdx < FaceIndices.Num(); fdx++) { int32 vdx = FaceIndices[fdx][0]; int32 ParticleIndex = BoneMap[vdx]; check(ReverseMap[ParticleIndex] != FGeometryCollection::Invalid); int32 GeometryIndex = ReverseMap[ParticleIndex]; if (FaceStart[GeometryIndex] == FGeometryCollection::Invalid) { // @todo(ContigiousVertices) : Files on disk are not contiguous, so until they are fixed just use the first set of vertices. //ensureMsgf(FaceCount[GeometryIndex] == 0, TEXT("Expected empty face count.")); FaceStart[GeometryIndex] = fdx; CurrentParticleIndex = ParticleIndex; } if (ParticleIndex == CurrentParticleIndex) { FaceCount[GeometryIndex]++; } // ensure contiguous particle indices // @todo(ContigiousVertices) : Files on disk are not contiguous, so until they are fixed just use the first set of vertices. //ensureMsgf(ParticleIndex == CurrentParticleIndex, TEXT("Expected contiguous particle indices in rigid body creation.")); } // find the inner and outer radius for (int vdx = 0; vdx < Vertex.Num(); vdx++) { int32 GeometryIndex = ReverseMap[BoneMap[vdx]]; // double indexing safe due to check in previous loop. Chaos::FRealSingle Delta = (Center[GeometryIndex] - Vertex[vdx]).Size(); InnerRadius[GeometryIndex] = FMath::Min(InnerRadius[GeometryIndex], Delta); OuterRadius[GeometryIndex] = FMath::Max(OuterRadius[GeometryIndex], Delta); } // Inner/Outer centroid for (int fdx = 0; fdx NumElements(FGeometryCollection::FacesGroup) && (Collection->MaterialIndex)[0] == (Collection->MaterialIndex)[1] && (Collection->MaterialIndex)[0] == 0) { int NumVisited = 0; // fill in the material IDs TManagedArray& Section = Collection->Sections; TManagedArray& MaterialID = Collection->MaterialID; for (int i = 0; i < Section.Num(); i++) { int first = Section[i].FirstIndex / 3; int last = first + Section[i].NumTriangles; for (int FaceIdx = first; FaceIdx < last; FaceIdx++) { MaterialID[FaceIdx] = Section[i].MaterialID; NumVisited++; } } check(NumVisited == Collection->NumElements(FGeometryCollection::FacesGroup)); // Reindex will update everything else that is required Collection->ReindexMaterials(); } } void GenerateTemporaryGuids(FManagedArrayCollection* Collection, int32 StartIdx, bool bForceInit) { bool bNeedsInit = false; if (!Collection->HasAttribute("GUID", FTransformCollection::TransformGroup)) { FManagedArrayCollection::FConstructionParameters Params(FName(""), false); Collection->AddAttribute("GUID", FTransformCollection::TransformGroup, Params); bNeedsInit = true; } if (bNeedsInit || bForceInit) { TManagedArray& Guids = Collection->ModifyAttribute("GUID", FTransformCollection::TransformGroup); for (int32 Idx = StartIdx; Idx < Guids.Num(); ++Idx) { Guids[Idx] = FGuid::NewGuid(); } } } }