Files
jimmy andrews 7c757e5254 on volume->dynamic mesh conversion, when normals are requested and groups are not requested, still keep the groups until they can be used to compute normals and then discard them.
this fixes an ensure on trying to place a new modeling tools primitive mesh in a level when a volume is in the level and volume snapping is enabled

#rb david.hill

[CL 29615343 by jimmy andrews in ue5-main branch]
2023-11-09 18:43:27 -05:00

242 lines
7.2 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ConversionUtils/VolumeToDynamicMesh.h"
#include "CompGeom/PolygonTriangulation.h"
#include "ConstrainedDelaunay2.h"
#include "Polygon2.h"
#include "DynamicMesh/DynamicMesh3.h"
#include "DynamicMesh/MeshNormals.h"
#include "DynamicMesh/MeshTransforms.h"
#include "GameFramework/Volume.h"
#include "MeshBoundaryLoops.h"
#include "MeshQueries.h"
#include "MeshRegionBoundaryLoops.h"
#include "Model.h"
#include "DynamicMesh/Operations/MergeCoincidentMeshEdges.h"
#include "Operations/MinimalHoleFiller.h"
#include "Operations/PlanarFlipsOptimization.h"
#include "Components/BrushComponent.h"
#if WITH_EDITOR
#include "Engine/Polys.h"
#endif
using namespace UE::Geometry;
namespace VolumeToDynamicMeshLocals
{
/** Attempts to use delaunay triangulation to triangulate the polygon, and falls back to ear clipping if that fails. */
TArray<FIndex3i> TriangulatePolygon(const FPolygon2d& ToTriangulate)
{
// This first part is equivalent to calling ConstrainedDelaunayTriangulate<double>(ToTriangulate), except
// it allows us to properly check for failure.
TConstrainedDelaunay2<double> DelaunayTriangulation;
DelaunayTriangulation.FillRule = TConstrainedDelaunay2<double>::EFillRule::Positive;
DelaunayTriangulation.Add(ToTriangulate);
if (DelaunayTriangulation.Triangulate() && DelaunayTriangulation.Triangles.Num() > 0)
{
return DelaunayTriangulation.Triangles;
}
// If delaunay triangulation fails, use ear clipping.
TArray<FIndex3i> TrianglesOut;
PolygonTriangulation::TriangulateSimplePolygon(ToTriangulate.GetVertices(), TrianglesOut);
return TrianglesOut;
}
}
namespace UE {
namespace Conversion {
void VolumeToDynamicMesh(AVolume* Volume, FDynamicMesh3& Mesh,
const FVolumeToMeshOptions& Options)
{
UModel* Model = Volume->Brush;
if (Model == nullptr) // model is null in some cases, eg default physics volume
{
return;
}
BrushToDynamicMesh(*Model, Mesh, Options);
if (Options.bInWorldSpace)
{
FTransform3d XForm(Volume->GetTransform());
MeshTransforms::ApplyTransform(Mesh, XForm, true);
}
}
void BrushComponentToDynamicMesh(UBrushComponent* Component, UE::Geometry::FDynamicMesh3& Mesh, const FVolumeToMeshOptions& Options)
{
UModel* Model = Component->Brush;
if (Model == nullptr) // model is null in some cases, eg default physics volume
{
return;
}
BrushToDynamicMesh(*Model, Mesh, Options);
if (Options.bInWorldSpace)
{
FTransform3d XForm(Component->GetComponentTransform());
MeshTransforms::ApplyTransform(Mesh, XForm, true);
}
}
void BrushToDynamicMesh(UModel& BrushModel, UE::Geometry::FDynamicMesh3& Mesh, const FVolumeToMeshOptions& Options)
{
using namespace VolumeToDynamicMeshLocals;
Mesh.Clear();
// Note: Groups are also used to compute normals matching the input polygons
// If normals are requested but groups are not, groups will be discarded after normals are computed
if (Options.bSetGroups || Options.bGenerateNormals)
{
Mesh.EnableTriangleGroups();
}
#if WITH_EDITOR
// In the editor, the preferred source of geometry for a volume is the Polys array,
// which the bsp nodes are generated from, because the polys may be broken up into
// pieces unnecessarily as bsp nodes.
// Polys are planar polygons.
// We do not try to merge any vertices yet.
const TArray<FPoly>& Polygons = BrushModel.Polys->Element;
for (FPoly Poly : Polygons)
{
int32 NumVerts = Poly.Vertices.Num();
if (NumVerts < 3)
{
continue;
}
FVector3d Normal = (FVector3d)Poly.Normal;
FFrame3d Plane((FVector3d)Poly.Base, Normal);
TArray<int32> Vids;
FPolygon2d ToTriangulate;
Vids.SetNum(NumVerts);
for (int32 VertexIndex = 0; VertexIndex < NumVerts; ++VertexIndex)
{
FVector3d Point = (FVector3d)Poly.Vertices[VertexIndex];
Vids[VertexIndex] = Mesh.AppendVertex(Point);
ToTriangulate.AppendVertex(Plane.ToPlaneUV(Point, 2));
}
// Note that this call gives triangles with the reverse orientation compared to the
// polygon, but the polygons we get are oriented opposite of what we want (they
// are clockwise if the normal is towards us), so this ends up giving us the triangle
// orientation that we want.
TArray<FIndex3i> PolyTriangles = TriangulatePolygon(ToTriangulate);
int32 GroupID = FDynamicMesh3::InvalidID;
if (Options.bSetGroups)
{
GroupID = Mesh.AllocateTriangleGroup();
}
for (FIndex3i Tri : PolyTriangles)
{
Mesh.AppendTriangle(Vids[Tri.A], Vids[Tri.B], Vids[Tri.C], GroupID);
}
}
#else
// Each "BspNode" is a planar polygon, triangulate each polygon and accumulate in a mesh.
// Note that this does not make any attempt to weld vertices/edges
for (const FBspNode& Node : BrushModel.Nodes)
{
FVector3d Normal = (FVector3d)Node.Plane;
FFrame3d Plane(Node.Plane.W * Normal, Normal);
int32 NumVerts = (Node.NodeFlags & PF_TwoSided) ? Node.NumVertices / 2 : Node.NumVertices; // ??
if (NumVerts > 0)
{
TArray<int32> Vids;
FPolygon2d ToTriangulate;
Vids.SetNum(NumVerts);
for (int32 VertexIndex = 0; VertexIndex < NumVerts; ++VertexIndex)
{
const FVert& Vert = BrushModel.Verts[Node.iVertPool + VertexIndex];
FVector3d Point = (FVector3d)BrushModel.Points[Vert.pVertex];
Vids[VertexIndex] = Mesh.AppendVertex(Point);
ToTriangulate.AppendVertex(Plane.ToPlaneUV(Point, 2));
}
// Note that this call gives triangles with the reverse orientation compared to the
// polygon, but the polygons we get are oriented opposite of what we want (they
// are clockwise if the normal is towards us), so this ends up giving us the triangle
// orientation that we want.
TArray<FIndex3i> PolyTriangles = TriangulatePolygon(ToTriangulate);
int32 GroupID = FDynamicMesh3::InvalidID;
if (Options.bSetGroups)
{
GroupID = Mesh.AllocateTriangleGroup();
}
for (FIndex3i Tri : PolyTriangles)
{
Mesh.AppendTriangle(Vids[Tri.A], Vids[Tri.B], Vids[Tri.C], GroupID);
}
}
}
#endif
if (Options.bMergeVertices)
{
// Merge the mesh edges to create a closed solid
double MinLen, MaxLen, AvgLen;
TMeshQueries<FDynamicMesh3>::EdgeLengthStats(Mesh, MinLen, MaxLen, AvgLen);
FMergeCoincidentMeshEdges Merge(&Mesh);
Merge.MergeVertexTolerance = FMathd::Max(Merge.MergeVertexTolerance, MinLen * 0.1);
Merge.Apply();
// If the mesh is not closed, the merge failed or the volume had cracks/holes.
// Do trivial hole fills to ensure the output is solid (really want autorepair here)
if (Mesh.IsClosed() == false && Options.bAutoRepairMesh)
{
FMeshBoundaryLoops BoundaryLoops(&Mesh, true);
for (FEdgeLoop& Loop : BoundaryLoops.Loops)
{
FMinimalHoleFiller Filler(&Mesh, Loop);
Filler.Fill();
}
}
// try to flip towards better triangles in planar areas, should reduce/remove degenerate geo
if (Options.bOptimizeMesh)
{
FPlanarFlipsOptimization(&Mesh, 5).Apply(); // Do five passes
}
Mesh.CompactInPlace();
}
if (Options.bGenerateNormals)
{
Mesh.EnableAttributes();
FDynamicMeshNormalOverlay* Normals = Mesh.Attributes()->PrimaryNormals();
FMeshNormals::InitializeOverlayTopologyFromFaceGroups(&Mesh, Normals);
FMeshNormals::QuickRecomputeOverlayNormals(Mesh);
if (!Options.bSetGroups)
{
Mesh.DiscardTriangleGroups();
}
}
}
}}//end namespace UE::Conversion