// Copyright Epic Games, Inc. All Rights Reserved. #include "CADKernelTools.h" #include "CADData.h" #include "CADOptions.h" #include "MeshDescription.h" #include "CADMeshDescriptionHelper.h" #include "StaticMeshAttributes.h" #include "StaticMeshOperations.h" #include "Core/CADEntity.h" #include "Core/MetadataDictionary.h" #include "Core/Session.h" #include "Core/Types.h" #include "Mesh/Criteria/Criterion.h" #include "Mesh/Meshers/Mesher.h" #include "Mesh/Structure/FaceMesh.h" #include "Mesh/Structure/ModelMesh.h" #include "Topo/Model.h" #include "Topo/Body.h" #include "Topo/Shell.h" #include "Topo/TopologicalEdge.h" #include "Topo/TopologicalShapeEntity.h" #include "Topo/TopologicalFace.h" #include "Topo/TopologicalVertex.h" typedef uint32 TriangleIndex[3]; namespace CADLibrary { static void FillVertexPosition(FMeshConversionContext& Context, UE::CADKernel::FModelMesh& ModelMesh, FMeshDescription& MeshDescription) { TArray VertexArray; ModelMesh.GetNodeCoordinates(VertexArray); for (FVector3f& Vertex : VertexArray) { Vertex *= 0.1f; // mm (CADKernel unit) to cm (UE unit) } int32 VertexCount = VertexArray.Num(); TVertexAttributesRef VertexPositions = MeshDescription.GetVertexPositions(); MeshDescription.ReserveNewVertices(Context.MeshParameters.bIsSymmetric ? VertexCount * 2 : VertexCount); Context.VertexIds.SetNumZeroed(VertexCount); // Make MeshDescription.VertexPositions and VertexID int32 VertexIndex = -1; for (const FVector3f& Vertex : VertexArray) { VertexIndex++; FVertexID VertexID = MeshDescription.CreateVertex(); VertexPositions[VertexID] = FDatasmithUtils::ConvertVector((FDatasmithUtils::EModelCoordSystem)Context.ImportParameters.GetModelCoordSys(), Vertex); Context.VertexIds[VertexIndex] = VertexID; } // if Symmetric mesh, the symmetric side of the mesh have to be generated if (Context.MeshParameters.bIsSymmetric) { FMatrix44f SymmetricMatrix = FDatasmithUtils::GetSymmetricMatrix(Context.MeshParameters.SymmetricOrigin, Context.MeshParameters.SymmetricNormal); Context.SymmetricVertexIds.SetNum(VertexArray.Num()); VertexIndex = 0; for (const FVector3f& Vertex : VertexArray) { FVertexID VertexID = MeshDescription.CreateVertex(); VertexPositions[VertexID] = FDatasmithUtils::ConvertVector((FDatasmithUtils::EModelCoordSystem)Context.ImportParameters.GetModelCoordSys(), Vertex); VertexPositions[VertexID] = SymmetricMatrix.TransformPosition(VertexPositions[VertexID]); Context.SymmetricVertexIds[VertexIndex++] = VertexID; } } } bool FillMesh(FMeshConversionContext& Context, UE::CADKernel::FModelMesh& ModelMesh, FMeshDescription& MeshDescription) { using namespace UE::CADKernel; const int32 UVChannel = 0; const int32 TriangleCount = 3; const TriangleIndex Clockwise = { 0, 1, 2 }; const TriangleIndex CounterClockwise = { 0, 2, 1 }; TArray TriangleVertexInstanceIDs; TriangleVertexInstanceIDs.SetNum(TriangleCount); TArray MeshVertexInstanceIDs; // Gather all array data FStaticMeshAttributes Attributes(MeshDescription); TVertexInstanceAttributesRef VertexInstanceNormals = Attributes.GetVertexInstanceNormals(); TVertexInstanceAttributesRef VertexInstanceTangents = Attributes.GetVertexInstanceTangents(); TVertexInstanceAttributesRef VertexInstanceBinormalSigns = Attributes.GetVertexInstanceBinormalSigns(); TVertexInstanceAttributesRef VertexInstanceColors = Attributes.GetVertexInstanceColors(); TVertexInstanceAttributesRef VertexInstanceUVs = Attributes.GetVertexInstanceUVs(); TPolygonGroupAttributesRef PolygonGroupImportedMaterialSlotNames = Attributes.GetPolygonGroupMaterialSlotNames(); if (!VertexInstanceNormals.IsValid() || !VertexInstanceTangents.IsValid() || !VertexInstanceBinormalSigns.IsValid() || !VertexInstanceColors.IsValid() || !VertexInstanceUVs.IsValid() || !PolygonGroupImportedMaterialSlotNames.IsValid()) { return false; } // Find all the materials used TMap MaterialToPolygonGroupMapping; for (const FFaceMesh* FaceMesh : ModelMesh.GetFaceMeshes()) { const FTopologicalFace& Face = (const FTopologicalFace&) FaceMesh->GetGeometricEntity(); uint32 MaterialId = Face.GetMaterialId() ? Face.GetMaterialId() : Face.GetColorId(); MaterialToPolygonGroupMapping.Add(MaterialId, INDEX_NONE); } // Add to the mesh, a polygon groups per material for (auto& Material : MaterialToPolygonGroupMapping) { uint32 MaterialHash = Material.Key; FName ImportedSlotName = *LexToString(MaterialHash); FPolygonGroupID PolyGroupID = MeshDescription.CreatePolygonGroup(); PolygonGroupImportedMaterialSlotNames[PolyGroupID] = ImportedSlotName; Material.Value = PolyGroupID; } VertexInstanceUVs.SetNumChannels(1); int32 NbStep = 1; if (Context.MeshParameters.bIsSymmetric) { NbStep = 2; } int32 FaceIndex = 0; const TSet& PatchIdSet = Context.PatchesToMesh; bool bImportOnlyAlreadyPresent = (bool)PatchIdSet.Num(); TPolygonAttributesRef PatchGroups = EnableCADPatchGroups(MeshDescription); int32 PatchIndex = 0; for (int32 Step = 0; Step < NbStep; ++Step) { // Swap mesh if needed const TriangleIndex& Orientation = (!Context.MeshParameters.bNeedSwapOrientation == (bool)Step) ? CounterClockwise : Clockwise; TArray& VertexIdSet = (Step == 0) ? Context.VertexIds : Context.SymmetricVertexIds; // Loop through the FaceMeshes and collect all tessellation data for (FFaceMesh* FaceMesh : ModelMesh.GetFaceMeshes()) { const FTopologicalFace& Face = (const FTopologicalFace&)FaceMesh->GetGeometricEntity(); if (bImportOnlyAlreadyPresent && !PatchIdSet.Contains(Face.GetPatchId())) { continue; } // Get the polygonGroup const FPolygonGroupID* PolygonGroupID = MaterialToPolygonGroupMapping.Find(Face.GetColorId()); if (PolygonGroupID == nullptr) { continue; } int32 VertexIDs[3]; //FVector Temp3D = { 0, 0, 0 }; //FVector2D TexCoord2D = { 0, 0 }; const TArray& TriangleVertexIndices = FaceMesh->TrianglesVerticesIndex; const TArray& VerticesGlobalIndex = FaceMesh->VerticesGlobalIndex; MeshVertexInstanceIDs.Empty(TriangleVertexIndices.Num()); PatchIndex++; // build each valid face i.e. 3 different indexes for (int32 Index = 0; Index < TriangleVertexIndices.Num(); Index += 3) { VertexIDs[0] = VertexIdSet[VerticesGlobalIndex[TriangleVertexIndices[Index + Orientation[0]]]]; VertexIDs[1] = VertexIdSet[VerticesGlobalIndex[TriangleVertexIndices[Index + Orientation[1]]]]; VertexIDs[2] = VertexIdSet[VerticesGlobalIndex[TriangleVertexIndices[Index + Orientation[2]]]]; MeshVertexInstanceIDs.Add(TriangleVertexInstanceIDs[0] = MeshDescription.CreateVertexInstance((FVertexID)VertexIDs[0])); MeshVertexInstanceIDs.Add(TriangleVertexInstanceIDs[1] = MeshDescription.CreateVertexInstance((FVertexID)VertexIDs[1])); MeshVertexInstanceIDs.Add(TriangleVertexInstanceIDs[2] = MeshDescription.CreateVertexInstance((FVertexID)VertexIDs[2])); // Add the triangle as a polygon to the mesh description const FPolygonID PolygonID = MeshDescription.CreatePolygon(*PolygonGroupID, TriangleVertexInstanceIDs); // Set patch id attribute PatchGroups[PolygonID] = Face.GetPatchId(); } for (int32 IndexFace = 0; IndexFace < MeshVertexInstanceIDs.Num(); IndexFace += 3) { for (int32 Index = 0; Index < TriangleCount; Index++) { const FVertexInstanceID VertexInstanceID = MeshVertexInstanceIDs[IndexFace + Index]; // TODO // The UV scaling should be done in CADKernel const double ScaleUV = 0.001; // mm to m VertexInstanceUVs.Set(VertexInstanceID, UVChannel, FVector2f(FaceMesh->UVMap[TriangleVertexIndices[IndexFace + Orientation[Index]]] * ScaleUV)); VertexInstanceColors[VertexInstanceID] = FLinearColor::White; VertexInstanceTangents[VertexInstanceID] = FVector3f(ForceInitToZero); VertexInstanceBinormalSigns[VertexInstanceID] = 0.0f; } } if (!Step) { FDatasmithUtils::ConvertVectorArray(Context.ImportParameters.GetModelCoordSys(), FaceMesh->Normals); for (FVector3f& Normal : FaceMesh->Normals) { Normal = Normal.GetSafeNormal(); } } for (int32 IndexFace = 0; IndexFace < MeshVertexInstanceIDs.Num(); IndexFace += 3) { for (int32 Index = 0; Index < 3; Index++) { const FVertexInstanceID VertexInstanceID = MeshVertexInstanceIDs[IndexFace + Index]; VertexInstanceNormals[VertexInstanceID] = (FVector3f)FaceMesh->Normals[TriangleVertexIndices[IndexFace + Orientation[Index]]]; } } // compute normals if (Step) { // compute normals of Symmetric vertex FMatrix44f SymmetricMatrix = FDatasmithUtils::GetSymmetricMatrix(Context.MeshParameters.SymmetricOrigin, Context.MeshParameters.SymmetricNormal); for (const FVertexInstanceID& VertexInstanceID : MeshVertexInstanceIDs) { VertexInstanceNormals[VertexInstanceID] = SymmetricMatrix.TransformVector(VertexInstanceNormals[VertexInstanceID]); } } } } return true; } static bool ConvertModelMeshToMeshDescription(FMeshConversionContext& Context, UE::CADKernel::FModelMesh& InModelMesh, FMeshDescription& MeshDescription) { int32 VertexCount = InModelMesh.GetVertexCount(); int32 TriangleCount = InModelMesh.GetTriangleCount(); MeshDescription.ReserveNewVertexInstances(VertexCount); MeshDescription.ReserveNewPolygons(TriangleCount); MeshDescription.ReserveNewEdges(TriangleCount); FillVertexPosition(Context, InModelMesh, MeshDescription); if (!FillMesh(Context, InModelMesh, MeshDescription)) { return false; } // Build edge meta data FStaticMeshOperations::DetermineEdgeHardnessesFromVertexInstanceNormals(MeshDescription); return MeshDescription.Polygons().Num() > 0; } bool FCADKernelTools::Tessellate(UE::CADKernel::FTopologicalShapeEntity& CADTopologicalEntity, FMeshConversionContext& TessellationContext, FMeshDescription& OutMeshDescription) { using namespace UE::CADKernel; // Tessellate the model TSharedRef CADKernelModelMesh = FEntity::MakeShared(); const bool bActivateThinZoneMeshing = FImportParameters::bGActivateThinZoneMeshing; FMesher Mesher(*CADKernelModelMesh, TessellationContext.GeometricTolerance, bActivateThinZoneMeshing); DefineMeshCriteria(*CADKernelModelMesh, TessellationContext.ImportParameters, TessellationContext.GeometricTolerance); Mesher.MeshEntity(CADTopologicalEntity); return ConvertModelMeshToMeshDescription(TessellationContext, *CADKernelModelMesh, OutMeshDescription); } uint32 FCADKernelTools::GetFaceTessellation(UE::CADKernel::FFaceMesh& FaceMesh, FBodyMesh& OutBodyMesh, FObjectDisplayDataId FaceMaterial) { // Something wrong happened, either an error or no data to collect if (FaceMesh.TrianglesVerticesIndex.Num() == 0) { return 0; } FTessellationData& Tessellation = OutBodyMesh.Faces.Emplace_GetRef(); const UE::CADKernel::FTopologicalFace& TopologicalFace = (const UE::CADKernel::FTopologicalFace&)FaceMesh.GetGeometricEntity(); Tessellation.PatchId = TopologicalFace.GetPatchId(); Tessellation.PositionIndices = MoveTemp(FaceMesh.VerticesGlobalIndex); Tessellation.VertexIndices = MoveTemp(FaceMesh.TrianglesVerticesIndex); Tessellation.NormalArray = MoveTemp(FaceMesh.Normals); Tessellation.TexCoordArray = MoveTemp(FaceMesh.UVMap); if (FaceMaterial.ColorUId) { Tessellation.ColorUId = FaceMaterial.ColorUId; OutBodyMesh.ColorSet.Add(FaceMaterial.ColorUId); } if (FaceMaterial.MaterialUId) { Tessellation.MaterialUId = FaceMaterial.MaterialUId; OutBodyMesh.MaterialSet.Add(FaceMaterial.MaterialUId); } return Tessellation.VertexIndices.Num() / 3; } void GetDisplayDataIds(const UE::CADKernel::FTopologicalShapeEntity& ShapeEntity, FObjectDisplayDataId& DisplayDataId) { if (ShapeEntity.GetColorId() != 0) { DisplayDataId.ColorUId = ShapeEntity.GetColorId(); } if (ShapeEntity.GetMaterialId() != 0) { DisplayDataId.MaterialUId = ShapeEntity.GetMaterialId(); } } void FCADKernelTools::GetBodyTessellation(const UE::CADKernel::FModelMesh& ModelMesh, const UE::CADKernel::FBody& Body, FBodyMesh& OutBodyMesh) { ModelMesh.GetNodeCoordinates(OutBodyMesh.VertexArray); for (FVector3f& Vertex : OutBodyMesh.VertexArray) { Vertex *= 0.1f; } uint32 FaceSize = Body.FaceCount(); // Allocate memory space for tessellation data OutBodyMesh.Faces.Reserve(FaceSize); OutBodyMesh.ColorSet.Reserve(FaceSize); OutBodyMesh.MaterialSet.Reserve(FaceSize); FObjectDisplayDataId BodyMaterial; GetDisplayDataIds(Body, BodyMaterial); BodyMaterial.DefaultMaterialUId = BodyMaterial.MaterialUId != 0 ? BodyMaterial.MaterialUId : BodyMaterial.ColorUId; // Loop through the face of bodies and collect all tessellation data for (const TSharedPtr& Shell : Body.GetShells()) { if (!Shell.IsValid()) { continue; } FObjectDisplayDataId ShellMaterial = BodyMaterial; GetDisplayDataIds(*Shell, ShellMaterial); for (const UE::CADKernel::FOrientedFace& Face : Shell->GetFaces()) { if (!Face.Entity.IsValid()) { continue; } if (!Face.Entity->HasTesselation()) { continue; } FObjectDisplayDataId FaceMaterial = ShellMaterial; GetDisplayDataIds(*Face.Entity, FaceMaterial); UE::CADKernel::FFaceMesh* FaceMesh = Face.Entity->GetMesh(); uint32 TriangleNum = GetFaceTessellation(*FaceMesh, OutBodyMesh, FaceMaterial); OutBodyMesh.TriangleCount += TriangleNum; } } } void FCADKernelTools::DefineMeshCriteria(UE::CADKernel::FModelMesh& MeshModel, const FImportParameters& ImportParameters, double GeometricTolerance) { { TSharedPtr CurvatureCriterion = UE::CADKernel::FCriterion::CreateCriterion(UE::CADKernel::ECriterion::CADCurvature); MeshModel.AddCriterion(CurvatureCriterion); TSharedPtr MinSizeCriterion = UE::CADKernel::FCriterion::CreateCriterion(UE::CADKernel::ECriterion::MinSize, 2. * GeometricTolerance); MeshModel.AddCriterion(MinSizeCriterion); } if (ImportParameters.GetMaxEdgeLength() > SMALL_NUMBER) { const double MaxSize = ImportParameters.GetMaxEdgeLength() * 10.; // cm to mm TSharedPtr MaxSizeCriterion = UE::CADKernel::FCriterion::CreateCriterion(UE::CADKernel::ECriterion::MaxSize, MaxSize); MeshModel.AddCriterion(MaxSizeCriterion); } if (ImportParameters.GetChordTolerance() > SMALL_NUMBER) { double MaxChord = ImportParameters.GetChordTolerance() * 10.; // cm to mm TSharedPtr ChordCriterion = UE::CADKernel::FCriterion::CreateCriterion(UE::CADKernel::ECriterion::Sag, MaxChord); MeshModel.AddCriterion(ChordCriterion); } if (ImportParameters.GetMaxNormalAngle() > SMALL_NUMBER) { TSharedPtr MaxNormalAngleCriterion = UE::CADKernel::FCriterion::CreateCriterion(UE::CADKernel::ECriterion::Angle, ImportParameters.GetMaxNormalAngle()); MeshModel.AddCriterion(MaxNormalAngleCriterion); } } }