Files
UnrealEngine/Engine/Source/Programs/Enterprise/Datasmith/DatasmithARCHICADExporter/Private/Element2StaticMesh.cpp
2025-05-18 13:04:45 +08:00

580 lines
18 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Element2StaticMesh.h"
#include "Synchronizer.h"
#include "DatasmithHashTools.h"
#include "Utils/3DElement2String.h"
#include "ConvexPolygon.hpp"
#include "ModelElement.hpp"
#undef TicksPerSecond
#include "DatasmithSceneFactory.h"
#include "DatasmithMesh.h"
#include "DatasmithMeshExporter.h"
#include "DatasmithSceneExporter.h"
#include "Paths.h"
BEGIN_NAMESPACE_UE_AC
inline const Geometry::Point3D& Vertex2Point3D(const ModelerAPI::Vertex& inV)
{
return reinterpret_cast< const Geometry::Point3D& >(inV);
}
inline const Geometry::Vector3D& Vertor2Vector3D(const ModelerAPI::Vector& inV)
{
return reinterpret_cast< const Geometry::Vector3D& >(inV);
}
// Vertex value and used flag or mootools index
class FElement2StaticMesh::FVertex
{
public:
enum
{
kInvalidVertex = -1
};
// Constructor
FVertex()
: Used(false)
, Index(kInvalidVertex)
{
}
ModelerAPI::Vertex LocalVertex; // The vertex in local coordinates
ModelerAPI::Vertex WorldVertex; // The vertex in world coordinates
bool Used; // Used flag
int Index; // New index
};
// Constructor
FElement2StaticMesh::FElement2StaticMesh(const FSyncContext& InSyncContext)
: SyncContext(InSyncContext)
, bSomeHasTextures(false)
, BugsCount(0)
, GeometryShift(0, 0, 0)
{
}
FElement2StaticMesh::~FElement2StaticMesh() {}
// Compute name of mesh element
FString FElement2StaticMesh::ComputeMeshElementName(const TCHAR* InMeshFileHash)
{
FDatasmithHash HashName;
HashName.Update(InMeshFileHash);
for (VecMaterialSyncData::SizeType i = 0; i < GlobalMaterialsUsed.Num(); ++i)
{
HashName.Update(GlobalMaterialsUsed[i]->GetDatasmithId());
}
return LexToString(HashName.GetHashValue());
}
#if DUMP_GEOMETRY
// Return a mesh dump
utf8_string FElement2StaticMesh::MeshAsString(const FDatasmithMesh& Mesh)
{
utf8_string Dump(Utf8StringFormat("Mesh Name = %s\n", TCHAR_TO_UTF8(Mesh.GetName())));
int32 VerticesCount = Mesh.GetVerticesCount();
Dump += Utf8StringFormat("\tVertices Count = %d\n", VerticesCount);
for (int32 IdxVertice = 0; IdxVertice < VerticesCount; ++IdxVertice)
{
FVector3f Vertex = Mesh.GetVertex(IdxVertice);
Dump += Utf8StringFormat("\t\tVertice[%d] = {%f, %f, %f}\n", IdxVertice, Vertex.X, Vertex.Y, Vertex.Z);
}
int32 UVChannelCount = Mesh.GetUVChannelsCount();
Dump += Utf8StringFormat("\tUV Channels Count = %d\n", UVChannelCount);
for (int32 IdxChannel = 0; IdxChannel < UVChannelCount; ++IdxChannel)
{
int32 UVCount = Mesh.GetUVCount(IdxChannel);
Dump += Utf8StringFormat("\t\tChannels[%d] Count = %d\n", IdxChannel, UVCount);
for (int32 IdxUV = 0; IdxUV < UVCount; ++IdxUV)
{
FVector2D UV = Mesh.GetUV(IdxChannel, IdxUV);
Dump += Utf8StringFormat("\t\t\tChannels[%d][%d] UV = {%f, %f}\n", IdxChannel, IdxUV, UV.X, UV.Y);
}
}
int32 FacesCount = Mesh.GetFacesCount();
Dump += Utf8StringFormat("\tFaces Count = %d\n", FacesCount);
for (int32 IdxFace = 0; IdxFace < FacesCount; ++IdxFace)
{
int32 Vertex1;
int32 Vertex2;
int32 Vertex3;
int32 MaterialId;
Mesh.GetFace(IdxFace, Vertex1, Vertex2, Vertex3, MaterialId);
Dump += Utf8StringFormat("\t\tVertex[%d] = {%d, %d, %d} Mat = %d\n", IdxFace, Vertex1, Vertex2, Vertex3,
MaterialId);
FVector3f Point1 = Mesh.GetVertex(Vertex1);
FVector3f Point2 = Mesh.GetVertex(Vertex2);
FVector3f Point3 = Mesh.GetVertex(Vertex3);
Dump += Utf8StringFormat("\t\t\t\t{{%f, %f, %f}, {%f, %f, %f}, {%f, %f, %f}}\n", Point1.X, Point1.Y, Point1.Z,
Point2.X, Point2.Y, Point2.Z, Point3.X, Point3.Y, Point3.Z);
for (int32 IdxComponent = 0; IdxComponent < 3; IdxComponent++)
{
FVector3f Normal = Mesh.GetNormal(IdxFace * 3 + IdxComponent);
Dump += Utf8StringFormat("\t\t\tNormal[%d][%d] = {%f, %f, %f}\n", IdxFace, IdxComponent, Normal.X, Normal.Y,
Normal.Z);
}
for (int32 IdxChannel = 0; IdxChannel < UVChannelCount; ++IdxChannel)
{
Mesh.GetFaceUV(IdxFace, IdxChannel, Vertex1, Vertex2, Vertex3);
FVector2D UV1 = Mesh.GetUV(IdxChannel, Vertex1);
FVector2D UV2 = Mesh.GetUV(IdxChannel, Vertex2);
FVector2D UV3 = Mesh.GetUV(IdxChannel, Vertex3);
Dump += Utf8StringFormat("\t\t\tUV[%d][%d] = {%d, %d, %d} === {{%f, %f}, {%f, %f}, {%f, %f}}\n", IdxFace,
IdxChannel, Vertex1, Vertex2, Vertex3, UV1.X, UV1.Y, UV2.X, UV2.Y, UV3.X, UV3.Y);
}
}
return Dump;
}
// Dump a mesh
void FElement2StaticMesh::DumpMesh(const FDatasmithMesh& Mesh)
{
static bool DoDump = false;
if (DoDump)
{
UE_AC_TraceF("%s", MeshAsString(Mesh).c_str());
}
}
// Return a mesh element dump
utf8_string FElement2StaticMesh::MeshElementAsString(const IDatasmithMeshElement& Mesh)
{
utf8_string Dump;
Dump += Utf8StringFormat("Mesh \"%s\"\n", TCHAR_TO_UTF8(Mesh.GetName()));
Dump += Utf8StringFormat("\tLabel = \"%s\"\n", TCHAR_TO_UTF8(Mesh.GetLabel()));
Dump += Utf8StringFormat("\tFile = \"%s\"\n", TCHAR_TO_UTF8(Mesh.GetFile()));
const FVector3f Dim = Mesh.GetDimensions();
Dump += Utf8StringFormat("\tDimensions = {%f, %f, %f}\n", Dim.X, Dim.Y, Dim.Z);
Dump += Utf8StringFormat("\tArea = %f\n", Mesh.GetArea());
Dump +=
Utf8StringFormat("\tWidth = %f, Height = %f, Depth = %f\n", Mesh.GetWidth(), Mesh.GetHeight(), Mesh.GetDepth());
Dump += Utf8StringFormat("\tMaterial Slot Count = %d\n", Mesh.GetMaterialSlotCount());
return Dump;
}
// Dump a mesh element
void FElement2StaticMesh::DumpMeshElement(const IDatasmithMeshElement& Mesh)
{
static bool DoDump = false;
if (DoDump)
{
UE_AC_TraceF("%s", MeshElementAsString(Mesh).c_str());
}
}
#endif
// Create a triangle for polygon vertex ≈ new Triangle(first, previous, last)
void FElement2StaticMesh::AddVertex(GS::Int32 InBodyVertex, const Geometry::Vector3D& VertexNormal)
{
UE_AC_Assert(InBodyVertex > 0);
int ObjectVertex = InBodyVertex + StartVertex - 1;
// Get the vertex value
FVertex& vertex = Vertices[ObjectVertex];
if (!vertex.Used)
{
// Not already used, get value from body
CurrentBody.GetVertex(InBodyVertex, &vertex.LocalVertex, ModelerAPI::CoordinateSystem::ElemLocal);
vertex.LocalVertex.x = vertex.LocalVertex.x + GeometryShift.x;
vertex.LocalVertex.y = vertex.LocalVertex.y + GeometryShift.y;
vertex.LocalVertex.z = vertex.LocalVertex.z + GeometryShift.z;
vertex.LocalVertex.x *= SyncContext.ScaleLength;
vertex.LocalVertex.y *= SyncContext.ScaleLength;
vertex.LocalVertex.z *= SyncContext.ScaleLength;
CurrentBody.GetVertex(InBodyVertex, &vertex.WorldVertex, ModelerAPI::CoordinateSystem::World);
vertex.Used = true;
}
// Get vertex texture coordinate index
int ObjectUV = FTriangle::kInvalidIndex;
const FMaterialsDatabase::FMaterialSyncData& MatData = *GlobalMaterialsUsed[CurrentTriangle.LocalMatID];
bool bAlwaysSendUV = true;
if (MatData.bHasTexture || bAlwaysSendUV)
{
// Get texture coordinate
ModelerAPI::TextureCoordinate AcUV;
try
{
CurrentPolygon.GetTextureCoordinate(&vertex.WorldVertex, &AcUV);
}
catch (...)
{
UE_AC_STAT(++SyncContext.Stats.TotalBugsCount);
if (BugsCount++ == 0)
{
UE_AC_DebugF("FElement2StaticMesh::AddVertex - Exception in GetTextureCoordinate\n");
}
AcUV.u = 0;
AcUV.v = 0;
}
// Rotate texture and size the texture (ideally build a material that implement this functionality)
FTextureCoordinate UV;
UV.u = AcUV.u; //(MatData.CosAngle * AcUV.u - MatData.SinAngle * AcUV.v) * MatData.InvXSize;
UV.v = -AcUV.v; //(-MatData.SinAngle * AcUV.u - MatData.CosAngle * AcUV.v) * MatData.InvYSize;
#if PIVOT_0_5_0_5
UV.u += 0.5;
UV.v += 0.5;
#endif
// Convert Vertex coordinate to texture coordinate.
int* UVFound = UVs.Find(UV);
if (UVFound == nullptr)
{
ObjectUV = (int)UVs.Num();
UVs.Add(UV, ObjectUV);
bSomeHasTextures = true;
}
else
{
ObjectUV = *UVFound;
}
}
FVector3f CurrentNormal(float(VertexNormal.x), -float(VertexNormal.y), float(VertexNormal.z));
// Create triangles
if (VertexCount == 0)
{
// First polygon vertex is used for all triangles
CurrentTriangle.V0 = ObjectVertex;
CurrentTriangle.UV0 = ObjectUV;
CurrentTriangle.Normals[0] = CurrentNormal;
}
else
{
if (VertexCount != 1)
{
// Third and following vertex
CurrentTriangle.V2 = ObjectVertex;
CurrentTriangle.UV2 = ObjectUV;
CurrentTriangle.Normals[2] = CurrentNormal;
#if defined(DEBUG)
if (CurrentTriangle.V0 == CurrentTriangle.V1 || CurrentTriangle.V0 == CurrentTriangle.V2 ||
CurrentTriangle.V1 == CurrentTriangle.V2)
{
static bool bVertexConfusedSignaled = false;
if (!bVertexConfusedSignaled)
{
bVertexConfusedSignaled = true;
UE_AC_DebugF("FElement2StaticMesh::AddVertex - Triangle have 2 vertex confused\n");
}
}
if (CurrentTriangle.UV0 == CurrentTriangle.UV1 || CurrentTriangle.UV0 == CurrentTriangle.UV2 ||
CurrentTriangle.UV1 == CurrentTriangle.UV2)
{
static bool bUVConfusedSignaled = false;
if (!bUVConfusedSignaled)
{
bUVConfusedSignaled = true;
UE_AC_DebugF("FElement2StaticMesh::AddVertex - Triangle have 2 UV confused\n");
}
}
#endif
Triangles.Add(CurrentTriangle);
}
CurrentTriangle.V1 = ObjectVertex;
CurrentTriangle.UV1 = ObjectUV;
CurrentTriangle.Normals[1] = CurrentNormal;
}
VertexCount++;
}
// Set the material for the current polygon
void FElement2StaticMesh::InitPolygonMaterial()
{
CurrentPolygon.GetMaterialIndex(MaterialIndex);
GS::Int32 MaterialIdx = MaterialIndex.GetOriginalModelerIndex();
CurrentPolygon.GetPolygonTextureIndex(TextureIndex);
GS::Int32 TextureIdx = TextureIndex.GetOriginalModelerIndex();
const FMaterialsDatabase::FMaterialSyncData* CurrentMaterial = &SyncContext.GetMaterialsDatabase().GetMaterial(
SyncContext, MaterialIdx, TextureIdx, bIsSurfaceBody ? kDoubleSide : kSingleSide);
// Performance heuristic, we presume reuse of previous one
if (LocalMaterialIndex >= GlobalMaterialsUsed.Num() || GlobalMaterialsUsed[LocalMaterialIndex] != CurrentMaterial)
{
for (LocalMaterialIndex = GlobalMaterialsUsed.Num();;)
{
if (LocalMaterialIndex == 0)
{
// Not found, add it to the vector and quit the loop
LocalMaterialIndex = GlobalMaterialsUsed.Num();
GlobalMaterialsUsed.Add(CurrentMaterial);
break;
}
if (GlobalMaterialsUsed[--LocalMaterialIndex] == CurrentMaterial)
{
// found, quit the loop
break;
}
}
}
UE_AC_Assert(LocalMaterialIndex < GlobalMaterialsUsed.Num() ||
GlobalMaterialsUsed[LocalMaterialIndex] == CurrentMaterial);
CurrentTriangle.LocalMatID = (int)LocalMaterialIndex;
}
void FElement2StaticMesh::AddElementGeometry(const ModelerAPI::Element& InModelElement,
const Geometry::Vector3D& InGeometryShift)
{
#if 0
UE_AC_TraceF("Element\n%s\n", F3DElement2String::Element2String(InModelElement).c_str());
#endif
#if 0
static GS::Guid BreakGuid("0A0BFAC4-B753-1144-A733-1E73939B3ECB");
if (InModelElement.GetElemGuid() == BreakGuid)
{
UE_AC_DebugF("FElement2StaticMesh::AddElementGeometry - Break element found\n");
}
#endif
GeometryShift = InGeometryShift;
// Collect geometry from element's bodies
GS::Int32 NbBodies = InModelElement.GetTessellatedBodyCount();
UE_AC_STAT(SyncContext.Stats.BodiesStats.Inc(NbBodies));
for (GS::Int32 IndexBody = 1; IndexBody <= NbBodies; IndexBody++)
{
InModelElement.GetTessellatedBody(IndexBody, &CurrentBody);
bIsSurfaceBody = CurrentBody.IsSurfaceBody();
GS::Int32 NbVertices = CurrentBody.GetVertexCount();
StartVertex = (int)Vertices.Num();
Vertices.AddZeroed(NbVertices); // Set space for bodies vertex
// Collect triangles from body's polygon
GS::Int32 NbPolygons = CurrentBody.GetPolygonCount();
UE_AC_STAT(SyncContext.Stats.PolygonsStats.Inc(NbPolygons));
for (GS::Int32 IndexPolygon = 1; IndexPolygon <= NbPolygons; IndexPolygon++)
{
CurrentBody.GetPolygon(IndexPolygon, &CurrentPolygon);
if (!CurrentPolygon.IsInvisible())
{
// Cutting plan create invisible polygons contour where it cut. So, we must not export theses polygons
InitPolygonMaterial();
if (CurrentPolygon.IsComplex())
{
UE_AC_STAT(++SyncContext.Stats.PolygonsComplex);
// For a complex polygon we decompose it in convex polygons
GS::Int32 NbPolys = CurrentPolygon.GetConvexPolygonCount();
for (GS::Int32 i = 1; i <= NbPolys; i++)
{
// For all convex polygons
UE_AC_STAT(++SyncContext.Stats.PolygonsConvex);
ModelerAPI::ConvexPolygon ConvexPolygon;
CurrentPolygon.GetConvexPolygon(i, &ConvexPolygon);
GS::Int32 NbVerts = ConvexPolygon.GetVertexCount();
VertexCount = 0; // Start polygon triangulation
for (GS::Int32 j = 1; j <= NbVerts; j++)
{
AddVertex(ConvexPolygon.GetVertexIndex(j),
Vertor2Vector3D(ConvexPolygon.GetNormalVectorByVertex(
j, ModelerAPI::CoordinateSystem::ElemLocal)));
}
}
}
else
{
VertexCount = 0; // Start polygon triangulation
GS::Int32 NbEdges = CurrentPolygon.GetEdgeCount();
for (GS::Int32 IndexPedg = 1; IndexPedg <= NbEdges; IndexPedg++)
{
AddVertex(CurrentPolygon.GetVertexIndex(IndexPedg),
Vertor2Vector3D(CurrentPolygon.GetNormalVectorByVertex(
IndexPedg, ModelerAPI::CoordinateSystem::ElemLocal)));
}
}
}
}
}
if (!HasGeometry())
{
UE_AC_VerboseF("FElement2StaticMesh::AddElementGeometry - No material used for element %s\n",
InModelElement.GetElemGuid().ToUniString().ToUtf8());
}
}
// Validate Vertice
inline void ValidateVertice(int32 InIndex, const FElement2StaticMesh::VecVertices& InVertices, int32 InVertexUsedCount)
{
UE_AC_Assert(InIndex >= 0 && InIndex < InVertices.Num() && InVertices[InIndex].Index >= 0 &&
InVertices[InIndex].Index < InVertexUsedCount);
}
// Fill 3d maesh data from collected goemetry
void FElement2StaticMesh::FillMesh(FDatasmithMesh* OutMesh)
{
// Count used vertices and set new index value
int32 VertexUsedCount = 0;
VecVertices::SizeType NbVertices = Vertices.Num();
VecVertices::SizeType IndexVertex;
for (IndexVertex = 0; IndexVertex < NbVertices; IndexVertex++)
{
FVertex& vertex = Vertices[IndexVertex];
if (vertex.Used)
{
vertex.Index = VertexUsedCount++;
}
else
{
vertex.Index = FVertex::kInvalidVertex;
}
}
// Copy all used vertices
OutMesh->SetVerticesCount(VertexUsedCount);
UE_AC_STAT(SyncContext.Stats.TotalTrianglePts += VertexUsedCount);
for (IndexVertex = 0; IndexVertex < NbVertices; IndexVertex++)
{
FVertex& vertex = Vertices[IndexVertex];
if (vertex.Index != FVertex::kInvalidVertex)
{
OutMesh->SetVertex(vertex.Index, (float)vertex.LocalVertex.x, -(float)vertex.LocalVertex.y,
(float)vertex.LocalVertex.z);
}
}
// Create a UV channel and fill it
int32 UVChannel = OutMesh->GetUVChannelsCount();
UE_AC_Assert(UVChannel == 0); // Must be 0
OutMesh->AddUVChannel();
OutMesh->SetUVCount(UVChannel, int32(UVs.Num()));
UE_AC_STAT(SyncContext.Stats.TotalUVPts += int32(UVs.Num()));
for (const TPair< FTextureCoordinate, int >& ItUV : UVs)
{
OutMesh->SetUV(UVChannel, ItUV.Value, ItUV.Key.u, ItUV.Key.v);
}
// Count valid triangles
int32 TrianglesValidCount = 0;
VecTriangles::SizeType NbTriangles = Triangles.Num();
VecTriangles::SizeType IndexTriangle;
for (IndexTriangle = 0; IndexTriangle < NbTriangles; IndexTriangle++)
{
if (Triangles[IndexTriangle].IsValid())
{
TrianglesValidCount++;
}
}
// Copy triangles to face, normals and UV
UE_AC_STAT(SyncContext.Stats.TotalTriangles += TrianglesValidCount);
OutMesh->SetFacesCount(TrianglesValidCount);
int32 IndexFace = 0;
for (IndexTriangle = 0; IndexTriangle < NbTriangles; IndexTriangle++)
{
const FTriangle& triangle = Triangles[IndexTriangle];
if (triangle.IsValid())
{
#if 0
ValidateVertice(triangle.V0, Vertices, VertexUsedCount);
ValidateVertice(triangle.V1, Vertices, VertexUsedCount);
ValidateVertice(triangle.V2, Vertices, VertexUsedCount);
#endif
OutMesh->SetFace(IndexFace, Vertices[triangle.V0].Index, Vertices[triangle.V1].Index,
Vertices[triangle.V2].Index, triangle.LocalMatID);
for (int32 IndexComponent = 0; IndexComponent < 3; IndexComponent++)
{
const FVector3f& Normal = triangle.Normals[IndexComponent];
OutMesh->SetNormal(IndexFace * 3 + IndexComponent, Normal.X, Normal.Y, Normal.Z);
}
OutMesh->SetFaceUV(IndexFace, UVChannel, triangle.UV0, triangle.UV1, triangle.UV2);
IndexFace++;
}
}
}
// Create a datasmith mesh element
TSharedPtr< IDatasmithMeshElement > FElement2StaticMesh::CreateMesh()
{
TSharedPtr< IDatasmithMeshElement > MeshElement;
FDatasmithMesh Mesh;
FillMesh(&Mesh);
FDatasmithHash MeshHasher;
MeshHasher.ComputeDatasmithMeshHash(Mesh);
FMD5Hash MeshHash = MeshHasher.GetHashValue();
Mesh.SetName(*LexToString(MeshHash));
const FMeshDimensions* Dimensions = SyncContext.GetSyncDatabase().GetMeshIndexor().FindMesh(Mesh.GetName());
FString MeshElementName = ComputeMeshElementName(Mesh.GetName());
#if DUMP_GEOMETRY
DumpMesh(Mesh);
#endif
// Define output path
TCHAR SubDir1[2] = {Mesh.GetName()[0], 0};
TCHAR SubDir2[2] = {Mesh.GetName()[1], 0};
FString OutputPath(FPaths::Combine(SyncContext.GetSyncDatabase().GetAssetsFolderPath(), SubDir1, SubDir2));
// If mesh already exist ?
FString FullPath(FPaths::Combine(OutputPath, FPaths::SetExtension(Mesh.GetName(), TEXT("udsmesh"))));
if (Dimensions == nullptr || !FPaths::FileExists(FullPath))
{
// Create a new mesh file
UE_AC_STAT(SyncContext.Stats.TotalMeshesCreated++);
FDatasmithMeshExporter MeshExporter;
MeshElement =
MeshExporter.ExportToUObject(*OutputPath, Mesh.GetName(), Mesh, nullptr, EDSExportLightmapUV::Never);
if (MeshElement.IsValid())
{
MeshElement->SetName(*MeshElementName);
MeshElement->SetFileHash(MeshHash);
SyncContext.GetSyncDatabase().GetMeshIndexor().AddMesh(*MeshElement);
}
}
else
{
// Reuse the previous mesh file
UE_AC_STAT(SyncContext.Stats.TotalMeshesReused++);
MeshElement = FDatasmithSceneFactory::CreateMesh(*MeshElementName);
MeshElement->SetFile(*FullPath);
MeshElement->SetFileHash(MeshHash);
MeshElement->SetDimensions(Dimensions->Area, Dimensions->Width, Dimensions->Height, Dimensions->Depth);
}
if (MeshElement.IsValid())
{
for (VecMaterialSyncData::SizeType i = 0; i < GlobalMaterialsUsed.Num(); ++i)
{
MeshElement->SetMaterial(*GlobalMaterialsUsed[i]->GetDatasmithId(), (int32)i);
}
#if DUMP_GEOMETRY
DumpMeshElement(*MeshElement);
#endif
}
return MeshElement;
}
END_NAMESPACE_UE_AC