Files
UnrealEngine/Engine/Plugins/Runtime/GeometryProcessing/Source/DynamicMesh/Private/Operations/IntrinsicTriangulationMesh.cpp
2025-05-18 13:04:45 +08:00

4656 lines
168 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Operations/IntrinsicTriangulationMesh.h"
#include "Operations/MeshGeodesicSurfaceTracer.h"
#include "DynamicMesh/DynamicMeshAttributeSet.h"
#include "Util/IndexUtil.h"
#include "Algo/Reverse.h"
#include "Containers/BitArray.h"
#include "Containers/Queue.h"
#include "MathUtil.h"
#include "Async/ParallelFor.h"
#include "VectorTypes.h"
#include "MatrixTypes.h"
using namespace UE::Geometry;
/**
* Helper functions that use triangle edge lengths instead of vertex positions to do some euclidean triangle-based calculations.
* Many of these are based on the "law of cosines"
*/
namespace
{
/**
* Given a flat triangle with edges L0, L1, L2 in CCW order, compute the angle between
* the L0 and L2 edge in radians [0, Pi] range.
*
* used by Sharp, Soliman and Crane [2019, ACM Transactions on Graphics] section 3.2
*
* but can be understood as trivial application of "the law of cosines"
*/
double InteriorAngle(const double L0, const double L1, const double L2)
{
// V1 = V2 - V0. L1^2 = ||V1||^2 = ||V2||^2 + ||V0||^2 - 2 Dot(V2, V0) = L2^2 + L0^2 - 2 L2 L0 CosTheta
// CosTheta = (L0^2 + L1^2 - L2^2) / (2 L0 L1)
double CosTheta = (L0 * L0 + L2 * L2 - L1 * L1) / (2. * L0 * L2);
// account for roundoff, theoretically this would already be in the [-1,1] range
CosTheta = FMath::Clamp(CosTheta, -1., 1.);
return FMath::Acos(CosTheta);
}
/**
* For a triangle, use the law of cos to compute the third edge length
* - Given interior angle in radians AngleR, and adjacent edge lengths L0 and L1, this computes the opposing edge length.
*/
double LawOfCosLength(const double AngleR, const double L0, const double L1)
{
double L2sqr = L0 * L0 + L1 * L1 - 2.*L0*L1*FMath::Cos(AngleR);
// theoretically this is always positive, but protect round-off. Alternately, could compute (L0-L1)^2 + 4 L0 L1 Sin^2 ( AngleR/2)
L2sqr = FMath::Max(L2sqr, 0.);
return FMath::Sqrt(L2sqr);
}
/**
* Compute the unsigned distance between two points, described as barycentric coordinates
* relative to a triangle with side lengths L0, L1, L2 is CCW order.
* @params L0, L1, L2 - triangle side lengths in CCW order
* @param BCPoint0 - Barycentric coordinates for the first point
* @param BCPoint1 - Barycentric coordinates for the second point
*
* NB: it is assumed that the points are within the triangle, and the triangle is valid (i.e. lengths satisfy triangle inequality)
*/
double DistanceBetweenBarycentricPoints(const double L0, const double L1, const double L2, const FVector3d& BCPoint0, const FVector3d& BCPoint1)
{
// see Sharp, Soliman and Crane [2019, ACM Transactions on Graphics] or Schindler and Chen [2012, Section 3.2]
using Vector3Type = FVector3d;
using ScalarType = double;
const ScalarType Zero(0);
const Vector3Type u = BCPoint1 - BCPoint0;
const ScalarType dsqr = -(L0 * L0 * u[0] * u[1] + L1 * L1 * u[1] * u[2] + L2 * L2 * u[2] * u[0]);
const ScalarType d = (dsqr < Zero) ? Zero : sqrt(dsqr);
return d;
}
/**
* Compute a vector (in polar form) from triangle corner to a point within the triangle.
* @params L0, L1, L2 - triangle side lengths in CCW order
* @param BarycetricPoint - interior point in triangle
*
* @return, a polar vector from the vertex shared by L0, L2 to the BarycentricPoint in the form
* Vector[0] = radial distance
* Vector[1] = angle measured from the L0 side of the triangle
*/
FVector2d VectorToPoint(const double L0, const double L1, const double L2, const FVector3d& BarycentricPoint)
{
FVector3d BCpi(1., 0., 0.);
FVector3d BCpj(0., 1., 0.);
double rpi = DistanceBetweenBarycentricPoints(L0, L1, L2, BCpi, BarycentricPoint);
double rpj = DistanceBetweenBarycentricPoints(L0, L1, L2, BCpj, BarycentricPoint);
double angle = InteriorAngle(L0, rpj, rpi);
return FVector2d(rpi, angle);
}
/**
* Heron's formula for computing the area of a triangle given the lengths of all three sides.
*/
double TriangleArea(const double L0, const double L1, const double L2)
{
// note: since this is a triangle, the lengths should satisfy triangle inequality, meaning this should be positive
double AreaSqrTimes16 = FMath::Max(0., (L0 + L1 + L2) * (-L0 + L1 + L2 ) * (L0 -L1 + L2) * (L0 + L1 -L2));
return FMath::Sqrt(AreaSqrTimes16) * 0.25;
}
double TriangleArea(const FVector3d& Ls)
{
return TriangleArea(Ls[0], Ls[1], Ls[2]);
}
/**
* Compute the cotangent of the interior angle opposite the edge L0, give a triangle with edges {L0, L1, L2} in CCW order
*/
double ComputeCotangent(const double L0, const double L1, const double L2)
{
const double Area = TriangleArea(L0, L1, L2);
const double CT = 0.25 * (-L0 * L0 + L1 * L1 + L2 * L2) / Area;
return CT;
}
/**
* Return the location of third vertex in a triangle in R2 with prescribed lengths
* such that the resulting triangle could be constructed as { (0,0), (L0, 0), Result}.
*
* NB: this assumes, but does not check, that the lengths satisfy the triangle inequality
*/
FVector2d ComputeOpposingVert2d(const double L0, const double L1, const double L2)
{
const double Area = TriangleArea(L0, L1, L2);
const double Y = 2. * Area / L0;
const double X = FMath::Sqrt(FMath::Max(0., (L2 - Y) * (L2 + Y)));
FVector2d ResultPos(X, Y);
// angle between L0 and L2 edges can be computed form 2 * L0 * L2 * Cos(theta) = L0^2 + L2^2 - L1^2
if (L0 * L0 + L2 * L2 < L1 * L1)
{
ResultPos.X = -X; // angle > 90-degrees.
}
return ResultPos;
}
/**
* Given three side lengths that satisfy the triangle inequality,
* this updates the 2d positions to be the vertices of a triangle,
* the edge lengths of which ( when in CCW order) match the prescribed lengths.
* with vertices { (0,0), (L0, 0), (X, Y)} where Y > 0
*/
void TriangleFromLengths(const double L0, const double L1, const double L2, FVector2d& p0, FVector2d& p1, FVector2d& p3)
{
p0 = FVector2d(0., 0.);
p1 = FVector2d(L0, 0.);
p3 = ComputeOpposingVert2d(L0, L1, L2);
}
};
/**
* Utilities and generic implementation for flipping a mesh to Delaunay
*/
namespace
{
/**
* LIFO for unique DynamicMesh IDs, can be constructed from any FRefCountVector::IndexEnumerable
* and supports a filtering function on construction. If the filtering function is absent, all
* unique elements will be added, otherwise only those selected by the filter.
*
* for example:
*
* auto AddToQueueFilter = [](int ID)->bool{...};
*
* FIndexLIFO EdgeQueueLIFO(Mesh.EdgeIndicesItr(), AddToQueueFilter);
*/
class FIndexLIFO
{
public:
// constructor that adds all unique IDs from IndexEnumerable
FIndexLIFO(FRefCountVector::IndexEnumerable IDEnumerable)
: IsEnqueued(false, (int32)IDEnumerable.Vector->GetMaxIndex())
{
const int32 NumIDs = (int32)IDEnumerable.Vector->GetCount();
IDs.Reserve(NumIDs);
for (int32 ID : IDEnumerable)
{
Enqueue(ID);
}
}
// filtering constructor that only adds IDs for which Filter(ID) is true
FIndexLIFO(FRefCountVector::IndexEnumerable IDEnumerable, const TFunctionRef<bool(int32)>& Filter)
: IsEnqueued(false, (int32)IDEnumerable.Vector->GetMaxIndex())
{
const int32 NumIDs = (int32)IDEnumerable.Vector->GetCount();
IDs.Reserve(NumIDs);
for (int32 ID : IDEnumerable)
{
if (Filter(ID))
{
Enqueue(ID);
}
}
}
bool Dequeue(int32& IDOut)
{
if (IDs.Num())
{
IDOut = IDs.Pop(EAllowShrinking::No);
IsEnqueued[IDOut] = false;
return true;
}
IDOut = -1;
return false;
}
void Enqueue(int32 ID)
{
if (!IsEnqueued[ID])
{
IDs.Add(ID);
IsEnqueued[ID] = true;
}
}
private:
FIndexLIFO();
TBitArray<FDefaultBitArrayAllocator> IsEnqueued;
TArray<int32> IDs;
};
/**
* FIFO for unique Dynamic Mesh IDs - can be constructed from any FRefCountVector::IndexEnumerable
* and supports a filtering function on construction. If the filtering function is absent, all
* unique elements will be added, otherwise only those selected by the filter.
*
* for example:
*
* auto AddToQueueFilter = [](int ID)->bool{...};
*
* FIndexFIFO EdgeQueueFIFO(Mesh.EdgeIndicesItr(), AddToQueueFilter);
*/
class FIndexFIFO
{
public:
// constructor that adds all unique IDs from IndexEnumerable
FIndexFIFO(FRefCountVector::IndexEnumerable IDEnumerable)
: IsEnqueued(false, (int32)IDEnumerable.Vector->GetMaxIndex())
{
const int32 NumEdges = (int32)IDEnumerable.Vector->GetMaxIndex();
for (int32 ID : IDEnumerable)
{
Enqueue(ID);
}
}
// filtering constructor that only adds IDs for which Filter(ID) is true Note: Filter is called in parallel.
FIndexFIFO(FRefCountVector::IndexEnumerable IDEnumerable, const TFunctionRef<bool(int32)>& Filter)
: IsEnqueued(false, (int32)IDEnumerable.Vector->GetMaxIndex())
{
// parallel evaluation that assumes Filter is expensive
const int32 MaxID = (int32)IDEnumerable.Vector->GetMaxIndex();
TArray<int32> ToInclude;
ToInclude.AddZeroed(MaxID);
ParallelFor(MaxID, [&ToInclude, &Filter](int32 ID)
{
if (Filter(ID))
{
ToInclude[ID] = 1;
}
});
for (int32 ID = 0; ID < MaxID; ++ID)
{
if (ToInclude[ID] == 1)
{
Enqueue(ID);
}
}
}
bool Dequeue(int32& IDOut)
{
if (IDs.Dequeue(IDOut))
{
IsEnqueued[IDOut] = false;
return true;
}
IDOut = -1;
return false;
}
void Enqueue(int32 ID)
{
if (!IsEnqueued[ID])
{
IDs.Enqueue(ID);
IsEnqueued[ID] = true;
}
}
private:
FIndexFIFO();
TBitArray<FDefaultBitArrayAllocator> IsEnqueued;
TQueue<int32> IDs;
};
/**
* Carry out edge flips on intrinsic mesh until either the MaxFlipCount is reached or the mesh is fully Delaunay and returns
* the number of flips.
* Note: There can be cases where an edge can not flip (e.g. if the resulting edge already exists)
* - for this reason, there may be some "uncorrected" edges in the resulting mesh.
*
* @param IntrinsicMesh - The mesh to be operated on.
* @param Uncorrected - contains on return, all the edges that could not be flipped.
* @param Threshold - edges with cotan weight less than threshold should be flipped.
*/
template <typename MeshType>
int32 FlipToDelaunayImpl(MeshType& Mesh, TSet<int32>& Uncorrected, const int32 MaxFlipCount, double Threshold = -TMathUtilConstants<double>::ZeroTolerance)
{
Uncorrected.Empty();
// returns true if an edge should be flipped.
auto EdgeShouldFlipFilter = [&Mesh, Threshold](int32 EID)->bool
{
if (!Mesh.IsEdge(EID) || Mesh.IsBoundaryEdge(EID))
{
return false;
}
const double CotanWeightValue = Mesh.EdgeCotanWeight(EID);
// Delaunay test
return CotanWeightValue < Threshold;
};
// Enqueue the "bad" edges.
FIndexFIFO EdgeQueue(Mesh.EdgeIndicesItr(), EdgeShouldFlipFilter);
// flip away the bad edges
int32 FlipCount = 0;
int32 EID;
while (EdgeQueue.Dequeue(EID) && FlipCount < MaxFlipCount)
{
if (EdgeShouldFlipFilter(EID))
{
FIntrinsicTriangulation::FEdgeFlipInfo EdgeFlipInfo;
const EMeshResult Result = Mesh.FlipEdge(EID, EdgeFlipInfo);
if (Result == EMeshResult::Ok)
{
FlipCount++;
for (int32 t = 0; t < 2; ++t)
{
const FIndex3i TriEIDs = Mesh.GetTriEdges(EdgeFlipInfo.Triangles[t]);
int32 IndexOf = TriEIDs.IndexOf(EID);
EdgeQueue.Enqueue(TriEIDs[(IndexOf + 1) % 3]);
EdgeQueue.Enqueue(TriEIDs[(IndexOf + 2) % 3]);
}
}
else
{
Uncorrected.Add(EID);
}
}
}
return FlipCount;
}
};
namespace SignpostSufaceTraceUtil
{
using namespace IntrinsicCorrespondenceUtils;
/**
* helper code that uses FSignpost to trace from a specified intrinsic mesh vertex in a given direction.
*
*Note: this doesn't check the validity of the start point - the calling code should do that
*/
UE::Geometry::FMeshGeodesicSurfaceTracer TraceFromIntrinsicVert( const FSignpost& SignpostData, const int32 TraceStartVID,
const double TracePolarAngle, const double TraceDist )
{
const FDynamicMesh3* SurfaceMesh = SignpostData.SurfaceMesh;
const FSurfacePoint& StartSurfacePoint = SignpostData.IntrinsicVertexPositions[TraceStartVID];
const FSurfacePoint::FSurfacePositionUnion& SurfacePosition = StartSurfacePoint.Position;
double ActualDist = 0.;
UE::Geometry::FMeshGeodesicSurfaceTracer SurfaceTracer(*SurfaceMesh);
switch (StartSurfacePoint.PositionType)
{
case FSurfacePoint::EPositionType::Vertex:
{
// convert vertex location
const int32 ExtrinsicVID = StartSurfacePoint.Position.VertexPosition.VID;
const int32 ExtrinsicRefEID = SignpostData.VIDToReferenceEID[ExtrinsicVID];
const FMeshGeodesicSurfaceTracer::FMeshTangentDirection TangentDirection = { ExtrinsicVID, ExtrinsicRefEID, TracePolarAngle };
ActualDist = SurfaceTracer.TraceMeshFromVertex(TangentDirection, TraceDist);
}
break;
case FSurfacePoint::EPositionType::Edge:
{
// convert edge location - Not used, and might have bugs
const int32 RefSurfaceEID = SurfacePosition.EdgePosition.EdgeID;
double Alpha = SurfacePosition.EdgePosition.Alpha;
// convert to BC
const int32 SurfaceTID = SurfaceMesh->GetEdgeT(RefSurfaceEID).A;
const FIndex2i SurfaceEdgeV = SurfaceMesh->GetEdgeV(RefSurfaceEID);
const int32 IndexOf = SurfaceMesh->GetTriEdges(SurfaceTID).IndexOf(RefSurfaceEID);
const bool bSameOrientation = (SurfaceMesh->GetTriangle(SurfaceTID)[IndexOf] == SurfaceEdgeV.A);
FVector3d BaryPoint(0., 0., 0.);
if (!bSameOrientation)
{
Alpha = 1. - Alpha;
}
BaryPoint[IndexOf] = Alpha;
BaryPoint[(IndexOf + 1) % 3] = 1. - Alpha;
FMeshGeodesicSurfaceTracer::FMeshSurfaceDirection DirectionOnSurface(RefSurfaceEID, TracePolarAngle);
ActualDist = SurfaceTracer.TraceMeshFromBaryPoint(SurfaceTID, BaryPoint, DirectionOnSurface, TraceDist);
}
break;
case FSurfacePoint::EPositionType::Triangle:
{
// trace from BC point in triangle
const int32 SurfaceTID = SurfacePosition.TriPosition.TriID;
const FVector3d BaryPoint = SurfacePosition.TriPosition.BarycentricCoords;
const int32 RefSurfaceEID = SignpostData.TIDToReferenceEID[SurfaceTID];
FMeshGeodesicSurfaceTracer::FMeshSurfaceDirection DirectionOnSurface(RefSurfaceEID, TracePolarAngle);
SurfaceTracer.TraceMeshFromBaryPoint(SurfaceTID, BaryPoint, DirectionOnSurface, TraceDist);
}
break;
default:
{
// not possible
check(0);
}
}
// RVO..
return SurfaceTracer;
}
};
namespace FNormalCoordSurfaceTraceImpl
{
using namespace IntrinsicCorrespondenceUtils;
// @return ID of a vertex-adjacent edge that will allow all adjacent edges to be visited in CCW order.
template <typename MeshType>
int32 IdentifyInitialAdjacentEdge(const MeshType& Mesh, const int32 VID)
{
if (!Mesh.IsVertex(VID))
{
return MeshType::InvalidID;
}
if (Mesh.IsBoundaryVertex(VID))
{
// find the clockwise-most edge
for (int32 NbrEID : Mesh.VtxEdgesItr(VID))
{
if (Mesh.IsBoundaryEdge(NbrEID))
{
const int32 NbrTID = Mesh.GetEdgeT(NbrEID).A;
const int32 IndexOf = Mesh.GetTriEdges(NbrTID).IndexOf(NbrEID);
if (Mesh.GetTriangle(NbrTID)[IndexOf] == VID)
{
return NbrEID;
}
}
}
return MeshType::InvalidID; // shouldn't reach
}
else
{
for (int32 NbrEID : Mesh.VtxEdgesItr(VID))
{
// get the first
return NbrEID;
}
return MeshType::InvalidID; // shouldn't reach here
}
}
/**
* Low-level method for use with normal-coordinate equipped intrinsic meshes.
*
* Given a surface edge that intersects (but does not terminate at) an intrinsic vertex 'IntrinsicVID'
* this function identifies the adjacent intrinsic mesh elements (i.e. edges or faces ) also containing that surface edge.
*
* This is used when following a surface mesh edge across an intrinsic mesh (using normal coordinates).
* Note: this situation is the result of an edge split of an intrinsic edge that was initially aligned with a surface edge.
*/
template <typename IntrinsicMeshType>
bool GetAdjElementsContainingSurfaceEdge(const IntrinsicMeshType& IntrinsicMesh, const int32 IntrinsicVID, TArray<TTuple<int32, int32>>& IntrisicTIDs, TArray<int32>& IntrisicEIDs)
{
checkSlow( IsEdgePoint(IntrinsicMesh.GetVertexSurfacePoint(IntrinsicVID)) );
const FNormalCoordinates& NCoords = IntrinsicMesh.GetNormalCoordinates();
for (const int32 EID : IntrinsicMesh.VtxEdgesItr(IntrinsicVID))
{
if (NCoords.IsSurfaceEdgeSegment(EID))
{
IntrisicEIDs.Add(EID);
}
// triangle that has EID as an outgoing edge
const int32 TID = [&]
{
const FIndex2i AdjTris = IntrinsicMesh.GetEdgeT(EID);
for (int32 i = 0; i < 2; ++i)
{
const int32 TID = AdjTris[i];
if (TID == IntrinsicMeshType::InvalidID)
{
continue;
}
const FIndex3i TriEIDs = IntrinsicMesh.GetTriEdges(TID);
const int32 IndexOf = TriEIDs.IndexOf(EID);
const bool bIsEIDOutgoing = (IntrinsicMesh.GetTriangle(TID)[IndexOf] == IntrinsicVID);
if (bIsEIDOutgoing)
{
return TID;
}
}
return IntrinsicMeshType::InvalidID;
}();
if (TID == IntrinsicMeshType::InvalidID)
{
continue;
}
const FIndex3i TriEIDs = IntrinsicMesh.GetTriEdges(TID);
const int32 IndexOf = TriEIDs.IndexOf(EID);
// permuted triangle is {a, b, c} where a is VID.
// given a surface edge crosses 'a' it will traverse the face of this triangle iff it exits face bc.
const FIndex3i PermutedEIDs = IntrinsicMesh.Permute(IndexOf, TriEIDs);
const int32 Nbc = NCoords.NumEdgeCrossing(PermutedEIDs[1]); // number of surface edges that exits face bc
if (Nbc == 0) // a surface edge that crosses 'a' doesn't exit this triangle face bc
{
continue;
}
const int32 Eca_b = NCoords.NumCornerEmanatingRefEdges(PermutedEIDs, 1); // number of surface edges that exit corner b and exit ca
if (Eca_b > 0) // a surface edge that crosses 'a' is blocked from exiting this triangle by other surface edges
{
continue;
}
const int32 Eab_c = NCoords.NumCornerEmanatingRefEdges(PermutedEIDs, 2); // number of surface edges that exit corner c and exit ab
if (Eab_c > 0) // a surface edge that crosses 'a' is blocked from exiting this triangle by other surface edges
{
continue;
}
const int32 Cb = NCoords.NumCornerCrossingRefEdges(PermutedEIDs, 1); // number of surface edges that cross corner b
const int32 Cc = NCoords.NumCornerCrossingRefEdges(PermutedEIDs, 2); // number of surface edges that cross corner c
if (Nbc > Cb + Cc) // a surface edge that crosses 'a' must exit face bc
{
IntrisicTIDs.Add(TTuple<int32, int32>(TID, IndexOf));
}
}
return (IntrisicTIDs.Num() > 0 || IntrisicEIDs.Num() > 0);
}
/**
* Low-level method for use with normal-coordinate equipped intrinsic meshes.
*
* Given an intrinsic mesh vertex that is situated on a surface mesh edge and information that specifies the incoming section of the surface edge,
* this function computes the crossing point where the outgoing section of the surface edge exits the intrinsic vertex 1-ring.
*
* if bIncomingIsEdge == true, then the incoming section of the surface edge is along the intrinsic edge EdgeID = IncomingID,
* otherwise it crossed the intrinsic triangle TriID = IncomingID.
*
* on return the IncomingID and bIncomingIsEdge have been updated with the surface mesh elements traversed by the path to next crossing,
* and the crossing itself is encoded in the returned FEdgeAndCrossing struct. for convenience VID is also updated if the next crossing is an intrinsic vertx.
*/
template <typename IntrinsicMeshType>
FNormalCoordinates::FEdgeAndCrossingIdx NextCrossingFromEdgePoint(const IntrinsicMeshType& IntrinsicMesh, int32& VID, int32& IncomingID, bool& bIncomingIsEdge)
{
checkSlow(IsEdgePoint(IntrinsicMesh.GetVertexSurfacePoint(VID)));
// four cases: the outer product of "enters {on intrinsic edge, across intrinsic tri}" with "exits {on intrinsic edge, across intrinsic tri}"
// find intrinsic mesh elements adjacent to VID that support the surface edge
TArray<TTuple<int32, int32>> TIDs; TArray<int32> EIDs;
GetAdjElementsContainingSurfaceEdge(IntrinsicMesh, VID, TIDs, EIDs);
// only one edge crosses the implicit vert, so there can only be 2 sides to that edge ..
checkSlow(TIDs.Num() + EIDs.Num() == 2);
auto EncodeExitsOnEdge = [&](const int32 ExitEID, FNormalCoordinates::FEdgeAndCrossingIdx& Xing )
{
const typename IntrinsicMeshType::FEdge ExitEdge = IntrinsicMesh.GetEdge(ExitEID);
const int32 AdjTID = ExitEdge.Tri[0];
const int32 IndexOfe = IntrinsicMesh.GetTriEdges(AdjTID).IndexOf(ExitEID);
const int32 IndexOfNextV = (IntrinsicMesh.GetTriangle(AdjTID)[IndexOfe] == VID) ? (IndexOfe + 1) % 3 : IndexOfe;
Xing.TID = ExitEdge.Tri[0];
Xing.EID = IndexOfNextV;
Xing.CIdx = 0;
return Xing;
};
auto EncodeExitsAcrossTri = [&](const TTuple<int32, int32>& TridAndIdx, FNormalCoordinates::FEdgeAndCrossingIdx& Xing)
{
const FNormalCoordinates& NCoords = IntrinsicMesh.GetNormalCoordinates();
const int32 ExitTID = TridAndIdx.Get<0>();
const int32 OutgoingIndex = TridAndIdx.Get<1>();
// when counting the surface edge that cross the triangle face opposite to VID, what number is the single edge that eminates from VID?
const FIndex3i TriEIDs = IntrinsicMesh.GetTriEdges(ExitTID);
// permuted triangle is {a, b, c} where a is VID.
// given a surface edge crosses 'a' it will traverse the face of this triangle iff it exits face bc.
const FIndex3i PermutedEIDs = IntrinsicMesh.Permute(OutgoingIndex, TriEIDs);
const int32 Cc = NCoords.NumCornerCrossingRefEdges(PermutedEIDs, 2); // number of surface edges that cross corner c
// counting from corner c towards b, this is the 'CC +1'-th surface edge to cross the intrinsic edge
const int32 CIdx = Cc + 1;
Xing.TID = ExitTID;
Xing.EID = PermutedEIDs[1];
Xing.CIdx = CIdx;
return Xing;
};
FNormalCoordinates::FEdgeAndCrossingIdx Xing({-1, -1, -1});
if (bIncomingIsEdge) // entered on an intrinsic edge
{
const int32 EnterEID = IncomingID;
const bool bExitsOnEdge = (EIDs.Num() == 2);
const bool bExitsAccrosTri = (TIDs.Num() > 0);
if (bExitsOnEdge)
{
checkSlow(!bExitsAccrosTri);
const int32 Id = (EIDs[0] == EnterEID) ? 1 : 0;
checkSlow(EIDs[1 - Id] == EnterEID);
const int32 ExitEID = EIDs[Id];
EncodeExitsOnEdge(ExitEID, Xing);
VID = IntrinsicMesh.GetTriangle(Xing.TID)[Xing.EID];
IncomingID = ExitEID;
bIncomingIsEdge = true;
}
else if (bExitsAccrosTri)
{
checkSlow(!bExitsOnEdge);
checkSlow(TIDs.Num() == 1); // can only traverse a single tri ( we already know it entered by an edge)
EncodeExitsAcrossTri(TIDs[0], Xing);
IncomingID = TIDs[0].Get<0>();
bIncomingIsEdge = false;
}
else
{
checkSlow(0);
}
}
else // entered across and intrinsic triangle
{
const int32 EnterTID = IncomingID;
const bool bExitsOnEdge = (EIDs.Num() == 1);
const bool bExitsAcrossTri = (TIDs.Num() == 2);
if (bExitsOnEdge)
{
const int32 ExitEID = EIDs[0];
EncodeExitsOnEdge(ExitEID, Xing);
VID = IntrinsicMesh.GetTriangle(Xing.TID)[Xing.EID];
IncomingID = ExitEID;
bIncomingIsEdge = true;
}
else if (bExitsAcrossTri)
{
const int32 Id = (TIDs[0].Get<0>() == EnterTID) ? 1 : 0;
EncodeExitsAcrossTri(TIDs[Id], Xing);
IncomingID = TIDs[0].Get<0>();
bIncomingIsEdge = false;
}
else
{
checkSlow(0);
}
}
return Xing;
}
template <typename IntrinsicMeshType>
void ContinueFromEdgePointCrossing(const IntrinsicMeshType& IntrinsicMesh, int32 VID, int32 IncomingID, bool bIncomingIsEdge, TArray<FNormalCoordinates::FEdgeAndCrossingIdx>& Crossings)
{
int32 CIdx = 0;
while (CIdx == 0 && IsEdgePoint(IntrinsicMesh.GetVertexSurfacePoint(VID)))
{
FNormalCoordinates::FEdgeAndCrossingIdx Xing = NextCrossingFromEdgePoint(IntrinsicMesh, VID, IncomingID, bIncomingIsEdge);
Crossings.Add(Xing);
CIdx = Xing.CIdx;
}
}
/**
* Low-level method for use with normal-coordinate equipped intrinsic meshes.
* Traces the p-th surface edge crossing of the intrinsic edge edgeID across the adjacent intrinsic triangle TriID
* here 'P' is counted is the CCW direction (relative to TriID).
*
* on returns TTuple<in32, int32> = {EdgeID,P}
* on return if 'P' == 0, the path exits a vertex otherwise it exits the updated EdgeID (with index P relative to the next triangle it enters)
*
* This is adapted from Algorithm 2 of Gillespi et al, 2020
*/
template <typename IntrinsicMeshType>
TTuple<int32, int32> GetCrossingExit(const IntrinsicMeshType& IntrinsicMesh, const FNormalCoordinates& NCoords, const int32 TriID, const int32 EdgeIDin, const int32 Pin)
{
TTuple<int32, int32> ExitCoord(EdgeIDin, Pin);
int32& EdgeID = ExitCoord.Get<0>();
int32& P = ExitCoord.Get<1>();
const FIndex3i TriEIDs = IntrinsicMesh.GetTriEdges(TriID);
const int32 IndexOf = TriEIDs.IndexOf(EdgeID);
const int32 Edge_ji = EdgeID;
const int32 Edge_il = TriEIDs[(IndexOf + 1) % 3];
const int32 Edge_lj = TriEIDs[(IndexOf + 2) % 3];
const int32 N_ji = NCoords.NumEdgeCrossing(Edge_ji);
const int32 N_il = NCoords.NumEdgeCrossing(Edge_il);
const int32 N_lj = NCoords.NumEdgeCrossing(Edge_lj);
if (N_ji > N_lj + N_il) // case 1
{
// some of the N_ji edges that cross edge IJ must connect with the vertex at L
if (P <= N_il)
{
// exits side il
EdgeID = Edge_il;
P = Pin;
}
else if ((N_il < P) && (P <= N_ji - N_lj))
{
// this path terminates at vertex
// this is a slight abuse. The convention, when CrossingIndex = 0, we specify the vertex by the edge that originates there.
EdgeID = TriEIDs.IndexOf(Edge_lj); // GetTriange()[EdgeID] = vert. note changed from Edge_lj where i = GetTriangle()[TriEIDs.IndexOf(EdgeID_lj)] will be the vertex.
P = 0;
}
else
{
EdgeID = Edge_lj;
P = Pin - (N_ji - N_lj);
}
}
else if (N_lj > N_ji + N_il) // case 2
{
EdgeID = Edge_lj;
P = Pin + (N_lj - N_ji);
}
else if (N_il > N_ji + N_lj) // case 3
{
EdgeID = Edge_il;
P = Pin;
}
else // case 4
{
const int32 Cij_l = (N_lj + N_il - N_ji) / 2;
if (P <= N_il - Cij_l)
{
EdgeID = Edge_il;
P = Pin;
}
else
{
const int32 Clj_i = (N_il + N_ji - N_lj) / 2;
EdgeID = Edge_lj;
P = Pin - Clj_i + Cij_l;
}
}
return ExitCoord;
}
/**
* Low-level method: code that uses normal coordinates to continues tracing a surface edge across an intrinsic mesh until it encounters a vertex.
*
*
* This function requires that the fist edge crossing has already been computed will populate the a sequence of intrinsic edge crossings for the surface mesh edge
* until the path encounters a vertex. The actual crossings are recorded as a list of the edges crossed, and not the location on the individual edges.
*/
template <typename IntrinsicMeshType>
void ContinueTraceSurfaceEdgeAcrossFaces( const IntrinsicMeshType& IntrinsicMesh, TArray<FNormalCoordinates::FEdgeAndCrossingIdx>& Crossings )
{
const FNormalCoordinates::FEdgeAndCrossingIdx& LastXing = Crossings.Last();
checkSlow(LastXing.CIdx != 0); // continuing a face crossing
const int32 StartTID = LastXing.TID;
const int32 StartEID = LastXing.EID;
const int32 StartP = LastXing.CIdx;
const FNormalCoordinates& NCoords = IntrinsicMesh.GetNormalCoordinates();
if (!IntrinsicMesh.IsTriangle(StartTID))
{
return;
}
const FIndex3i StartTriEIDs = IntrinsicMesh.GetTriEdges(StartTID);
checkSlow(StartTriEIDs.IndexOf(StartEID) != -1);
const int32 NumXStartEID = NCoords.NumEdgeCrossing(StartEID);
if (StartP > NumXStartEID || StartP < 1) // note the actual permitted range of P is smaller..
{
checkSlow(0); // this shouldn't happen
return;
}
// This is adapted from Algorithm 2 of Gillespi et al, 2020
auto GetNextCrossing = [&IntrinsicMesh, &NCoords](int32& TriID, int32& EdgeID, int32& P)
{
// get next tri
TriID = [&]
{
const FIndex2i EdgeT = IntrinsicMesh.GetEdgeT(EdgeID);
return (EdgeT.A == TriID) ? EdgeT.B : EdgeT.A;
}();
checkSlow(TriID != -1);
const TTuple<int32, int32> ExitEIDandP = GetCrossingExit(IntrinsicMesh, NCoords, TriID, EdgeID, P);
EdgeID = ExitEIDandP.Get<0>();
P = ExitEIDandP.Get<1>();
};
int32 P = StartP;
int32 EID = StartEID;
int32 TID = StartTID;
do
{
GetNextCrossing(TID, EID, P);
auto& Xing = Crossings.AddZeroed_GetRef();
Xing.TID = TID;
Xing.EID = EID;
Xing.CIdx = P;
checkSlow(P > -1);
} while (P != 0); // P = 0 when the path terminates ( paths terminate at vertices only)
}
/**
* Low-level method for use with normal-coordinate equipped intrinsic meshes.
*
* Given a partial trace of a surface edge, continue the trace across the intrinsic mesh until the path terminates at one end
* of the surface edge.
*
* Crossings.Last() must be an edge crossing, and not a vertex point.
*/
template <typename IntrinsicMeshType>
void ContinueTraceSurfaceEdge(const IntrinsicMeshType& IntrinsicMesh, TArray<FNormalCoordinates::FEdgeAndCrossingIdx>& Crossings)
{
if (Crossings.Num() == 0)
{
return;
}
auto IsIntrinsicVertex = [](const FNormalCoordinates::FEdgeAndCrossingIdx& Xing)->bool
{
return (Xing.CIdx == 0);
};
checkSlow(!IsIntrinsicVertex(Crossings.Last()));
auto IsSurfaceVertex = [&IntrinsicMesh, &IsIntrinsicVertex](const FNormalCoordinates::FEdgeAndCrossingIdx& Xing)->bool
{
if (!IsIntrinsicVertex(Xing))
{
return false;
}
// convert to intrinsic VID
const int32 IndexOfV = Xing.EID;
const int32 IntrinsicVID = IntrinsicMesh.GetTriangle(Xing.TID)[IndexOfV];
return IsVertexPoint(IntrinsicMesh.GetVertexSurfacePoint(IntrinsicVID));
};
while (!IsSurfaceVertex(Crossings.Last()))
{
const FNormalCoordinates::FEdgeAndCrossingIdx& LastXing = Crossings.Last();
if (!IsIntrinsicVertex(LastXing))
{
// trace enters intrinsic triangle edge - continue across intrinsic triangle faces.
ContinueTraceSurfaceEdgeAcrossFaces(IntrinsicMesh, Crossings);
}
else
{
// trace hit an intrinsic vertex, coming from an intrinsic triangle face.
// convert to intrinsic VID
const int32 IndexOfV = LastXing.EID;
const int32 IntrinsicVID = IntrinsicMesh.GetTriangle(LastXing.TID)[IndexOfV];
checkSlow(IsEdgePoint(IntrinsicMesh.GetVertexSurfacePoint(IntrinsicVID)));
// advance along the surface edge until it either enters an intrinsic triangle or
// follows on more intrinsic edges to terminate at a surface vertex.
const bool bEnterTypeEdge = false; // we entered across a triangle face
const int32 EnterEID = LastXing.TID;
ContinueFromEdgePointCrossing(IntrinsicMesh, IntrinsicVID, EnterEID, bEnterTypeEdge, Crossings);
}
}
}
/**
* Low-level method for use with normal-coordinate equipped intrinsic meshes.
*
* Traces a surface edge across an intrinsic mesh, the result is an array of intrinsic edges crossed and an integer coordinates
* that specify the intrinsic edge specific index of the crossing.
*
* The surface edge to trace is specified by the triangle adjacent to the edge, and the local index of the edge within that triangle
*/
template <typename IntrinsicMeshType>
TArray<FNormalCoordinates::FEdgeAndCrossingIdx> TraceSurfaceEdge( const IntrinsicMeshType& IntrinsicMesh,
const int32 SurfaceTID, const int32 IndexOf )
{
const FNormalCoordinates& NCoords = IntrinsicMesh.GetNormalCoordinates();
const FDynamicMesh3& SurfaceMesh = *NCoords.SurfaceMesh;
if (!SurfaceMesh.IsTriangle(SurfaceTID) || IndexOf > 2 || IndexOf < 0)
{
TArray< FNormalCoordinates::FEdgeAndCrossingIdx > EmptyCrossings;
return EmptyCrossings;
}
// the origin vertex
const int32 StartVID = SurfaceMesh.GetTriangle(SurfaceTID)[IndexOf];
// the surface edge we are tracing
const int32 TraceEID = SurfaceMesh.GetTriEdge(SurfaceTID, IndexOf);
const int32 RefEID = NCoords.VIDToReferenceEID[StartVID];
const int32 OrderOfTraceEID = NCoords.GetEdgeOrder(StartVID, TraceEID);
const int32 ValenceOfStartVID = NCoords.RefVertDegree[StartVID];
checkSlow(OrderOfTraceEID != -1);
// data to gather about the intrinsic triangle that where the trace starts.
struct
{
bool bIsAlsoSurfaceEdge = false;
int32 TID = -1;
int32 EID = -1;
int32 IdxOf = -1;
int32 FirstRoundabout = -1;
int32 SecondRoundabout = -1;
} FinderInfo;
// Identify the target intrinsic triangle where this edge trace starts.
{
// where to start when visiting the intrinsic triangles that are adjacent to the startVID
const int32 VistorStartEID = IdentifyInitialAdjacentEdge(IntrinsicMesh, StartVID);
// test for the special case when the StartVID has valence one on the intrinsic mesh (ie a single triangle is wrapped into a cone)
const FIndex2i IntrinsicEdgeT = IntrinsicMesh.GetEdgeT(VistorStartEID);
if (IntrinsicEdgeT.A == IntrinsicEdgeT.B)
{
const int32 IntrinsicEID = VistorStartEID;
const int32 IntrinsicTID = IntrinsicEdgeT.A;
const FIndex3i IntrinsicVIDs = IntrinsicMesh.GetTriangle(IntrinsicTID);
const int32 IdxOf = IntrinsicVIDs.IndexOf(StartVID);
const int32 ThisRoundabout = NCoords.RoundaboutOrder[IntrinsicTID][IdxOf];
const bool bOnSurfaceEdge = NCoords.IsSurfaceEdgeSegment(IntrinsicEID);
const bool bEquivalentToSurface = bOnSurfaceEdge && (ThisRoundabout == OrderOfTraceEID);
FinderInfo.bIsAlsoSurfaceEdge = bEquivalentToSurface;
FinderInfo.TID = IntrinsicTID;
FinderInfo.EID = IntrinsicEID;
FinderInfo.IdxOf = IdxOf;
FinderInfo.FirstRoundabout = ThisRoundabout;
FinderInfo.SecondRoundabout = ThisRoundabout;
}
else
{
// when visiting each adjacent intrinsic triangle (in CCW order) test if the surface edge we trace is inside this triangle.
auto EdgeFinder = [&](int32 IntrinsicTID, int32 IntrinsicEID, int32 IdxOf)->bool
{
const FIndex3i IntrinsicTriEIDs = IntrinsicMesh.GetTriEdges(IntrinsicTID);
const int32 ThisRoundabout = NCoords.RoundaboutOrder[IntrinsicTID][IdxOf];
const bool bOnSurfaceEdge = NCoords.IsSurfaceEdgeSegment(IntrinsicEID);
const bool bEquivalentToSurface = bOnSurfaceEdge && (ThisRoundabout == OrderOfTraceEID);
int32 NextRoundabout = -1;
bool bShouldBreak = false;
if (bEquivalentToSurface) // test if the current intrinsic edge is the same as the surface mesh trace edge
{
bShouldBreak = true;
}
else // test if the edge starts in this intrinsic triangle
{
const int32 NextIntrinsicEID = IntrinsicTriEIDs[(IdxOf + 2) % 3];
const FIndex2i NextIntrinsicEdgeT = IntrinsicMesh.GetEdgeT(NextIntrinsicEID);
const int32 NextIntrinsicTID = (NextIntrinsicEdgeT.A == IntrinsicTID) ? NextIntrinsicEdgeT.B : NextIntrinsicEdgeT.A;
if (NextIntrinsicTID != -1)
{
const int32 NextIndexOf = IntrinsicMesh.GetTriEdges(NextIntrinsicTID).IndexOf(NextIntrinsicEID);
NextRoundabout = NCoords.RoundaboutOrder[NextIntrinsicTID][NextIndexOf];
if (NextRoundabout < ThisRoundabout) // Order = {NextRO |zero cut | ThisRO}
{
// we have crossed the zero roundabout cut ( e.g. NextRo = 2oclock and ThisRo = 11oclock)
if ( NextRoundabout > OrderOfTraceEID) // Order = {NextRO, Trace | zero cut | ThisRO}
{
bShouldBreak = true;
}
else
{
NextRoundabout += ValenceOfStartVID; // Order = {NextRO, | zero cut |, Trace(?), ThisRO}
}
}
checkSlow(IntrinsicMesh.GetTriangle(NextIntrinsicTID)[NextIndexOf] == StartVID);
//checkSlow(NextRoundabout != 0 || ThisRoundabout != 0)
if ((NextRoundabout > OrderOfTraceEID) && (OrderOfTraceEID >= ThisRoundabout))
{
bShouldBreak = true;
}
}
else // mesh boundary case and we made it to the last triangle w/o finding this edge. it must be in this one.
{
bShouldBreak = true;
}
}
if (bShouldBreak)
{
FinderInfo.bIsAlsoSurfaceEdge = bEquivalentToSurface;
FinderInfo.TID = IntrinsicTID;
FinderInfo.EID = IntrinsicEID;
FinderInfo.IdxOf = IdxOf;
FinderInfo.FirstRoundabout = ThisRoundabout;
FinderInfo.SecondRoundabout = NextRoundabout;
}
return bShouldBreak;
};
VisitVertexAdjacentElements(IntrinsicMesh, StartVID, VistorStartEID, EdgeFinder);
checkSlow(FinderInfo.TID != -1); // should have found something!
}
}
TArray<FNormalCoordinates::FEdgeAndCrossingIdx > Crossings;
// add start vertex location
auto& StartXing = Crossings.AddZeroed_GetRef();
StartXing.TID = FinderInfo.TID;
StartXing.EID = FinderInfo.IdxOf; // TriVIDs(IdxOf) = StartVID
StartXing.CIdx = 0;
if (FinderInfo.bIsAlsoSurfaceEdge)
{
// the surface edge we are tracing is initially coincident with an intrinsic edge.
// add end intrinsic vertex location
auto& EndXing = Crossings.AddZeroed_GetRef();
EndXing.TID = FinderInfo.TID;
EndXing.EID = (FinderInfo.IdxOf + 1) % 3; // TriVIDs( (IdxOf +1)%3) = EndVID
EndXing.CIdx = 0;
const int32 IndexOfV = (FinderInfo.IdxOf + 1) % 3;
const int32 EndIntrinsicVID = IntrinsicMesh.GetTriangle(FinderInfo.TID)[IndexOfV];
// the surface edge will continue if the end intrinsic vertex is not a surface vertex.
// advance along the surface edge until it either enters an intrinsic triangle or
// follows on more intrinsic edges to terminate at a surface vertex.
if (IsEdgePoint(IntrinsicMesh.GetVertexSurfacePoint(EndIntrinsicVID)))
{
const bool bEnterTypeEdge = true;
const int32 EnterEID = FinderInfo.EID;
ContinueFromEdgePointCrossing(IntrinsicMesh, EndIntrinsicVID, EnterEID, bEnterTypeEdge, Crossings);
const bool bVertexTerminated = (Crossings.Last().CIdx == 0);
if (bVertexTerminated)
{
// Made it to the end of the surface edge since the trace must have hit an intrinsic vertex that is also a surface vertex.
return MoveTemp(Crossings);
}
else
{
// continue across intrinsic faces
ContinueTraceSurfaceEdge(IntrinsicMesh, Crossings);
}
}
else
{
// Made it to the end of the surface edge since the trace must have hit an intrinsic vertex that is also a surface vertex.
return MoveTemp(Crossings);
}
}
else
{
// edge trace starts at StartVID and exits the opposite edge of intrinsic Tri TID (but it isn't an edge of the intrinsic tri).
const FIndex3i TriEIDs = IntrinsicMesh.GetTriEdges(FinderInfo.TID);
const int32 OppEID = TriEIDs[(FinderInfo.IdxOf + 1) % 3];
const int32 CrossingIdx = [&]
{
const int32 AdvanceFromFirstRO = ( (ValenceOfStartVID + OrderOfTraceEID) - FinderInfo.FirstRoundabout) % ValenceOfStartVID;
if (NCoords.IsSurfaceEdgeSegment(FinderInfo.EID))
{
return AdvanceFromFirstRO;
}
else
{
const int32 NumCrossings = NCoords.NumEdgeCrossing(FinderInfo.EID);
return AdvanceFromFirstRO + 1 + NumCrossings;
}
}();
checkSlow(CrossingIdx > 0); // would be zero if the edge was also a surface edge, but that case is handled above
// the first intrinsic edge crossing. Need this to jump-start the continuation.
auto& Xing = Crossings.AddZeroed_GetRef();
Xing.TID = FinderInfo.TID;
Xing.EID = OppEID;
Xing.CIdx = CrossingIdx;
ContinueTraceSurfaceEdge(IntrinsicMesh, Crossings);
}
return MoveTemp(Crossings);
}
/**
* Low-level method for use with normal-coordinate equipped intrinsic meshes.
*
* Traces a surface edge across an intrinsic mesh, the result is an array of intrinsic edges crossed each with a corresponding
* integer. The integer is used to disambiguate when a single intrinsic edge is crossed by multiple surface edges.
*/
template <typename IntrinsicMeshType>
TArray<FNormalCoordinates::FEdgeAndCrossingIdx> TraceSurfaceEdge( const IntrinsicMeshType& IntrinsicMesh,
const int32 SurfaceEID, const bool bReverse )
{
const FNormalCoordinates& NCoords = IntrinsicMesh.GetNormalCoordinates();
const FDynamicMesh3& SurfaceMesh = *NCoords.SurfaceMesh;
if (!SurfaceMesh.IsEdge(SurfaceEID))
{
TArray< FNormalCoordinates::FEdgeAndCrossingIdx > Crossings;
return MoveTemp(Crossings);
}
const FIndex2i EdgeV = SurfaceMesh.GetEdgeV(SurfaceEID);
const int32 StartVID = (bReverse) ? EdgeV.B : EdgeV.A;
// if this surface edge corresponds to an intrinsic edge, then the trace is trivial
{
// note this test relies on the fact that intrinsic vertices that sit on surface mesh vertices
// will have the same vertex IDs by construction.
int32 EquivalentIntrinsicEID = -1;
const int32 EndVID = (bReverse) ? EdgeV.A : EdgeV.B;
for (int32 IntrinsicEID : IntrinsicMesh.VtxEdgesItr(StartVID))
{
const FIndex2i IntrinsicEdgeV = IntrinsicMesh.GetEdgeV(IntrinsicEID);
if (IntrinsicEdgeV.IndexOf(EndVID) != -1 && NCoords.IsSurfaceEdgeSegment(IntrinsicEID))
{
EquivalentIntrinsicEID = IntrinsicEID;
break;
}
}
if (EquivalentIntrinsicEID != -1)
{
checkSlow(IntrinsicMesh.IsEdge(EquivalentIntrinsicEID));
const int32 IntrinsicEID = EquivalentIntrinsicEID;
const int32 IntrinsicTID = IntrinsicMesh.GetEdgeT(IntrinsicEID).A;
const int32 IndexOf = IntrinsicMesh.GetTriEdges(IntrinsicTID).IndexOf(IntrinsicEID);
const bool bRerverseIntrinsic = !(IntrinsicMesh.GetTriangle(IntrinsicTID)[IndexOf] == StartVID);
TArray< FNormalCoordinates::FEdgeAndCrossingIdx > Crossings;
Crossings.SetNumUninitialized(2);
auto& XingStart = Crossings[0];
XingStart.TID = IntrinsicTID;
XingStart.EID = (bRerverseIntrinsic) ? (IndexOf + 1) % 3 : IndexOf;
XingStart.CIdx = 0;
auto& XingEnd = Crossings[1];
XingEnd.TID = IntrinsicTID;
XingEnd.EID = (bRerverseIntrinsic) ? IndexOf : (IndexOf + 1) % 3;
XingEnd.CIdx = 0;
return MoveTemp(Crossings);
}
}
// the edge isn't equivalent to an intrinsic edge: encode its direction by identifying it as an edge of a triangle and do the trace
{
const FIndex2i EdgeT = SurfaceMesh.GetEdgeT(SurfaceEID);
int32 TID = EdgeT.A;
int32 IndexOf = SurfaceMesh.GetTriEdges(TID).IndexOf(SurfaceEID);
if (SurfaceMesh.GetTriangle(TID)[IndexOf] != StartVID)
{
if (EdgeT.B != -1)
{
TID = EdgeT.B;
IndexOf = SurfaceMesh.GetTriEdges(TID).IndexOf(SurfaceEID);
checkSlow(SurfaceMesh.GetTriangle(TID)[IndexOf] == StartVID);
return TraceSurfaceEdge(IntrinsicMesh, TID, IndexOf);
}
else
{
TArray<FNormalCoordinates::FEdgeAndCrossingIdx> TraceResult = TraceSurfaceEdge(IntrinsicMesh, TID, IndexOf);
Algo::Reverse(TraceResult);
return MoveTemp(TraceResult);
}
}
else
{
return TraceSurfaceEdge(IntrinsicMesh, TID, IndexOf);
}
}
}
/**
* Low-level utility for use with normal-coordinate equipped intrinsic meshes.
*
* Utility to trace an edge defined on the TraceMesh across the HostMesh, given a list of host mesh edges intersected by the trace mesh.
* this assumes the host mesh and trace mesh come from an intrinsic mesh, surface mesh pair.
*
* From the list of edges being crossed, this creates a 2d triangle strip of the triangles being traversed, and translates Trace{Start,End}SurfacePosition
* to this space so it can compute the actual intersection locations of the trace with the triangle edges (stored line-based barycentric coord, alpha).
*
* Note: this assumes, that StartSurfacePoint and EndSurfacePoints are actually points on the edge, and HostXing lists the Ids of the edges
* being crossed in order.
*/
template<typename HostMeshType, typename TraceMeshType, typename TraceVIDToSurfacePointFtor>
void ConvertEdgesCrossed( const FSurfacePoint& StartSurfacePoint, const FSurfacePoint& EndSurfacePoint,
const TArray<int32>& HostEdgesCrossed, const HostMeshType& HostMesh,
const TraceMeshType& TraceMesh, double CoalesceThreshold,
const TraceVIDToSurfacePointFtor& VIDToSurfacePoint,
TArray<FSurfacePoint>& EdgeTrace)
{
if (HostEdgesCrossed.Num() == 0)
{
// no edge crossings to convert.
return;
}
// utility to keep from adding duplicate VIDs. Duplicate VIDs could result when Coalescing..
auto AddVIDToTrace = [&EdgeTrace](const int32 HostVID)
{
if (EdgeTrace.Num() == 0)
{
EdgeTrace.Emplace(HostVID);
}
else
{
const FSurfacePoint& LastPoint = EdgeTrace.Last();
const bool bSameAsLast = ( IsVertexPoint(LastPoint)
&& LastPoint.Position.VertexPosition.VID == HostVID );
if (!bSameAsLast)
{
EdgeTrace.Emplace(HostVID);
}
}
};
// knowing the host mesh edges this trace edge crosses, we make a 2d triangle strip by unfolding the 3d triangles the edge crosses
// and solve a 2x2 problem for each 2d edge crossed.
// struct holds bare-bones 2d triangle strip and ability to map vertexIDs from the src mesh
struct
{
TMap<int32, int32> ToStripIndexMap; // maps from mesh VID to triangle strip VID.
TArray<FVector2d> StripVertexBuffer;
} TriangleStrip;
TriangleStrip.StripVertexBuffer.Reserve(3 + HostEdgesCrossed.Num());
// Functor to add a triangle to the triangle strip. This assumes that at least one shared edge of this triangle already exits in the strip
auto AddTriangleToStrip = [&HostMesh, &TriangleStrip](const int32 HostTID)
{
FIndex3i TriVIDs = HostMesh.GetTriangle(HostTID);
// two of the tri vertices are already in the buffer. Identify the new one.
int32 IndexOf = -1;
for (int32 i = 0; i < 3; ++i)
{
const int32 VID = TriVIDs[i];
const int32* StripVID = TriangleStrip.ToStripIndexMap.Find(VID);
if (!StripVID)
{
IndexOf = i;
break;
}
}
// no need to do anything if all vertices had previously been added,
if (IndexOf == -1)
{
return;
}
const FVector3d Verts[3] = { HostMesh.GetVertex(TriVIDs[0]), HostMesh.GetVertex(TriVIDs[1]), HostMesh.GetVertex(TriVIDs[2]) };
// with new vert last (i.e. V2).
const FIndex3i Reordered((IndexOf + 1) % 3, (IndexOf + 2) % 3, IndexOf);
// the spanning vectors
const FVector3d E1 = Verts[Reordered[1]] - Verts[Reordered[0]];
const FVector3d E2 = Verts[Reordered[2]] - Verts[Reordered[0]];
// coordinates of V2 relative to the direction of E1, and its orthogonal complement.
const double E1DotE2 = E2.Dot(E1);
const double E1LengthSqr = FMath::Max(E1.SizeSquared(), TMathUtilConstants<double>::ZeroTolerance);
const double E1Length = FMath::Sqrt(E1LengthSqr);
const double E1Dist = E2.Dot(E1) / E1Length;
const double OrthE1DistSqr = FMath::Max(0., E2.SizeSquared() - E1DotE2 * E1DotE2 / E1LengthSqr);
const double OrthE1Dist = FMath::Sqrt(OrthE1DistSqr);
// 2d version of E1
const int32* StripTriV0 = TriangleStrip.ToStripIndexMap.Find(TriVIDs[Reordered[0]]);
const int32* StripTriV1 = TriangleStrip.ToStripIndexMap.Find(TriVIDs[Reordered[1]]);
checkSlow(StripTriV0); checkSlow(StripTriV1);
const FVector2d& StripTriVert0 = TriangleStrip.StripVertexBuffer[*StripTriV0];
const FVector2d& StripTriVert1 = TriangleStrip.StripVertexBuffer[*StripTriV1];
const FVector2d StripE1 = (StripTriVert1 - StripTriVert0);
const FVector2d StripE1Perp(-StripE1[1], StripE1[0]); // Rotate StripE1 90 CCW.
// new vertex 2d position
const FVector2d StripTriVert2 = StripTriVert0 + (StripE1)*E1DotE2 / E1LengthSqr + (StripE1Perp / E1Length) * OrthE1Dist;
TriangleStrip.StripVertexBuffer.Add(StripTriVert2);
const int32 StripVID = TriangleStrip.StripVertexBuffer.Num() - 1;
const int32 SurfaceVID = TriVIDs[IndexOf];
TriangleStrip.ToStripIndexMap.Add(SurfaceVID, StripVID);
};
// find host triangle that contains both the StartVID and the first host edge we cross.
const int32 FirstHostTID = [&]
{
if (IsVertexPoint(StartSurfacePoint))
{
const int32 HostEID = HostEdgesCrossed[0];
const FIndex2i HostEdgeT = HostMesh.GetEdgeT(HostEID);
const FIndex3i TriAVIDs = HostMesh.GetTriangle(HostEdgeT.A);
const FIndex3i TriBVIDs = HostMesh.GetTriangle(HostEdgeT.B);
const int32 HostStartVID = StartSurfacePoint.Position.VertexPosition.VID;
const int32 HostTID = (TriAVIDs.IndexOf(HostStartVID) != -1) ? HostEdgeT.A : HostEdgeT.B;
checkSlow(HostMesh.GetTriangle(HostTID).IndexOf(HostStartVID) != -1);
return HostTID;
}
else if (IsEdgePoint(StartSurfacePoint))
{
const int32 StartEID = StartSurfacePoint.Position.EdgePosition.EdgeID;
const int32 ExitEID = HostEdgesCrossed[0];
const FIndex2i AdjTris = HostMesh.GetEdgeT(StartEID);
const int32 NumAdj = (AdjTris[1] == -1) ? 1 : 2;
for (int i = 0; i < NumAdj; ++i)
{
int32 TID = AdjTris[i];
if (HostMesh.GetTriEdges(TID).IndexOf(ExitEID) != -1)
{
return TID;
}
}
return -1;
}
else
{
checkSlow(IsFacePoint(StartSurfacePoint));
return StartSurfacePoint.Position.TriPosition.TriID;
}
}();
// jump-start the process of making the triangle strip by adding the first 2 verts
// that define the edge prior (in the ccw sense) to the first edge crossing.
{
const FIndex3i TriVIDs = HostMesh.GetTriangle(FirstHostTID);
const FIndex3i TriEIDs = HostMesh.GetTriEdges(FirstHostTID);
const int32 FirstEIDXed = HostEdgesCrossed[0];
const int32 IndexOfe = TriEIDs.IndexOf(FirstEIDXed);
const int32 FirstVID = TriVIDs[(IndexOfe + 2)%3];
const int32 SecondVID = TriVIDs[(IndexOfe)];
const FVector3d Vert0 = HostMesh.GetVertex(FirstVID);
const FVector3d Vert1 = HostMesh.GetVertex(SecondVID);
const double LSqr = FMath::Max((Vert0 - Vert1).SizeSquared(), TMathUtilConstants<double>::ZeroTolerance);
const double L = FMath::Sqrt(LSqr);
const FVector2d StripVert0(0., 0.);
const FVector2d StripVert1(L, 0.);
TriangleStrip.StripVertexBuffer.Add(StripVert0);
TriangleStrip.ToStripIndexMap.Add(FirstVID, 0);
TriangleStrip.StripVertexBuffer.Add(StripVert1);
TriangleStrip.ToStripIndexMap.Add(SecondVID, 1);
}
// unfold the triangle strip, add the first triangle and then all subsequent ones
AddTriangleToStrip(FirstHostTID);
{
int32 LastHostTID = FirstHostTID;
for (int32 HostEID : HostEdgesCrossed)
{
const FIndex2i EdgeT = HostMesh.GetEdgeT(HostEID);
LastHostTID = (EdgeT.A == LastHostTID) ? EdgeT.B : EdgeT.A;
AddTriangleToStrip(LastHostTID);
}
}
// translate a trace point on the surface of the host mesh, to Fvector2d relative to the 2d triangle strip
auto To2dStripPosition = [&](const FSurfacePoint& TraceSurfacePoint)->FVector2d
{
if (IsVertexPoint(TraceSurfacePoint))
{
// end point is a vertex in the host mesh
const auto& HostVertexPosition = TraceSurfacePoint.Position.VertexPosition;
const int32 HostVID = HostVertexPosition.VID;
const int32* StripVID = TriangleStrip.ToStripIndexMap.Find(HostVID);
checkSlow(StripVID);
return TriangleStrip.StripVertexBuffer[*StripVID];
}
if (IsEdgePoint(TraceSurfacePoint))
{
// end point lies on an edge of the host mesh. identify the edge vertices in the 2d-triangle strip
// and reconstruct the point from alpha
const auto& HostEdgePosition = TraceSurfacePoint.Position.EdgePosition;
const double Alpha = HostEdgePosition.Alpha;
const FIndex2i HostEdgeV = HostMesh.GetEdgeV(HostEdgePosition.EdgeID);
const FIndex2i StripEdgeV( *TriangleStrip.ToStripIndexMap.Find(HostEdgeV.A),
*TriangleStrip.ToStripIndexMap.Find(HostEdgeV.B) );
const FVector2d StripEdgePos[2] = { TriangleStrip.StripVertexBuffer[StripEdgeV.A],
TriangleStrip.StripVertexBuffer[StripEdgeV.B] };
return Alpha * StripEdgePos[0] + (1. - Alpha) * StripEdgePos[1];
}
else
{
// end point lies in the face of the last triangle. identify the tri vertices in the 2d-triangle strip and
// reconstruct the point from the barycentric coordinates.
checkSlow(IsFacePoint(TraceSurfacePoint));
const auto& HostTriPosition = TraceSurfacePoint.Position.TriPosition;
const FVector3d Barycentric = HostTriPosition.BarycentricCoords;
const FIndex3i HostTri = HostMesh.GetTriangle(HostTriPosition.TriID);
const FIndex3i StripTri( *TriangleStrip.ToStripIndexMap.Find(HostTri[0]),
*TriangleStrip.ToStripIndexMap.Find(HostTri[1]),
*TriangleStrip.ToStripIndexMap.Find(HostTri[2]) );
const FVector2d StripTriPos[3] = { TriangleStrip.StripVertexBuffer[StripTri[0]],
TriangleStrip.StripVertexBuffer[StripTri[1]],
TriangleStrip.StripVertexBuffer[StripTri[2]] };
return Barycentric[0] * StripTriPos[0] + Barycentric[1] * StripTriPos[1] + Barycentric[2] * StripTriPos[2];
}
};
// translate the end of the trace edge to a position in the unfolded triangle strip
const FVector2d TraceStartVertex = To2dStripPosition(StartSurfacePoint);
const FVector2d TraceEndVertex = To2dStripPosition(EndSurfacePoint);
const FVector2d TraceVector = TraceEndVertex - TraceStartVertex;
// loop over the two-d versions of the host edges the path crosses and find the intersection.
for (int32 HostEID : HostEdgesCrossed)
{
const FIndex2i EdgeV = HostMesh.GetEdgeV(HostEID);
const int32* StripA = TriangleStrip.ToStripIndexMap.Find(EdgeV.A);
const int32* StripB = TriangleStrip.ToStripIndexMap.Find(EdgeV.B);
checkSlow(StripA); checkSlow(StripB);
const FIndex2i StripEdgeV(*StripA, *StripB);
const FVector2d StripVertA = TriangleStrip.StripVertexBuffer[StripEdgeV.A];
const FVector2d StripVertB = TriangleStrip.StripVertexBuffer[StripEdgeV.B];
// solve Alpha*StripVertA + (1-Alpha)StripVertB = Gamma Start + (1-Gamma)End.
// i.e.
// Alpha * (StripVertA - StripVertB) + Gamma * (End - Start) = End - StripVertB.
// here End - Start = TraceVector
// write this as 2x2 matrix problem M.x = b solving for unknown vector x=(alpha, gamma).
// matrix
double m[2][2];
m[0][0] = (StripVertA - StripVertB)[0]; m[0][1] = TraceVector[0];
m[1][0] = (StripVertA - StripVertB)[1]; m[1][1] = TraceVector[1];
// b-vector
const FVector2d b = TraceEndVertex - StripVertB;
const double Det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
// inverse: not yet scaled by 1/det
double invm[2][2];
invm[0][0] = m[1][1]; invm[0][1] = -m[0][1];
invm[1][0] = -m[1][0]; invm[1][1] = m[0][0];
// solve for alpha only ( don't care about gamma )
double alpha = invm[0][0] * b[0] + invm[0][1] * b[1];
if (FMath::Abs(Det) < TMathUtilConstants<double>::ZeroTolerance)
{
// this surface edge and the intrinsic edge that intersects it are nearly parallel.
// Assume the crossing happens at the farther vertex.
const double dA = StripVertA.SquaredLength();
const double dB = StripVertB.SquaredLength();
alpha = (dA > dB) ? 1. : 0.;
}
else
{
alpha = FMath::Clamp(alpha / Det, 0., 1.);
}
// may want to convert this edge crossing to a vertex crossing, if it is close enough.
int32 CoalesceVID = -1;
bool bSnapToVert = false;
if (alpha < CoalesceThreshold)
{
CoalesceVID = EdgeV.B;
bSnapToVert = true;
}
else if ((1. - alpha) < CoalesceThreshold)
{
CoalesceVID = EdgeV.A;
bSnapToVert = true;
}
if (bSnapToVert)
{
AddVIDToTrace(CoalesceVID);
}
else
{
EdgeTrace.Emplace(HostEID, alpha);
}
}
}
/**
* Utility to trace an edge defined (by TraceEID) on the TraceMesh across the HostMesh, given a list of host mesh edges intersected by the trace mesh.
* this assumes the host mesh and trace mesh come from an intrinsic mesh, surface mesh pair.
*/
template<typename HostMeshType, typename TraceMeshType, typename TraceVIDToSurfacePointFtor>
TArray<FSurfacePoint> TraceEdgeOverHost( const int32 TraceEID, const TArray<int32>& HostEdgesCrossed, const HostMeshType& HostMesh,
const TraceMeshType& TraceMesh, double CoalesceThreshold, bool bReverse,
const TraceVIDToSurfacePointFtor& VIDToSurfacePoint)
{
TArray<FSurfacePoint> EdgeTrace;
if (!TraceMesh.IsEdge(TraceEID))
{
// empty array..
return MoveTemp(EdgeTrace);
}
EdgeTrace.Reserve(HostEdgesCrossed.Num()+2);
// find start (.A) and end (.B) trace mesh vids for this edge
const FIndex2i OrderedTraceEdgeV = [&]
{
// by convention the intrinsic edge direction will be defined by the first adj triangle
const int32 AdjTID = TraceMesh.GetEdgeT(TraceEID).A;
const int32 IndexOf = TraceMesh.GetTriEdges(AdjTID).IndexOf(TraceEID);
const FIndex3i TriVIDs = TraceMesh.GetTriangle(AdjTID);
return FIndex2i( TriVIDs[IndexOf], TriVIDs[(IndexOf + 1) % 3]);
}();
// translate to points on the surface of the host mesh
const FSurfacePoint TraceStartSurfacePoint = VIDToSurfacePoint(OrderedTraceEdgeV.A);
const FSurfacePoint TraceEndSurfacePoint = VIDToSurfacePoint(OrderedTraceEdgeV.B);
// add the first point in the trace.
EdgeTrace.Add(TraceStartSurfacePoint);
// compute (and add) the crossing locations relative to the edges of the host mesh
ConvertEdgesCrossed(TraceStartSurfacePoint, TraceEndSurfacePoint, HostEdgesCrossed, HostMesh, TraceMesh, CoalesceThreshold, VIDToSurfacePoint, EdgeTrace);
// maybe add end point, if it doesn't duplicate the last one already
{
const FSurfacePoint& LastPoint = EdgeTrace.Last();
const bool bSameAsLast = IsVertexPoint(TraceEndSurfacePoint)
&& IsVertexPoint(LastPoint)
&& (LastPoint.Position.VertexPosition.VID == TraceEndSurfacePoint.Position.VertexPosition.VID);
if (!bSameAsLast)
{
EdgeTrace.Add(TraceEndSurfacePoint);
}
}
// correct trace results order so it is either EdgeV order or reversed as requested
const bool bHasEdgeVOrder = (TraceMesh.GetEdgeV(TraceEID).A == OrderedTraceEdgeV.A);
const bool bNeedToReverse = (bHasEdgeVOrder && bReverse) || (!bHasEdgeVOrder && !bReverse);
if (bNeedToReverse)
{
Algo::Reverse(EdgeTrace);
}
return MoveTemp(EdgeTrace);
}
};
namespace FNormalCoordIntrinsicTraceImpl
{
using namespace IntrinsicCorrespondenceUtils;
template <typename IntrinsicMeshType>
TArray<FSurfacePoint> TraceEdge(const IntrinsicMeshType& IntrinsicMesh, int32 IntrinsicEID, double CoalesceThreshold, bool bReverse)
{
using FEdgeAndCrossingIdx = FNormalCoordinates::FEdgeAndCrossingIdx;
TArray<FSurfacePoint> Result;
if (!IntrinsicMesh.IsEdge(IntrinsicEID))
{
return MoveTemp(Result);
}
const FNormalCoordinates& NormalCoordinates = IntrinsicMesh.GetNormalCoordinates();
TArray<int32> SurfXings;
const int32 NumSurfXings = NormalCoordinates.NumEdgeCrossing(IntrinsicEID);
if (NumSurfXings > 0)
{
SurfXings.Reserve(NumSurfXings);
}
const FDynamicMesh3& SurfaceMesh = *IntrinsicMesh.GetExtrinsicMesh();
const FIndex2i IntrinsicEdgeT = IntrinsicMesh.GetEdgeT(IntrinsicEID);
// need to create a list of surface mesh edges that cross this edge. To do this we need to follow
// each curve that crosses this intrinsic edge to the vertices where it terminates ( thus identifying the surface edge )
for (int32 p = 1; p < NumSurfXings + 1; ++p)
{
const FIndex2i SurfaceEdgeV = [&]
{
TArray<FEdgeAndCrossingIdx> Crossings;
FIndex2i Verts;
// follow surface curve (i.e. surface edge) forward to the end and identify the vertex
{
Crossings.Add(FEdgeAndCrossingIdx({IntrinsicEdgeT.A, IntrinsicEID, p}));
FNormalCoordSurfaceTraceImpl::ContinueTraceSurfaceEdgeAcrossFaces(IntrinsicMesh, Crossings);
const FEdgeAndCrossingIdx& LastXing = Crossings.Last();
const FIndex3i TriEIDs = IntrinsicMesh.GetTriEdges(LastXing.TID);
Verts.A = IntrinsicMesh.GetTriangle(LastXing.TID)[LastXing.EID];
}
Crossings.Reset();
// follow surface curve (i.e. surface edge) backward to other end and identify the vertex
{
Crossings.Add(FEdgeAndCrossingIdx({IntrinsicEdgeT.B, IntrinsicEID, NumSurfXings + 1 - p}));
FNormalCoordSurfaceTraceImpl::ContinueTraceSurfaceEdgeAcrossFaces(IntrinsicMesh, Crossings);
const FEdgeAndCrossingIdx& LastXing = Crossings.Last();
const FIndex3i TriEIDs = IntrinsicMesh.GetTriEdges(LastXing.TID);
Verts.B = IntrinsicMesh.GetTriangle(LastXing.TID)[LastXing.EID];
}
return Verts;
}();
// identify the surface edge from its endpoints.
const int32 SurfaceEID = SurfaceMesh.FindEdge(SurfaceEdgeV.A, SurfaceEdgeV.B);
checkSlow(SurfaceEID != IndexConstants::InvalidID);
SurfXings.Add(SurfaceEID);
}
// do the actual trace - this identifies the surface mesh triangles crossed by the intrinsic edge and unfolds them into a triangle strip where the trace is performed
Result = FNormalCoordSurfaceTraceImpl::TraceEdgeOverHost(IntrinsicEID, SurfXings, SurfaceMesh, IntrinsicMesh, CoalesceThreshold, bReverse,
[&IntrinsicMesh](int32 VID) { return IntrinsicMesh.GetVertexSurfacePoint(VID); } );
return MoveTemp(Result);
}
}
int32 UE::Geometry::FlipToDelaunay(FIntrinsicTriangulation& IntrinsicMesh, TSet<int32>& Uncorrected, const int32 MaxFlipCount)
{
return FlipToDelaunayImpl(IntrinsicMesh, Uncorrected, MaxFlipCount);
}
int32 UE::Geometry::FlipToDelaunay(FSimpleIntrinsicEdgeFlipMesh& IntrinsicMesh, TSet<int32>& Uncorrected, const int32 MaxFlipCount)
{
return FlipToDelaunayImpl(IntrinsicMesh, Uncorrected, MaxFlipCount);
}
int32 UE::Geometry::FlipToDelaunay(FSimpleIntrinsicMesh& IntrinsicMesh, TSet<int32>& Uncorrected, const int32 MaxFlipCount)
{
return FlipToDelaunayImpl(IntrinsicMesh, Uncorrected, MaxFlipCount);
}
int32 UE::Geometry::FlipToDelaunay(FIntrinsicMesh& IntrinsicMesh, TSet<int32>& Uncorrected, const int32 MaxFlipCount)
{
return FlipToDelaunayImpl(IntrinsicMesh, Uncorrected, MaxFlipCount);
}
int32 UE::Geometry::FlipToDelaunay(FIntrinsicEdgeFlipMesh& IntrinsicMesh, TSet<int32>& Uncorrected, const int32 MaxFlipCount)
{
return FlipToDelaunayImpl(IntrinsicMesh, Uncorrected, MaxFlipCount);
}
/**------------------------------------------------------------------------------
* FSimpleIntrinsicEdgeFlipMesh Methods
*------------------------------------------------------------------------------ */
FSimpleIntrinsicEdgeFlipMesh::FSimpleIntrinsicEdgeFlipMesh(const FDynamicMesh3& SrcMesh)
{
Reset(SrcMesh);
}
void FSimpleIntrinsicEdgeFlipMesh::Clear()
{
Vertices.Clear();
VertexRefCounts.Clear();
VertexEdgeLists.Reset();
Triangles.Clear();
TriangleRefCounts.Clear();
TriangleEdges.Clear();
Edges.Clear();
EdgeRefCounts.Clear();
EdgeLengths.Clear();
InternalAngles.Clear();
}
void FSimpleIntrinsicEdgeFlipMesh::Reset(const FDynamicMesh3& SrcMesh)
{
Clear();
Vertices = SrcMesh.GetVerticesBuffer();
VertexRefCounts = SrcMesh.GetVerticesRefCounts();
VertexEdgeLists = SrcMesh.GetVertexEdges();
Triangles = SrcMesh.GetTrianglesBuffer();
TriangleRefCounts = SrcMesh.GetTrianglesRefCounts();
TriangleEdges = SrcMesh.GetTriangleEdges();
Edges = SrcMesh.GetEdgesBuffer();
EdgeRefCounts = SrcMesh.GetEdgesRefCounts();
const int32 MaxEID = MaxEdgeID();
EdgeLengths.SetNum(MaxEID);
for (int32 EID = 0; EID < MaxEID; ++EID)
{
if (!IsEdge(EID))
{
continue;
}
const FIndex2i EdgeV = GetEdgeV(EID);
const FVector3d Pos[2] = { GetVertex(EdgeV.A), GetVertex(EdgeV.B) };
EdgeLengths[EID] = (Pos[1] - Pos[0]).Length();
}
const int32 MaxTriID = MaxTriangleID();
InternalAngles.SetNum(MaxTriID);
for (int32 TID = 0; TID < MaxTriID; ++TID)
{
if (!IsTriangle(TID))
{
continue;
}
// angles at v0, v1, v2, in that order
InternalAngles[TID] = ComputeTriInternalAnglesR(TID);
}
}
FIndex2i FSimpleIntrinsicEdgeFlipMesh::GetEdgeOpposingV(int32 EID) const
{
const FEdge& Edge = Edges[EID];
FIndex2i Result(InvalidID, InvalidID);
for (int32 i = 0; i < 2; ++i)
{
int32 TriID = Edge.Tri[i];
if (TriID == InvalidID) continue;
const FIndex3i TriEIDs = GetTriEdges(TriID);
const int32 IndexOf = TriEIDs.IndexOf(EID);
const FIndex3i TriVIDs = GetTriangle(TriID);
Result[i] = TriVIDs[AddTwoModThree[IndexOf]];
}
return Result;
}
FIndex2i FSimpleIntrinsicEdgeFlipMesh::GetOrientedEdgeV(int32 EID, int32 TID) const
{
int32 IndexOf = GetTriEdges(TID).IndexOf(EID);
checkSlow(IndexOf != InvalidID);
FIndex3i TriVIDs = GetTriangle(TID);
return FIndex2i(TriVIDs[IndexOf], TriVIDs[AddOneModThree[IndexOf]]);
}
int32 FSimpleIntrinsicEdgeFlipMesh::ReplaceEdgeTriangle(int32 eID, int32 tOld, int32 tNew)
{
FIndex2i& Tris = Edges[eID].Tri;
int32 a = Tris[0], b = Tris[1];
if (a == tOld) {
if (tNew == InvalidID)
{
Tris[0] = b;
Tris[1] = InvalidID;
}
else
{
Tris[0] = tNew;
}
return 0;
}
else if (b == tOld)
{
Tris[1] = tNew;
return 1;
}
else
{
return -1;
}
}
EMeshResult FSimpleIntrinsicEdgeFlipMesh::FlipEdgeTopology(int32 eab, FEdgeFlipInfo& FlipInfo)
{
if (!IsEdge(eab))
{
return EMeshResult::Failed_NotAnEdge;
}
if (IsBoundaryEdge(eab))
{
return EMeshResult::Failed_IsBoundaryEdge;
}
// find oriented edge [a,b], tris t0,t1, and other verts c in t0, d in t1
const FEdge Edge = Edges[eab];
int32 t0 = Edge.Tri[0], t1 = Edge.Tri[1];
FIndex2i oppV = GetEdgeOpposingV(eab);
FIndex2i orientedV = GetOrientedEdgeV(eab, t0);
int32 a = orientedV.A, b = orientedV.B;
if (oppV[0] == InvalidID || oppV[1] == InvalidID)
{
return EMeshResult::Failed_BrokenTopology;
}
int32 c = oppV[0];
int32 d = oppV[1];
const FIndex3i T0te = GetTriEdges(t0);
const FIndex3i T1te = GetTriEdges(t1);
const int32 T0IndexOf = T0te.IndexOf(eab);
const int32 T1IndexOf = T1te.IndexOf(eab);
// find edges bc, ca, ad, db
const int32 ebc = T0te[AddOneModThree[T0IndexOf]];
const int32 eca = T0te[AddTwoModThree[T0IndexOf]];
int32 ead = T1te[AddOneModThree[T1IndexOf]];
int32 edb = T1te[AddTwoModThree[T1IndexOf]];
if (!FlipInfo.bHadSameOrientations)
{
Swap(ead,edb);
}
// update triangles
Triangles[t0] = FIndex3i(c, d, b);
Triangles[t1] = FIndex3i(d, c, a);
// update edge AB, which becomes flipped edge CD
SetEdgeVerticesInternal(eab, c, d); // Edge.Vert = (c, d) or (d, c)
SetEdgeTrianglesInternal(eab, t0, t1); // Edge.Tri = (t0, t1)
const int32 ecd = eab;
// update the two other edges whose triangle nbrs have changed
if (ReplaceEdgeTriangle(eca, t0, t1) == -1)
{
checkfSlow(false, TEXT("FSimpleIntrinsicEdgeFlipMesh.FlipEdge: first ReplaceEdgeTriangle failed"));
return EMeshResult::Failed_UnrecoverableError;
}
if (ReplaceEdgeTriangle(edb, t1, t0) == -1)
{
checkfSlow(false, TEXT("FSimpleIntrinsicEdgeFlipMesh.FlipEdge: second ReplaceEdgeTriangle failed"));
return EMeshResult::Failed_UnrecoverableError;
}
// update triangle nbr lists (these are edges)
TriangleEdges[t0] = FIndex3i(ecd, edb, ebc);
TriangleEdges[t1] = FIndex3i(ecd, eca, ead);
// remove old eab from verts a and b, and Decrement ref counts
if (VertexEdgeLists.Remove(a, eab) == false)
{
checkfSlow(false, TEXT("FSimpleIntrinsicEdgeFlipMesh.FlipEdge: first edge list remove failed"));
return EMeshResult::Failed_UnrecoverableError;
}
VertexRefCounts.Decrement(a);
if (a != b)
{
if (VertexEdgeLists.Remove(b, eab) == false)
{
checkfSlow(false, TEXT("FSimpleIntrinsicEdgeFlipMesh.FlipEdge: second edge list remove failed"));
return EMeshResult::Failed_UnrecoverableError;
}
VertexRefCounts.Decrement(b);
}
if (IsVertex(a) == false || IsVertex(b) == false)
{
checkfSlow(false, TEXT("FSimpleIntrinsicEdgeFlipMesh.FlipEdge: either a or b is not a vertex?"));
return EMeshResult::Failed_UnrecoverableError;
}
// add edge ecd to verts c and d, and increment ref counts
VertexEdgeLists.Insert(c, ecd);
VertexRefCounts.Increment(c);
if (c != d)
{
VertexEdgeLists.Insert(d, ecd);
VertexRefCounts.Increment(d);
}
// success! collect up results
FlipInfo.EdgeID = eab;
FlipInfo.OriginalVerts = FIndex2i(a, b);
FlipInfo.OpposingVerts = FIndex2i(c, d);
FlipInfo.Triangles = FIndex2i(t0, t1);
return EMeshResult::Ok;
}
FVector3d FSimpleIntrinsicEdgeFlipMesh::ComputeTriInternalAnglesR(const int32 TID) const
{
const FVector3d Lengths = GetTriEdgeLengths(TID);
FVector3d Angles;
for (int32 v = 0; v < 3; ++v)
{
Angles[v] = InteriorAngle(Lengths[v], Lengths[AddOneModThree[v]], Lengths[AddTwoModThree[v]]);
}
return Angles;
}
double FSimpleIntrinsicEdgeFlipMesh::GetOpposingVerticesDistance(int32 EID) const
{
const bool bHasSameOrientation = AreSameOrientation(EID);
const double OrgLength = GetEdgeLength(EID);
const FIndex2i EdgeTris = GetEdgeT(EID);
checkSlow(EdgeTris[1] != FDynamicMesh3::InvalidID);
// compute 2d locations of the verts opposite the EID edge.
FVector2d Opp2dVerts[2];
for (int32 i = 0; i < 2; ++i)
{
const FIndex3i TriEIDs = GetTriEdges(EdgeTris[i]);
const FVector3d Ls = GetEdgeLengthTriple(TriEIDs);
const int32 IndexOf = TriEIDs.IndexOf(EID);
const FVector3d PermutedLs = Permute(IndexOf, Ls);
Opp2dVerts[i] = ComputeOpposingVert2d(PermutedLs[0], PermutedLs[1], PermutedLs[2]);
}
// rotate second tri so the shared edge aligns
Opp2dVerts[1].Y = -Opp2dVerts[1].Y;
if (bHasSameOrientation)
{
Opp2dVerts[1].X = OrgLength - Opp2dVerts[1].X;
}
return (Opp2dVerts[0] - Opp2dVerts[1]).Length();
}
EMeshResult FSimpleIntrinsicEdgeFlipMesh::FlipEdge(int32 EID, FEdgeFlipInfo& EdgeFlipInfo)
{
if (!IsEdge(EID))
{
return EMeshResult::Failed_NotAnEdge;
}
if (IsBoundaryEdge(EID))
{
return EMeshResult::Failed_IsBoundaryEdge;
}
const bool bHasSameOrientation = AreSameOrientation(EID);
EdgeFlipInfo.bHadSameOrientations = bHasSameOrientation;
// prohibit case where the edge is shared by a non-convex pair of triangles.
{
// original triangles
FDynamicMesh3::FEdge OrgEdge = GetEdge(EID);
// total interior angles at the edge vertices as ordered by the first triangle.
double TotalAngleAtOrgVert[2] = { 0., 0. };
{
// accumulate interior angles at edge vertices in first triangle.
{
const int32 TriID = OrgEdge.Tri[0];
const int32 IndexOf = GetTriEdges(TriID).IndexOf(EID);
TotalAngleAtOrgVert[0] += InternalAngles[TriID][IndexOf];
TotalAngleAtOrgVert[1] += InternalAngles[TriID][AddOneModThree[IndexOf]];
}
// accumulate interior angles at the edge vertices in the second triangle
{
const int32 TriID = OrgEdge.Tri[1];
const int32 IndexOf = GetTriEdges(TriID).IndexOf(EID);
if (bHasSameOrientation)
{
TotalAngleAtOrgVert[1] += InternalAngles[TriID][IndexOf];
TotalAngleAtOrgVert[0] += InternalAngles[TriID][AddOneModThree[IndexOf]];
}
else
{
TotalAngleAtOrgVert[0] += InternalAngles[TriID][IndexOf];
TotalAngleAtOrgVert[1] += InternalAngles[TriID][AddOneModThree[IndexOf]];
}
}
}
if (TotalAngleAtOrgVert[0] > TMathUtilConstants<double>::Pi - TMathUtilConstants<double>::ZeroTolerance || TotalAngleAtOrgVert[1] > TMathUtilConstants<double>::Pi - TMathUtilConstants<double>::ZeroTolerance)
{
return EMeshResult::Failed_Unsupported;
}
}
// prohibit case where one of the ends of the edge has degree one (this looks like a triangle rolled into a cone ).
{
FIndex2i EdgeT = GetEdgeT(EID);
if (EdgeT.A == EdgeT.B)
{
return EMeshResult::Failed_Unsupported;
}
}
// compute the length of edge after flip
const double PostFlipLength = GetOpposingVerticesDistance(EID);
// flip edge in the underlying mesh
const EMeshResult MeshFlipResult = FlipEdgeTopology(EID, EdgeFlipInfo);
if (MeshFlipResult != EMeshResult::Ok)
{
// could fail for many reasons, e.g. a boundary edge or the new edge already exists.
return MeshFlipResult;
}
// update the intrinsic edge length
EdgeLengths[EID] = PostFlipLength;
// update interior angles for the tris
for (int32 t = 0; t < 2; ++t)
{
const int32 TID = EdgeFlipInfo.Triangles[t];
InternalAngles[TID] = ComputeTriInternalAnglesR(TID);
}
return MeshFlipResult;
}
FVector2d FSimpleIntrinsicEdgeFlipMesh::EdgeOpposingAngles(int32 EID) const
{
FVector2d Result;
FDynamicMesh3::FEdge Edge = GetEdge(EID);
{
const int32 IndexOf = GetTriEdges(Edge.Tri.A).IndexOf(EID);
const int32 IndexOfOpp = AddTwoModThree[IndexOf];
const double Angle = GetTriInternalAngleR(Edge.Tri.A, IndexOfOpp);
Result[0] = Angle;
}
if (Edge.Tri.B != FDynamicMesh3::InvalidID)
{
const int32 IndexOf = GetTriEdges(Edge.Tri.B).IndexOf(EID);
const int32 IndexOfOpp = AddTwoModThree[IndexOf];
const double Angle = GetTriInternalAngleR(Edge.Tri.B, IndexOfOpp);
Result[1] = Angle;
}
else
{
Result[1] = -TMathUtilConstants<double>::MaxReal;
}
return Result;
}
double FSimpleIntrinsicEdgeFlipMesh::EdgeCotanWeight(int32 EID) const
{
auto ComputeCotanOppAngle = [this](int32 EID, int32 TID)->double
{
const FIndex3i TriEIDs = GetTriEdges(TID);
const int32 IOf = TriEIDs.IndexOf(EID);
const FVector3d TriLs = GetEdgeLengthTriple(TriEIDs);
const FVector3d Ls(TriLs[IOf], TriLs[AddOneModThree[IOf]], TriLs[AddTwoModThree[IOf]]);
return ComputeCotangent(Ls[0], Ls[1], Ls[2]);
};
FDynamicMesh3::FEdge Edge = GetEdge(EID);
double Result = ComputeCotanOppAngle(EID, Edge.Tri.A);
if (Edge.Tri.B != FDynamicMesh3::InvalidID)
{
Result += ComputeCotanOppAngle(EID, Edge.Tri.B);
Result /= 2;
}
return Result;
}
/**------------------------------------------------------------------------------
* FSimpleIntrinsicMesh Methods
*-------------------------------------------------------------------------------*/
UE::Geometry::EMeshResult UE::Geometry::FSimpleIntrinsicMesh::PokeTriangleTopology(int32 TriangleID, FPokeTriangleInfo& PokeResult)
{
PokeResult = FPokeTriangleInfo();
if (!IsTriangle(TriangleID))
{
return EMeshResult::Failed_NotATriangle;
}
FIndex3i tv = GetTriangle(TriangleID);
FIndex3i te = GetTriEdges(TriangleID);
// create vertex with averaged position..
FVector3d vPos = (1. / 3.) * (GetVertex(tv[0]) + GetVertex(tv[1]) + GetVertex(tv[2]));
int32 newVertID = AppendVertex(vPos);
// add in edges to center vtx, do not connect to triangles yet
int32 eAN = AddEdgeInternal(tv[0], newVertID, -1, -1);
int32 eBN = AddEdgeInternal(tv[1], newVertID, -1, -1);
int32 eCN = AddEdgeInternal(tv[2], newVertID, -1, -1);
VertexRefCounts.Increment(tv[0]);
VertexRefCounts.Increment(tv[1]);
VertexRefCounts.Increment(tv[2]);
VertexRefCounts.Increment(newVertID, 3);
// old triangle becomes tri along first edge
Triangles[TriangleID] = FIndex3i(tv[0], tv[1], newVertID);
TriangleEdges[TriangleID] = FIndex3i(te[0], eBN, eAN);
// add two triangles
int32 t1 = AddTriangleInternal(tv[1], tv[2], newVertID, te[1], eCN, eBN);
int32 t2 = AddTriangleInternal(tv[2], tv[0], newVertID, te[2], eAN, eCN);
// second and third edges of original tri have neighbors
ReplaceEdgeTriangle(te[1], TriangleID, t1);
ReplaceEdgeTriangle(te[2], TriangleID, t2);
// set the triangles for the edges we created above
SetEdgeTrianglesInternal(eAN, TriangleID, t2);
SetEdgeTrianglesInternal(eBN, TriangleID, t1);
SetEdgeTrianglesInternal(eCN, t1, t2);
PokeResult.OriginalTriangle = TriangleID;
PokeResult.TriVertices = tv;
PokeResult.NewVertex = newVertID;
PokeResult.NewTriangles = FIndex2i(t1, t2);
PokeResult.NewEdges = FIndex3i(eAN, eBN, eCN);
PokeResult.BaryCoords = FVector3d(1. / 3., 1. / 3., 1. / 3.);
return EMeshResult::Ok;
}
UE::Geometry::EMeshResult UE::Geometry::FSimpleIntrinsicMesh::SplitEdgeTopology(int32 eab, FEdgeSplitInfo& SplitInfo)
{
SplitInfo = FEdgeSplitInfo();
if (!IsEdge(eab))
{
return EMeshResult::Failed_NotAnEdge;
}
// look up primary edge & triangle
const FEdge Edge = Edges[eab];
const int32 t0 = Edge.Tri[0];
if (t0 == InvalidID)
{
return EMeshResult::Failed_BrokenTopology;
}
const FIndex3i T0tv = GetTriangle(t0);
const FIndex3i T0te = GetTriEdges(t0);
const int32 IndexOfe = T0te.IndexOf(eab);
const int32 a = T0tv[IndexOfe];
const int32 b = T0tv[AddOneModThree[IndexOfe]];
const int32 c = T0tv[AddTwoModThree[IndexOfe]];
// look up edge bc, which needs to be modified
const int32 ebc = T0te[AddOneModThree[IndexOfe]];
// RefCount overflow check. Conservatively leave room for
// extra increments from other operations.
if (VertexRefCounts.GetRawRefCount(c) > FRefCountVector::INVALID_REF_COUNT - 3)
{
return EMeshResult::Failed_HitValenceLimit;
}
SplitInfo.OriginalEdge = eab;
SplitInfo.OriginalVertices = FIndex2i(a, b); // this is the oriented a,b
SplitInfo.OriginalTriangles = FIndex2i(t0, InvalidID);
SplitInfo.SplitT = 0.5;
// quite a bit of code is duplicated between boundary and non-boundary case, but it
// is too hard to follow later if we factor it out...
if (IsBoundaryEdge(eab))
{
// create vertex
const FVector3d vNew = 0.5 * (GetVertex(a) + GetVertex(b));
const int32 f = AppendVertex(vNew);
// rewrite existing triangle
Triangles[t0][AddOneModThree[IndexOfe]] = f;
// add second triangle
const int32 t2 = AddTriangleInternal(f, b, c, InvalidID, InvalidID, InvalidID);
// rewrite edge bc, create edge af
ReplaceEdgeTriangle(ebc, t0, t2);
const int32 eaf = eab;
Edges[eaf].Vert = FIndex2i(FMath::Min(a, f), FMath::Max(a, f));
//ReplaceEdgeVertex(eaf, b, f);
if (a != b)
{
VertexEdgeLists.Remove(b, eab);
}
VertexEdgeLists.Insert(f, eaf);
// create edges fb and fc
const int32 efb = AddEdgeInternal(f, b, t2);
const int32 efc = AddEdgeInternal(f, c, t0, t2);
// update triangle edge-nbrs
ReplaceTriangleEdge(t0, ebc, efc);
TriangleEdges[t2] = FIndex3i(efb, ebc, efc);
// update vertex refcounts
VertexRefCounts.Increment(c);
VertexRefCounts.Increment(f, 2);
SplitInfo.bIsBoundary = true;
SplitInfo.OtherVertices = FIndex2i(c, InvalidID);
SplitInfo.NewVertex = f;
SplitInfo.NewEdges = FIndex3i(efb, efc, InvalidID);
SplitInfo.NewTriangles = FIndex2i(t2, InvalidID);
return EMeshResult::Ok;
}
else // interior triangle branch
{
// look up other triangle
const int32 t1 = Edges[eab].Tri[1];
SplitInfo.OriginalTriangles.B = t1;
const FIndex3i T1tv = GetTriangle(t1);
const FIndex3i T1te = GetTriEdges(t1);
const int32 T1IndexOfe = T1te.IndexOf(eab);
checkSlow(T1tv[T1IndexOfe] == b);
const int32 d = T1tv[AddTwoModThree[T1IndexOfe]];
const int32 edb = T1te[AddTwoModThree[T1IndexOfe]];
// RefCount overflow check. Conservatively leave room for
// extra increments from other operations.
if (VertexRefCounts.GetRawRefCount(d) > FRefCountVector::INVALID_REF_COUNT - 3)
{
return EMeshResult::Failed_HitValenceLimit;
}
// create vertex
FVector3d vNew = 0.5 * (GetVertex(a) + GetVertex(b));
int32 f = AppendVertex(vNew);
// rewrite existing triangles, replacing b with f
Triangles[t0][AddOneModThree[IndexOfe]] = f;
Triangles[t1][T1IndexOfe] = f;
// add two triangles to close holes we just created
int32 t2 = AddTriangleInternal(f, b, c, InvalidID, InvalidID, InvalidID);
int32 t3 = AddTriangleInternal(f, d, b, InvalidID, InvalidID, InvalidID);
// update the edges we found above, to point to triangles
ReplaceEdgeTriangle(ebc, t0, t2);
ReplaceEdgeTriangle(edb, t1, t3);
// edge eab became eaf
int32 eaf = eab; //Edge * eAF = eAB;
Edges[eaf].Vert = FIndex2i(FMath::Min(a, f), FMath::Max(a, f));
// update a/b/f vertex-edges
if (a != b)
{
VertexEdgeLists.Remove(b, eab);
}
VertexEdgeLists.Insert(f, eaf);
// create edges connected to f (also updates vertex-edges)
int32 efb = AddEdgeInternal(f, b, t2, t3);
int32 efc = AddEdgeInternal(f, c, t0, t2);
int32 edf = AddEdgeInternal(d, f, t1, t3);
// update triangle edge-nbrs
ReplaceTriangleEdge(t0, ebc, efc);
ReplaceTriangleEdge(t1, edb, edf);
TriangleEdges[t2] = FIndex3i(efb, ebc, efc);
TriangleEdges[t3] = FIndex3i(edf, edb, efb);
// update vertex refcounts
VertexRefCounts.Increment(c);
VertexRefCounts.Increment(d);
VertexRefCounts.Increment(f, 4);
SplitInfo.bIsBoundary = false;
SplitInfo.OtherVertices = FIndex2i(c, d);
SplitInfo.NewVertex = f;
SplitInfo.NewEdges = FIndex3i(efb, efc, edf);
SplitInfo.NewTriangles = FIndex2i(t2, t3);
return EMeshResult::Ok;
}
}
UE::Geometry::EMeshResult UE::Geometry::FSimpleIntrinsicMesh::PokeTriangle(int32 TID, const FVector3d& BaryCoordinates, FPokeTriangleInfo& PokeInfo)
{
if (!IsTriangle(TID))
{
return EMeshResult::Failed_NotATriangle;
}
// state before the poke
const FIndex3i OriginalVIDs = GetTriangle(TID);
const FIndex3i OriginalEdges = GetTriEdges(TID);
const FVector3d OriginalTriEdgeLengths = GetTriEdgeLengths(TID);
// Add a new vertex and faces to the IntrinsicMesh.
// Note: the r3 position will be wrong initially since this poke will just interpolate the corners of the tri.
// we fix this position as the last step in this function
EMeshResult PokeResult = PokeTriangleTopology(TID, PokeInfo);
if (PokeResult != EMeshResult::Ok)
{
return PokeResult;
}
FIndex3i NewTris(TID, PokeInfo.NewTriangles[0], PokeInfo.NewTriangles[1]);
const int32 NewVID = PokeInfo.NewVertex;
// Need to update intrinsic information for the 3 triangles that resulted from the poke
// 1) update the edge lengths for the triangles
// 2) update the internal angles for the triangles
// (1) compute intrinsic edge lengths for the 3 new edges
{
FVector3d DistancesFromOldCorner;
for (int32 v = 0; v < 3; ++v)
{
FVector3d BaryCoordVertex(0., 0., 0.); BaryCoordVertex[v] = 1.;
const double Distance = DistanceBetweenBarycentricPoints( OriginalTriEdgeLengths[0],
OriginalTriEdgeLengths[1],
OriginalTriEdgeLengths[2],
BaryCoordVertex, BaryCoordinates);
DistancesFromOldCorner[v] = Distance;
}
{
// original tri is and verts (a, b,c) and edges (ab, bc, ca)
// the updated tri has verts (a, b, new) and edges (ab, bnew, newa)
FIndex3i T0EIDs = GetTriEdges(NewTris[0]);
EdgeLengths.InsertAt(DistancesFromOldCorner[1], T0EIDs[1]);
EdgeLengths.InsertAt(DistancesFromOldCorner[0], T0EIDs[2]);
// new tri[0] has verts (b, c, new) and edges (bc, cnew, newb)
FIndex3i T1EIDs = GetTriEdges(PokeInfo.NewTriangles[0]);
EdgeLengths.InsertAt(DistancesFromOldCorner[2], T1EIDs[1]);
}
}
// (2) update internal angles for the triangles.
{
InternalAngles.InsertAt(ComputeTriInternalAnglesR(NewTris[2]), NewTris[2]);
InternalAngles.InsertAt(ComputeTriInternalAnglesR(NewTris[1]), NewTris[1]);
InternalAngles[NewTris[0]] = ComputeTriInternalAnglesR(NewTris[0]);
}
// record the coordinate actually used.
PokeInfo.BaryCoords = BaryCoordinates;
return EMeshResult::Ok;
}
UE::Geometry::EMeshResult UE::Geometry::FSimpleIntrinsicMesh::SplitEdge(int32 EdgeAB, FEdgeSplitInfo& SplitInfo, double SplitParameterT)
{
SplitParameterT = FMath::Clamp(SplitParameterT, 0., 1.);
if (!IsEdge(EdgeAB))
{
return EMeshResult::Failed_NotAnEdge;
}
const FEdge OriginalEdge = GetEdge(EdgeAB);
if (IsBoundaryEdge(EdgeAB))
{
// state before the split
const int32 TID = OriginalEdge.Tri[0];
const int32 IndexOfe = GetTriEdges(TID).IndexOf(EdgeAB);
// Info about the original T0 tri, reordered to make the split edge the first edge..
const FVector3d OriginalT0EdgeLengths = Permute(IndexOfe, GetTriEdgeLengths(TID)); // as (|ab|, |bc|, |ca|)
// Update the connectivity with the edge split
EMeshResult Result = SplitEdgeTopology(EdgeAB, SplitInfo);
if (Result != EMeshResult::Ok)
{
return Result;
}
const int32 NewVID = SplitInfo.NewVertex;
const int32 NewTID = SplitInfo.NewTriangles[0]; // edges {fb, bc, cf}
const int32 NewEdgeFB = SplitInfo.NewEdges[0];
const int32 NewEdgeFC = SplitInfo.NewEdges[1];
const double DistAF = SplitParameterT * OriginalT0EdgeLengths[0];
const double DistFB = FMath::Max(0., OriginalT0EdgeLengths[0] - DistAF);
// new edge length
const double DistFC = DistanceBetweenBarycentricPoints( OriginalT0EdgeLengths[0],
OriginalT0EdgeLengths[1],
OriginalT0EdgeLengths[2],
FVector3d(SplitParameterT, 1. - SplitParameterT, 0.),
FVector3d(0., 0., 1.));
EdgeLengths.InsertAt(DistFC, NewEdgeFC);
EdgeLengths.InsertAt(DistFB, NewEdgeFB);
EdgeLengths[EdgeAB] = DistAF;
// update the internal angles
InternalAngles.InsertAt(ComputeTriInternalAnglesR(NewTID), NewTID);
InternalAngles[TID] = ComputeTriInternalAnglesR(TID);
return Result;
}
else
{
// state before the split
const int32 TID = OriginalEdge.Tri[0];
const int32 IndexOfe = GetTriEdges(TID).IndexOf(EdgeAB);
// Info about the original T0 tri, reordered to make the split edge the first edge..
const FVector3d OriginalT0EdgeLengths = Permute(IndexOfe, GetTriEdgeLengths(TID)); // as (|ab|, |bc|, |ca|)
// Info about the original T1 tri, reordered to make the split edge the first edge..
const int32 TID1 = OriginalEdge.Tri[1];
const int32 IndexOfT1e = GetTriEdges(TID1).IndexOf(EdgeAB);
const FVector3d OriginalT1EdgeLengths = Permute(IndexOfT1e, GetTriEdgeLengths(TID1)); // as (|ba|, |ad|, |db})
// Update the connectivity with the edge split
EMeshResult Result = SplitEdgeTopology(EdgeAB, SplitInfo);
if (Result != EMeshResult::Ok)
{
return Result;
}
const int32 NewVID = SplitInfo.NewVertex;
const int32 NewTID0 = SplitInfo.NewTriangles[0]; // edges {fb, bc, cf}
const int32 NewTID1 = SplitInfo.NewTriangles[1]; // edges {fd, db, bc}
const int32 EdgeAF = EdgeAB;
const int32 NewEdgeFB = SplitInfo.NewEdges[0];
const int32 NewEdgeFC = SplitInfo.NewEdges[1];
const int32 NewEdgeFD = SplitInfo.NewEdges[2];
const double DistAF = SplitParameterT * OriginalT0EdgeLengths[0];
const double DistFB = FMath::Max(0., OriginalT0EdgeLengths[0] - DistAF);
// new edge length
const double DistFC = DistanceBetweenBarycentricPoints( OriginalT0EdgeLengths[0],
OriginalT0EdgeLengths[1],
OriginalT0EdgeLengths[2],
FVector3d(SplitParameterT, 1. - SplitParameterT, 0.),
FVector3d(0., 0., 1.));
// new edge length
const double DistFD = DistanceBetweenBarycentricPoints( OriginalT1EdgeLengths[0],
OriginalT1EdgeLengths[1],
OriginalT1EdgeLengths[2],
FVector3d(1. - SplitParameterT, SplitParameterT, 0.),
FVector3d(0., 0., 1.));
EdgeLengths.InsertAt(DistFD, NewEdgeFD);
EdgeLengths.InsertAt(DistFC, NewEdgeFC);
EdgeLengths.InsertAt(DistFB, NewEdgeFB);
EdgeLengths[EdgeAF] = DistAF;
// update the internal angles (this uses edge lengths)
InternalAngles.InsertAt(ComputeTriInternalAnglesR(NewTID1), NewTID1);
InternalAngles.InsertAt(ComputeTriInternalAnglesR(NewTID0), NewTID0);
InternalAngles[TID] = ComputeTriInternalAnglesR(TID);
InternalAngles[TID1] = ComputeTriInternalAnglesR(TID1);
return Result;
}
}
/**------------------------------------------------------------------------------
* FIntrinsicTriangulation Methods
*-------------------------------------------------------------------------------*/
UE::Geometry::FIntrinsicTriangulation::FIntrinsicTriangulation(const FDynamicMesh3& SrcMesh)
: MyBase(SrcMesh)
, SignpostData(SrcMesh)
{
}
TArray<FIntrinsicTriangulation::FSurfacePoint> UE::Geometry::FIntrinsicTriangulation::TraceEdge(int32 EID, double CoalesceThreshold, bool bReverse) const
{
TArray<FIntrinsicTriangulation::FSurfacePoint> SurfacePoints;
if (!IsEdge(EID))
{
return SurfacePoints;
}
const FDynamicMesh3* SurfaceMesh = GetExtrinsicMesh();
const double IntrinsicEdgeLength = GetEdgeLength(EID);
const FIndex2i EdgeV = GetEdgeV(EID);
int32 StartVID = EdgeV.A;
int32 EndVID = EdgeV.B;
if (bReverse)
{
StartVID = EdgeV.B;
EndVID = EdgeV.A;
}
FIndex2i AdjTIDs = GetEdgeT(EID);
// look-up the polar angle of this edge as it leaves StartVID ( this angle is relative to a specified reference mesh edge )
const double PolarAngle =
[&]{
const int32 AdjTID = AdjTIDs.A;
const int32 IndexOf = GetTriEdges(AdjTID).IndexOf(EID);
checkSlow(IndexOf > -1);
if (GetTriangle(AdjTID)[IndexOf] == StartVID)
{
// IntrinsicEdgeAngles hold the local angle of each out-going edge relative to the vertex the edge exits
return SignpostData.IntrinsicEdgeAngles[AdjTID][IndexOf];
}
else
{
const double ToRadians = SignpostData.GeometricVertexInfo[StartVID].ToRadians;
// polar angle of prev edge just before (clockwise) the edge we want
const double PrevPolarAngle = SignpostData.IntrinsicEdgeAngles[AdjTID][(IndexOf + 1) % 3];
// angle between previous edge and this one
const double InternalAngle = InternalAngles[AdjTID][(IndexOf + 1) % 3];
// add the internal angle to rotate from Prev Edge to this edge
return ToRadians * InternalAngle + PrevPolarAngle;
}
}();
// Trace the surface mesh from the intrinsic StartVID, in the PolarAngle direction, a distance of IntrinsicEdgeLength.
//
// Note: this intrinsic vertex may or may not correspond to a vertex in the surface mesh if vertices were added to the intrinsic mesh
// by doing an edge split or a triangle poke.
FMeshGeodesicSurfaceTracer SurfaceTracer = SignpostSufaceTraceUtil::TraceFromIntrinsicVert(SignpostData, StartVID, PolarAngle, IntrinsicEdgeLength);
// util to convert the trace result to a surface point, potentially snapping to a vertex if within the coalesce threshold
auto TraceResultToSurfacePoint = [CoalesceThreshold, SurfaceMesh](const FMeshGeodesicSurfaceTracer::FTraceResult& TraceResult)->FSurfacePoint
{
if (TraceResult.bIsEdgePoint)
{
// TraceResult alpha is defined as EdgeV.A * (1-Alpha) + EdgeV.B Alpha; This is the complement of what we want
const double Alpha = FMath::Clamp((1. - TraceResult.EdgeAlpha), 0., 1.);
const int32 EID = TraceResult.EdgeID;
if (Alpha <= 0.5 && Alpha < CoalesceThreshold)
{
return FSurfacePoint(SurfaceMesh->GetEdgeV(EID).B);
}
else if (Alpha > 0.5 && (1. - Alpha) < CoalesceThreshold)
{
return FSurfacePoint(SurfaceMesh->GetEdgeV(EID).A);
}
return FSurfacePoint(EID, Alpha);
}
else
{
// TODO - should snap to vertex / edge if close?
const int32 TID = TraceResult.TriID;
const FVector3d BC = TraceResult.Barycentric;
return FSurfacePoint(TID, BC);
}
};
// Add surface point to the outgoing array, but don't allow for duplicate vertex points
auto AddSurfacePoint = [&SurfacePoints](const FSurfacePoint& PointA)
{
if (SurfacePoints.Num() == 0)
{
SurfacePoints.Add(PointA);
}
else
{
const FSurfacePoint& PointB = SurfacePoints.Last();
const bool bAreSameVertexPoint = ( PointA.PositionType == FSurfacePoint::EPositionType::Vertex &&
PointB.PositionType == FSurfacePoint::EPositionType::Vertex &&
PointA.Position.VertexPosition.VID == PointB.Position.VertexPosition.VID );
if (!bAreSameVertexPoint)
{
SurfacePoints.Add(PointA);
}
}
};
// package the surface trace results as series of surface points. Note, because this is a trace along an intrinsic edge
// the first and last result will be an intrinsic vertex (StartVID and EndVID)
TArray<FMeshGeodesicSurfaceTracer::FTraceResult>& TraceResults = SurfaceTracer.GetTraceResults();
{
int32 NumTraceResults = TraceResults.Num();
AddSurfacePoint(GetVertexSurfacePoint(StartVID));
for (int32 i = 1; i < NumTraceResults - 1; ++i)
{
FMeshGeodesicSurfaceTracer::FTraceResult& TraceResult = TraceResults[i];
FSurfacePoint SurfacePoint = TraceResultToSurfacePoint(TraceResult);
AddSurfacePoint(SurfacePoint);
}
AddSurfacePoint(GetVertexSurfacePoint(EndVID));
}
return SurfacePoints;
}
EMeshResult UE::Geometry::FIntrinsicTriangulation::FlipEdge(int32 EID, FEdgeFlipInfo& EdgeFlipInfo)
{
if (IsBoundaryEdge(EID))
{
return EMeshResult::Failed_IsBoundaryEdge;
}
// capture the state of the triangles before the flip.
const FIndex2i Tris = GetEdgeT(EID);
const FIndex2i PreFlipIndexOf(GetTriEdges(Tris[0]).IndexOf(EID), GetTriEdges(Tris[1]).IndexOf(EID));
// flip edge in the underlying mesh
// this updates the edge lengths and the interior angles.
const EMeshResult MeshFlipResult = MyBase::FlipEdge(EID, EdgeFlipInfo);
if (MeshFlipResult != EMeshResult::Ok)
{
// could fail for many reasons, e.g. a boundary edge
return MeshFlipResult;
}
// internal angles at the verts that are now connected by the flipped edge
const double NewAngleAtC = InternalAngles[Tris[1]][1];
const double NewAngleAtD = InternalAngles[Tris[0]][1];
// update signpost
SignpostData.OnFlipEdge(EID, Tris, EdgeFlipInfo.OpposingVerts, PreFlipIndexOf, NewAngleAtC, NewAngleAtD);
return EMeshResult::Ok;
}
double UE::Geometry::FIntrinsicTriangulation::UpdateVertexByEdgeTrace(const int32 NewVID, const int32 TraceStartVID, const double TracePolarAngle, const double TraceDist)
{
using FSurfaceTraceResult = FSignpost::FSurfaceTraceResult;
FMeshGeodesicSurfaceTracer SurfaceTracer = SignpostSufaceTraceUtil::TraceFromIntrinsicVert(SignpostData, TraceStartVID, TracePolarAngle, TraceDist);
TArray<FMeshGeodesicSurfaceTracer::FTraceResult>& TraceResultArray = SurfaceTracer.GetTraceResults();
// need to convert the result of the trace into the correct form
const FMeshGeodesicSurfaceTracer::FTraceResult& TraceResult = TraceResultArray.Last();
const FSurfacePoint TraceResultPosition(TraceResult.TriID, TraceResult.Barycentric);
// fix directions relative to local reference edge on the extrinsic mesh
// by finding direction of the TraceEID edge indecent on NewVID
const double AngleOffset = [&]
{
FVector2d Dir = TraceResult.SurfaceDirection.Dir;
// translate to Dir about the reference edge for this triangle
const int32 EndRefEID = SignpostData.TIDToReferenceEID[TraceResult.TriID];
const FMeshGeodesicSurfaceTracer::FTangentTri2& TangentTri2 = SurfaceTracer.GetLastTri();
// convert to local basis for first edge of tri2
if (TangentTri2.EdgeOrientationSign[0] == -1)
{
Dir = -Dir;
}
const int32 IndexOfEndRefEID = TangentTri2.PermutedTriEIDs.IndexOf(EndRefEID);
FVector2d DirRelToRefEID = TangentTri2.ChangeBasis(Dir, IndexOfEndRefEID);
if (TangentTri2.EdgeOrientationSign[IndexOfEndRefEID] == -1)
{
DirRelToRefEID = -DirRelToRefEID;
}
// angle of (new edge) path to NewVert relative to Ref edge
const double AngleToNewVert = FMath::Atan2(DirRelToRefEID.Y, DirRelToRefEID.X);
// reverse direction of path because we NewVert is the local origin for polar angles around new vert
const double AngleFromNewVert = AngleToNewVert + TMathUtilConstants<double>::Pi;
return AsZeroToTwoPi(AngleFromNewVert);
}();
// Trace result as position and angle.
FSurfaceTraceResult SurfaceTraceResult = { TraceResultPosition, AngleOffset };
// As R3
bool bTmpIsValid;
const FVector3d TraceResultPos = AsR3Position(SurfaceTraceResult.SurfacePoint, *SignpostData.SurfaceMesh, bTmpIsValid);
// update the R3 for NewVID in the intrinsic mesh
Vertices[NewVID] = TraceResultPos;
// store the surface position for the intrinsic vertex in the signpost data
// Currently we are storing every new intrinsic surface position as a point on a surface tri
// TODO: is there any advantage to classify as "Edge" position if very close to edge ?
SignpostData.IntrinsicVertexPositions.InsertAt(SurfaceTraceResult.SurfacePoint, NewVID);
// return relative angle of trace
return SurfaceTraceResult.Angle;
}
UE::Geometry::EMeshResult UE::Geometry::FIntrinsicTriangulation::PokeTriangle(int32 TID, const FVector3d& BaryCoordinates, FPokeTriangleInfo& PokeInfo)
{
if (!IsTriangle(TID))
{
return EMeshResult::Failed_NotATriangle;
}
// state before the poke
const FIndex3i OriginalVIDs = GetTriangle(TID);
const FVector3d OriginalEdgeDirs = SignpostData.IntrinsicEdgeAngles[TID];
// Add a new vertex and faces to the IntrinsicMesh.
// this operation will update the internal angles and the edge lengths, but the actual R3 positon
// of the vertex will need to be updated as will any signpost data.
EMeshResult PokeResult = MyBase::PokeTriangle(TID, BaryCoordinates, PokeInfo);
if (PokeResult != EMeshResult::Ok)
{
return PokeResult;
}
FIndex3i NewTris(TID, PokeInfo.NewTriangles[0], PokeInfo.NewTriangles[1]);
const int32 NewVID = PokeInfo.NewVertex;
// Need to update intrinsic information for the 3 triangles that resulted from the poke
// 1) update Signpost data ( the edge directions for the triangles )
// 2) update the surface point for the new vertex by tracing one of the new edges.
// also update the edge directions leaving the new vertex to be relative to the direction defined on the extrinsic mesh
// (1) update the edge directions
FVector3d TriEdgeDir[3]; // one for each tri in order (TID, NewTris[0], NewTris[1] )
{
// edges around the boundary of the original tri
TriEdgeDir[0][0] = OriginalEdgeDirs[0]; // AtoB dir
TriEdgeDir[1][0] = OriginalEdgeDirs[1]; // BtoC dir
TriEdgeDir[2][0] = OriginalEdgeDirs[2]; // CtoA dir
// edges from the corners of the original tri towards the new vertex at the "center"
const double ToRadiansAtA = SignpostData.GeometricVertexInfo[OriginalVIDs[0]].ToRadians;
const double ToRadiansAtB = SignpostData.GeometricVertexInfo[OriginalVIDs[1]].ToRadians;
const double ToRadiansAtC = SignpostData. GeometricVertexInfo[OriginalVIDs[2]].ToRadians;
TriEdgeDir[0][1] = AsZeroToTwoPi( OriginalEdgeDirs[1] + ToRadiansAtB * InternalAngles[NewTris[1]][0]); // BtoNew dir
TriEdgeDir[1][1] = AsZeroToTwoPi( OriginalEdgeDirs[2] + ToRadiansAtC * InternalAngles[NewTris[2]][0]); // CtoNew dir
TriEdgeDir[2][1] = AsZeroToTwoPi( OriginalEdgeDirs[0] + ToRadiansAtA * InternalAngles[NewTris[0]][0]); // AtoNew dir
// edges from new vertex to a, to b, and to c, using new-to-A as the zero direction. These will be updated later when we learn the correct angle for new-to-A
TriEdgeDir[0][2] = 0.; // NewToA dir
TriEdgeDir[1][2] = InternalAngles[NewTris[0]][2]; // NewToB dir
TriEdgeDir[2][2] = InternalAngles[NewTris[0]][2] + InternalAngles[NewTris[1]][2]; // NewToC dir
}
SignpostData.GeometricVertexInfo.InsertAt(FSignpost::FGeometricInfo(), NewVID); // we want the default of false, and 1.
// (2) update the surface point for the new vertex by tracing one of the new edges.
//
// --- fix the r3 position of the new vertex and compute its SurfacePoint Attributes by doing a trace on the Extrinsic Mesh along one of the edges incident on NewVID
// Use first incident edge and find the distance and direction (angle) to trace
// Q: would picking the shortest of the new edges be better than just the first?
const int32 AtoNewEID = PokeInfo.NewEdges[0]; // a-to-new edge
const double AtoNewDist = EdgeLengths[AtoNewEID];
// trace from A vertex in the direction toward the new vertex the edge length distance. This updates the surface position entry for the new vert.
const int32 AVID = OriginalVIDs[0];
const double AtoNewDir = TriEdgeDir[2][1];
const double NewToAAngle = UpdateVertexByEdgeTrace(NewVID, AVID, AtoNewDir, AtoNewDist);
// update the Newto{A,B,C} directions such that NewToA has the angle LocalEIDAngle.
for (int32 e = 0; e < 3; ++e)
{
TriEdgeDir[e][2] = AsZeroToTwoPi(TriEdgeDir[e][2] + NewToAAngle);
}
// record the new edge directions.
SignpostData.IntrinsicEdgeAngles.InsertAt(TriEdgeDir[2], NewTris[2]);
SignpostData.IntrinsicEdgeAngles.InsertAt(TriEdgeDir[1], NewTris[1]);
SignpostData.IntrinsicEdgeAngles[NewTris[0]] = TriEdgeDir[0];
// record the coordinate actually used.
PokeInfo.BaryCoords = BaryCoordinates;
return EMeshResult::Ok;
}
UE::Geometry::EMeshResult UE::Geometry::FIntrinsicTriangulation::SplitEdge(int32 EdgeAB, FEdgeSplitInfo& SplitInfo, double SplitParameterT)
{
SplitParameterT = FMath::Clamp(SplitParameterT, 0., 1.);
if (!IsEdge(EdgeAB))
{
return EMeshResult::Failed_NotAnEdge;
}
const FEdge OriginalEdge = GetEdge(EdgeAB);
if (IsBoundaryEdge(EdgeAB))
{
// state before the split
const int32 TID = OriginalEdge.Tri[0];
const int32 IndexOfe = GetTriEdges(TID).IndexOf(EdgeAB);
// Info about the original T0 tri, reordered to make the split edge the first edge..
const FIndex3i OriginalT0VIDs = Permute(IndexOfe, GetTriangle(TID)); // as (a, b, c)
const FVector3d OriginalT0EdgeDirs = Permute(IndexOfe, SignpostData.IntrinsicEdgeAngles[TID]); // as ( aTob, bToc, cToa)
const FVector3d OriginalT0EdgeLengths = Permute(IndexOfe, GetTriEdgeLengths(TID)); // as (|ab|, |bc|, |ca|)
// Update the connectivity with the edge split
EMeshResult Result = MyBase::SplitEdge(EdgeAB, SplitInfo, SplitParameterT);
if (Result != EMeshResult::Ok)
{
return Result;
}
const int32 NewVID = SplitInfo.NewVertex;
const int32 NewTID = SplitInfo.NewTriangles[0]; // edges {fb, bc, cf}
const double DistAF = SplitParameterT * OriginalT0EdgeLengths[0];
const FVector3d TIDInternalAngles = Permute(IndexOfe, InternalAngles[TID]);
// update the directions. Say DirFtoB is zero
const double ToRadiansAtC = SignpostData.GeometricVertexInfo[OriginalT0VIDs[2]].ToRadians;
SignpostData.GeometricVertexInfo.InsertAt(FSignpost::FGeometricInfo(), NewVID); // we want the default of false, and 1.
FVector3d TriEdgeDir[2]; // one for each tri in order (TID, NewTID )
// edges around the boundary of the original tri
TriEdgeDir[0][0] = OriginalT0EdgeDirs[0]; // AtoF dir
TriEdgeDir[1][1] = OriginalT0EdgeDirs[1]; // BtoC dir
TriEdgeDir[0][2] = OriginalT0EdgeDirs[2]; // CtoA dir
TriEdgeDir[1][0] = 0.; // FtoB dir
TriEdgeDir[1][2] = AsZeroToTwoPi(OriginalT0EdgeDirs[2] + ToRadiansAtC * TIDInternalAngles[2]); // CtoF dir
TriEdgeDir[0][1] = AsZeroToTwoPi(InternalAngles[NewTID][0]); // FtoC dir relative to FtoB
const double FtoADir = AsZeroToTwoPi(InternalAngles[NewTID][0] + TIDInternalAngles[1]); // FtoA dir relative to FtoB
// update the surface position of the new vertex and the relative directions from it.
// trace from A vertex in the direction toward the new vertex the edge length distance. This updates the surface position entry for the new vert.
const int32 AVID = OriginalT0VIDs[0];
const double AtoFDir = TriEdgeDir[0][0];
const double FToAAngle = UpdateVertexByEdgeTrace(NewVID, AVID, AtoFDir, DistAF);
const double ToLocalDir = (FToAAngle - FtoADir);
// update the Fto{B,C} directions such that the direction from F to A would agree with FtoAAngle
TriEdgeDir[0][1] = AsZeroToTwoPi(TriEdgeDir[0][1] + ToLocalDir);
TriEdgeDir[1][0] = AsZeroToTwoPi(TriEdgeDir[1][0] + ToLocalDir);
// store angle results
SignpostData.IntrinsicEdgeAngles.InsertAt(TriEdgeDir[1], NewTID);
SignpostData.IntrinsicEdgeAngles[TID] = Permute((3 - IndexOfe)%3, TriEdgeDir[0]);
return Result;
}
else
{
// state before the split
const int32 TID = OriginalEdge.Tri[0];
const int32 IndexOfe = GetTriEdges(TID).IndexOf(EdgeAB);
// Info about the original T0 tri, reordered to make the split edge the first edge..
const FIndex3i OriginalT0VIDs = Permute(IndexOfe, GetTriangle(TID)); // as (a, b, c)
const FVector3d OriginalT0EdgeDirs = Permute(IndexOfe, SignpostData.IntrinsicEdgeAngles[TID]); // as ( aTob, bToc, cToa)
const FVector3d OriginalT0EdgeLengths = Permute(IndexOfe, GetTriEdgeLengths(TID)); // as (|ab|, |bc|, |ca|)
// Info about the original T0 tri, reordered to make the split edge the first edge..
const int32 TID1 = OriginalEdge.Tri[1];
const int32 IndexOfT1e = GetTriEdges(TID1).IndexOf(EdgeAB);
const FIndex3i OriginalT1VIDs = Permute(IndexOfT1e, GetTriangle(TID1)); // as (b, a, d)
const FVector3d OriginalT1EdgeDirs = Permute(IndexOfT1e, SignpostData.IntrinsicEdgeAngles[TID1]); // as (bToa, aTod, dTob)
// Update the connectivity and the intrinsic data with the edge split
EMeshResult Result = MyBase::SplitEdge(EdgeAB, SplitInfo, SplitParameterT);
if (Result != EMeshResult::Ok)
{
return Result;
}
const int32 NewVID = SplitInfo.NewVertex;
const int32 NewTID0 = SplitInfo.NewTriangles[0]; // edges {fb, bc, cf}
const int32 NewTID1 = SplitInfo.NewTriangles[1]; // edges {fd, db, bc}
const double DistAF = SplitParameterT * OriginalT0EdgeLengths[0];
// update the directions. Say DirFtoB is zero
const double ToRadiansAtC = SignpostData.GeometricVertexInfo[OriginalT0VIDs[2]].ToRadians;
const double ToRadiansAtD = SignpostData.GeometricVertexInfo[OriginalT1VIDs[2]].ToRadians;
SignpostData.GeometricVertexInfo.InsertAt(FSignpost::FGeometricInfo(), NewVID); // we want the default of false, and 1.
const FVector3d TIDInternalAngles = Permute(IndexOfe, InternalAngles[TID]);
const FVector3d TID1InternalAngles = Permute(IndexOfT1e, InternalAngles[TID1]);
FVector3d TriEdgeDir[4]; // one for each tri in order (TID, NewTID0, TID1, NewTID1 )
// edges around the boundary of the original tri
TriEdgeDir[0][0] = OriginalT0EdgeDirs[0]; // AtoF dir
TriEdgeDir[1][1] = OriginalT0EdgeDirs[1]; // BtoC dir
TriEdgeDir[0][2] = OriginalT0EdgeDirs[2]; // CtoA dir
TriEdgeDir[3][2] = OriginalT1EdgeDirs[0]; // BtoF dir
TriEdgeDir[2][1] = OriginalT1EdgeDirs[1]; // AtoD dir
TriEdgeDir[3][1] = OriginalT1EdgeDirs[2]; // DtoB dir
TriEdgeDir[1][2] = AsZeroToTwoPi(OriginalT0EdgeDirs[2] + ToRadiansAtC * TIDInternalAngles[2]); // CtoF dir
TriEdgeDir[2][2] = AsZeroToTwoPi(OriginalT1EdgeDirs[2] + ToRadiansAtD * InternalAngles[NewTID1][1]); // Dtof dir
TriEdgeDir[1][0] = 0.; // FtoB dir
TriEdgeDir[0][1] = AsZeroToTwoPi(InternalAngles[NewTID0][0]); // FtoC dir relative to FtoB
TriEdgeDir[2][0] = AsZeroToTwoPi(InternalAngles[NewTID0][0] + TIDInternalAngles[1]); // FtoA dir relative to FtoB
TriEdgeDir[3][0] = AsZeroToTwoPi(InternalAngles[NewTID0][0] + TIDInternalAngles[1] + TID1InternalAngles[0]); // FtoD dir relative to FtoB
// trace from A vertex in the direction toward the new vertex the edge length distance. This updates the surface position entry for the new vert.
const int32 AVID = OriginalT0VIDs[0];
const double AtoFDir = TriEdgeDir[0][0];
const double FToAAngle = UpdateVertexByEdgeTrace(NewVID, AVID, AtoFDir, DistAF);
const double ToLocalDir = (FToAAngle - TriEdgeDir[2][0]);
// fix angles relative to local dir.
TriEdgeDir[1][0] = AsZeroToTwoPi(ToLocalDir);
TriEdgeDir[0][1] = AsZeroToTwoPi(TriEdgeDir[0][1] + ToLocalDir);
TriEdgeDir[2][0] = AsZeroToTwoPi(TriEdgeDir[2][0] + ToLocalDir);
TriEdgeDir[3][0] = AsZeroToTwoPi(TriEdgeDir[3][0] + ToLocalDir);
// store angle results
SignpostData.IntrinsicEdgeAngles.InsertAt(TriEdgeDir[3], NewTID1);
SignpostData.IntrinsicEdgeAngles.InsertAt(TriEdgeDir[1], NewTID0);
SignpostData.IntrinsicEdgeAngles[TID] = Permute((3 - IndexOfe) % 3, TriEdgeDir[0]);
SignpostData.IntrinsicEdgeAngles[TID1] = Permute((3 - IndexOfT1e) % 3, TriEdgeDir[2]);
return Result;
}
}
/**------------------------------------------------------------------------------
* FIntrinsicEdgeFlipMesh Methods
*-------------------------------------------------------------------------------*/
FIntrinsicEdgeFlipMesh::FIntrinsicEdgeFlipMesh(const FDynamicMesh3& SurfaceMesh)
{
// construct the intrinsic mesh directly from this surface mesh
MyBase::Reset(SurfaceMesh);
NormalCoordinates.Reset(SurfaceMesh);
}
EMeshResult FIntrinsicEdgeFlipMesh::FlipEdge(int32 EID, FEdgeFlipInfo& EdgeFlipInfo)
{
if (!IsEdge(EID))
{
return EMeshResult::Failed_NotAnEdge;
}
if (IsBoundaryEdge(EID))
{
return EMeshResult::Failed_IsBoundaryEdge;
}
// state before flip, needed when updating the normal coords after the flip
const FIndex2i EdgeT = GetEdgeT(EID);
const FIndex3i TAEIDs = GetTriEdges(EdgeT.A);
const FIndex3i TBEIDs = GetTriEdges(EdgeT.B);
const FIndex2i OppVs = GetEdgeOpposingV(EID);
EMeshResult FlipResult = MyBase::FlipEdge(EID, EdgeFlipInfo);
if (FlipResult == EMeshResult::Ok)
{
// update the normal coords
NormalCoordinates.OnFlipEdge(EdgeT.A, TAEIDs, OppVs.A, EdgeT.B, TBEIDs, OppVs.B, EID);
}
return FlipResult;
}
TArray<FIntrinsicEdgeFlipMesh::FSurfacePoint> FIntrinsicEdgeFlipMesh::TraceEdge(int32 IntrinsicEID, double CoalesceThreshold, bool bReverse) const
{
return FNormalCoordIntrinsicTraceImpl::TraceEdge(*this, IntrinsicEID, CoalesceThreshold, bReverse);
}
TArray<FIntrinsicEdgeFlipMesh::FEdgeAndCrossingIdx> FIntrinsicEdgeFlipMesh::GetImplicitEdgeCrossings(const int32 SurfaceEID, const bool bReverse) const
{
return FNormalCoordSurfaceTraceImpl::TraceSurfaceEdge(*this, SurfaceEID, bReverse);
}
TArray<FIntrinsicEdgeFlipMesh::FSurfacePoint>
FIntrinsicEdgeFlipMesh::TraceSurfaceEdge(int32 SurfaceEID, double CoalesceThreshold, bool bReverse) const
{
const FDynamicMesh3& HostMesh = *this->GetExtrinsicMesh();
const FIntrinsicEdgeFlipMesh& TraceMesh = *this;
TArray<FEdgeAndCrossingIdx> EdgeAndCrossingIdxs = this->GetImplicitEdgeCrossings(SurfaceEID, bReverse);
// NB: for this intrinsic mesh type, we know that the intrinsic verts are the same as the surface verts ( since splits and pokes aren't allowed)
TArray<int32> HostEdgeCrossings;
HostEdgeCrossings.Reserve(EdgeAndCrossingIdxs.Num() - 2); // EdgeAndCrossingsIdx include the start and end vertex. don't need them.
for (FEdgeAndCrossingIdx EdgeAndCrossing : EdgeAndCrossingIdxs)
{
if (EdgeAndCrossing.CIdx != 0) // skip the start and end vertex
{
HostEdgeCrossings.Add(EdgeAndCrossing.EID);
}
}
return FNormalCoordSurfaceTraceImpl::TraceEdgeOverHost(SurfaceEID, HostEdgeCrossings, HostMesh, TraceMesh, CoalesceThreshold, bReverse,
[](int32 VID) {return FSurfacePoint(VID);});
}
/**------------------------------------------------------------------------------
* TEdgeCorrespondence
*-------------------------------------------------------------------------------*/
template <typename IntrinsicMeshType>
void UE::Geometry::TEdgeCorrespondence<IntrinsicMeshType>::Setup(const IntrinsicMeshType& Mesh)
{
SurfaceEdgesCrossed.Reset();
IntrinsicMesh = &Mesh;
SurfaceMesh = Mesh.GetNormalCoordinates().SurfaceMesh;
const int32 IntrinsicMaxEID = IntrinsicMesh->MaxEdgeID();
const int32 SurfaceMaxEID = SurfaceMesh->MaxEdgeID();
SurfaceEdgesCrossed.SetNum(IntrinsicMaxEID);
const IntrinsicCorrespondenceUtils::FNormalCoordinates& NormalCoords = IntrinsicMesh->GetNormalCoordinates();
// allocate the SurfaceEdgesCrossed. From the normal coordinates we know how many surface edge crossings each intrinsic edge sees
for (int32 IntrinsicEID = 0; IntrinsicEID < IntrinsicMaxEID; ++IntrinsicEID)
{
if (!IntrinsicMesh->IsEdge(IntrinsicEID))
{
continue;
}
// number of times a surface edge crosses this intrinsic edge
const int32 NumXings = NormalCoords.NumEdgeCrossing(IntrinsicEID);
if (NumXings > 0)
{
SurfaceEdgesCrossed[IntrinsicEID].SetNum(NumXings);
} // else don't bother making an entry when NumXings == 0 since that means the edges are the same on both meshes.
}
// trace each surface edge across the intrinsic mesh and construct an ordered list of surface edges crossing each intrinsic edge.
// the order of the crossings should be consistent with the edge direction relative to the first adjacent tri
// (i.e. starting at the corner Mesh.GetTriEdges(GetEdgeT(EID).A).IndexOf(EID) )
for (int32 SurfaceEID = 0; SurfaceEID < SurfaceMaxEID; ++SurfaceEID)
{
if (!SurfaceMesh->IsEdge(SurfaceEID))
{
continue;
}
const TArray<FEdgeAndCrossingIdx> IntrinsicEdgeXings = IntrinsicMesh->GetImplicitEdgeCrossings(SurfaceEID, false /* = bReverseTrace*/);
for (int32 i = 0; i < IntrinsicEdgeXings.Num(); ++i)
{
const FEdgeAndCrossingIdx& EdgeXing = IntrinsicEdgeXings[i];
bool bIsEndVertex = (EdgeXing.CIdx == 0);
if (!bIsEndVertex)
{
const int32 IntrinsicEID = EdgeXing.EID;
const FIndex2i IntrinsicEdgeT = IntrinsicMesh->GetEdgeT(IntrinsicEID);
checkSlow(IntrinsicEdgeT.A == EdgeXing.TID || IntrinsicEdgeT.B == EdgeXing.TID);
// array of surface edges this intrinsic edge crosses, these should be ordered relative to the direction of TriA.
TArray<int32>& XingSurfaceEdges = SurfaceEdgesCrossed[IntrinsicEID];
// crossings count from the bottom of the edge to the top relative to EdgeXing.TID
const int32 XingID = (IntrinsicEdgeT.A == EdgeXing.TID) ? EdgeXing.CIdx - 1 : XingSurfaceEdges.Num() - EdgeXing.CIdx;
checkSlow(XingID > -1);
XingSurfaceEdges[XingID] = SurfaceEID;
}
}
}
}
template <typename IntrinsicMeshType>
TArray<UE::Geometry::IntrinsicCorrespondenceUtils::FSurfacePoint> TEdgeCorrespondence<IntrinsicMeshType>::TraceEdge(int32 IntrinsicEID, double CoalesceThreshold, bool bReverse) const
{
const FDynamicMesh3& HostMesh = *SurfaceMesh;
const IntrinsicMeshType& TraceMesh = *IntrinsicMesh;
const TArray<int32>& HostEdgeCrossings = SurfaceEdgesCrossed[IntrinsicEID];
return FNormalCoordSurfaceTraceImpl::TraceEdgeOverHost(IntrinsicEID, HostEdgeCrossings, HostMesh, TraceMesh, CoalesceThreshold, bReverse,
[&TraceMesh](int32 VID) { return TraceMesh.GetVertexSurfacePoint(VID); });
}
template struct UE::Geometry::TEdgeCorrespondence<UE::Geometry::FIntrinsicEdgeFlipMesh>;
template struct UE::Geometry::TEdgeCorrespondence<UE::Geometry::FIntrinsicMesh>;
/**------------------------------------------------------------------------------
* FIntrinsicMesh Helpers
*-------------------------------------------------------------------------------*/
namespace FIntrinsicMeshImplUtils
{
using namespace UE::Geometry;
/**
* Test if a given SurfacePoint is on the specified surface triangle of the surface mesh, this includes the triangle boundaries:
* i.e. will return true, if the point is on the face of the triangle, one of the edges, or on a vertex of the triangle.
*/
bool IsOnSurfaceTriangle(const IntrinsicCorrespondenceUtils::FSurfacePoint& SurfacePoint, const int32 SurfaceTID, const FDynamicMesh3& SurfaceMesh)
{
bool bFoundTID = false;
if (!SurfaceMesh.IsTriangle(SurfaceTID))
{
return bFoundTID;
}
if (IsFacePoint(SurfacePoint))
{
const int32 TID = SurfacePoint.Position.TriPosition.TriID;
if (TID == SurfaceTID)
{
bFoundTID = true;
}
}
else if (IsEdgePoint(SurfacePoint))
{
const int32 SurfaceEID = SurfacePoint.Position.EdgePosition.EdgeID;
const FIndex2i SurfaceEdgeT = SurfaceMesh.GetEdgeT(SurfaceEID);
if (SurfaceEdgeT.IndexOf(SurfaceTID) != -1)
{
bFoundTID = true;
}
}
else
{
checkSlow(IsVertexPoint(SurfacePoint));
const int32 SurfaceVID = SurfacePoint.Position.VertexPosition.VID;
for (int TID : SurfaceMesh.VtxTrianglesItr(SurfaceVID))
{
if (TID == SurfaceTID)
{
bFoundTID = true;
break;
}
}
}
return bFoundTID;
}
/**
* Collect the surface triangles that are adjacent to the specified surface point
* this may be one, two, or many - depending on location of the point (eg a surface point that is on an edge may have two adjacent tris)
*/
TArray<int32> GetAdjacentTriangles(const IntrinsicCorrespondenceUtils::FSurfacePoint& SurfacePoint, const FDynamicMesh3& SurfaceMesh)
{
TArray<int32> AdjTIDs;
if (IsEdgePoint(SurfacePoint))
{
const int32 SurfaceEID = SurfacePoint.Position.EdgePosition.EdgeID;
const FIndex2i SurfaceEdgeT = SurfaceMesh.GetEdgeT(SurfaceEID);
AdjTIDs.Add(SurfaceEdgeT.A);
if (SurfaceEdgeT.B != -1)
{
AdjTIDs.Add(SurfaceEdgeT.B);
}
}
else if (IsFacePoint(SurfacePoint))
{
const int32 SurfaceTID = SurfacePoint.Position.TriPosition.TriID;
AdjTIDs.Add(SurfaceTID);
}
else
{
checkSlow(IsVertexPoint(SurfacePoint));
const int32 SurfaceVID = SurfacePoint.Position.VertexPosition.VID;
for (int SurfaceTID : SurfaceMesh.VtxTrianglesItr(SurfaceVID))
{
AdjTIDs.Add(SurfaceTID);
}
}
return MoveTemp(AdjTIDs);
}
/**
* Convert a surface point to barycentric coordinates relative to the specified surface triangle.
* NB: this assumes (but does not check) that the surface point is on or adjacent to the surface triangle.
*/
FVector3d AsBarycenteric(const IntrinsicCorrespondenceUtils::FSurfacePoint& SurfacePoint, const int32 SurfaceTID, const FDynamicMesh3& SurfaceMesh)
{
FVector3d BC(0., 0., 0.);
if (IsEdgePoint(SurfacePoint)) // translate an edge point to barycentric coordinates
{
const int32 SurfaceEID = SurfacePoint.Position.EdgePosition.EdgeID;
const double Alpha = SurfacePoint.Position.EdgePosition.Alpha; // Pos(Edge.A) (Alpha) + (1-Alpha) Pos(Edge.B)
checkSlow(SurfaceMesh.GetEdgeT(SurfaceEID).IndexOf(SurfaceTID) != -1);
const FIndex2i SurfaceEdgeV = SurfaceMesh.GetEdgeV(SurfaceEID);
const FIndex3i SurfaceTri = SurfaceMesh.GetTriangle(SurfaceTID);
for (int32 i = 0; i < 3; ++i)
{
if (SurfaceTri[i] == SurfaceEdgeV.A)
{
BC[i] = Alpha;
}
else if (SurfaceTri[i] == SurfaceEdgeV.B)
{
BC[i] = (1. - Alpha);
}
}
}
else if (IsFacePoint(SurfacePoint)) // translate a face point to barycentric coordinates
{
checkSlow(SurfacePoint.Position.TriPosition.TriID == SurfaceTID);
BC = SurfacePoint.Position.TriPosition.BarycentricCoords;
}
else // translate a vertex point to barycentric coordinates
{
const int32 SurfaceVID = SurfacePoint.Position.VertexPosition.VID;
const FIndex3i SurfaceTri = SurfaceMesh.GetTriangle(SurfaceTID);
bool bValid = false;
for (int32 i = 0; i < 3; ++i)
{
if (SurfaceTri[i] == SurfaceVID)
{
BC[i] = 1.;
bValid = true;
}
}
checkSlow(bValid);
}
return BC;
}
bool AreOnSameSurfaceEdge(const IntrinsicCorrespondenceUtils::FSurfacePoint& PointA, const IntrinsicCorrespondenceUtils::FSurfacePoint& PointB, const FDynamicMesh3& Mesh)
{
if (IsFacePoint(PointA) || IsFacePoint(PointB))
{
return false;
}
if (IsVertexPoint(PointA) && IsVertexPoint(PointB))
{
return Mesh.FindEdge(PointA.Position.VertexPosition.VID, PointB.Position.VertexPosition.VID) != FDynamicMesh3::InvalidID;
}
auto TestVertAndEdge = [&Mesh](const IntrinsicCorrespondenceUtils::FSurfacePoint& VertexPoint, const IntrinsicCorrespondenceUtils::FSurfacePoint& EdgePoint)->bool
{
const int32 VID = VertexPoint.Position.VertexPosition.VID;
const int32 EID = EdgePoint.Position.EdgePosition.EdgeID;
const FDynamicMesh3::FEdge& Edge = Mesh.GetEdgeRef(EID);
return (Edge.Vert.IndexOf(VID) != -1);
};
if (IsVertexPoint(PointA))
{
// B must be an edge point.
return TestVertAndEdge(PointA, PointB);
}
else
{
// B must be vert and A is edge
return TestVertAndEdge(PointB, PointA);
}
}
/** array given matrix-like access with row-major layout for a non-square matrix */
struct FNonSqrMat
{
FNonSqrMat(int32 NumCols, int32 NumRows)
: N(NumCols)
, M(NumRows)
{
Data.AddZeroed(N * M);
}
double& operator()(int32 i, int32 j)
{
return Data[RowMajorOffset(i, j)];
}
const double& operator()(int32 i, int32 j) const
{
return Data[RowMajorOffset(i, j)];
}
int32 RowMajorOffset(int32 i, int32 j) const
{
return i * N + j;
}
int32 N;
int32 M;
TArray<double> Data;
};
/**
* Given an underspecified matrix equation (more unknowns than equations) of the form M * Soln = BVector,
* where M is a Nx3 matrix, Soln is an N-dimensional array, and BVector is a 3-dimensional array
*
* this computes the solution that minimizes the quantity |Soln|
*
* The matrix M is specified by providing the three rows of the matrix (Row0, Row1, Row2).
*
* @return false on failure, otherwise the vector Soln will hold the result.
*/
bool ComputeMinNormSolution(const TArray<double>& Row0, const TArray<double>& Row1, const TArray<double>& Row2, const FVector3d& BVector, TArray<double>& Soln)
{
checkSlow(Row0.Num() == Row1.Num() && Row2.Num() == Row1.Num());
const int32 NCols = Row0.Num();
checkSlow(NCols > 2);
Soln.SetNum(NCols);
if (NCols == 3)
{
const FMatrix3d Mat3d(Row0[0], Row0[1], Row0[2],
Row1[0], Row1[1], Row1[2],
Row2[0], Row2[1], Row2[2]);
const double Det = Mat3d.Determinant();
if (TMathUtil<double>::Abs(Det) < TMathUtil<double>::Epsilon)
{
return false;
}
else
{
const FMatrix3d InvMat3d = Mat3d.Inverse();
const FVector3d SolVector3d = InvMat3d * BVector;
Soln[0] = SolVector3d[0];
Soln[1] = SolVector3d[1];
Soln[2] = SolVector3d[2];
}
}
else
{
FNonSqrMat Mat(NCols, 3);
for (int j = 0; j < NCols; ++j)
{
Mat(0, j) = Row0[j];
Mat(1, j) = Row1[j];
Mat(2, j) = Row2[j];
}
// form MMt = Mat * Transpose(Mat)
FMatrix3d MMt(0.);
for (int32 i = 0; i < 3; ++i)
{
FVector3d& Row = (i==0) ? MMt.Row0 : (i==1) ? MMt.Row1 : MMt.Row2 ;
for (int32 j = 0; j < 3; ++j)
{
for (int32 k = 0; k < NCols; ++k)
{
Row[j] += Mat(i, k) * Mat(j, k);
}
}
}
// solve MMt y = b, and compute x = Mt * y
const double Det = MMt.Determinant();
if (TMathUtil<double>::Abs(Det) < TMathUtil<double>::Epsilon)
{
return false;
}
else
{
const FMatrix3d InvMMt = MMt.Inverse();
const FVector3d YVector3d = InvMMt * BVector;
//Soln = Transpose(Mat) * Yvector;
for (int32 j = 0; j < NCols; ++j)
{
Soln[j] = Mat(0, j) * YVector3d[0] + Mat(1, j) * YVector3d[1] + Mat(2, j) * YVector3d[2];
}
}
}
return true;
}
} // end namespace FIntrinsicMeshImplUtils
/**------------------------------------------------------------------------------
* FIntrinsicMesh Methods
*-------------------------------------------------------------------------------*/
UE::Geometry::FIntrinsicMesh::FIntrinsicMesh(const FDynamicMesh3& SurfaceMesh)
: MyBase(SurfaceMesh)
, NormalCoordinates(SurfaceMesh)
{
const int32 MaxVertexID = SurfaceMesh.MaxVertexID();
// add surface position.
IntrinsicVertexPositions.SetNum(MaxVertexID);
// initialize: identify with vertex in Extrinsic Mesh
for (int32 VID = 0; VID < MaxVertexID; ++VID)
{
if (SurfaceMesh.IsVertex(VID))
{
FSurfacePoint VertexSurfacePoint(VID);
IntrinsicVertexPositions[VID] = VertexSurfacePoint;
}
}
}
TArray<UE::Geometry::FIntrinsicMesh::FSurfacePoint> FIntrinsicMesh::TraceEdge(int32 IntrinsicEID, double CoalesceThreshold, bool bReverse) const
{
return FNormalCoordIntrinsicTraceImpl::TraceEdge(*this, IntrinsicEID, CoalesceThreshold, bReverse);
}
TArray<UE::Geometry::FIntrinsicMesh::FEdgeAndCrossingIdx> FIntrinsicMesh::GetImplicitEdgeCrossings(const int32 SurfaceEID, const bool bReverse) const
{
return FNormalCoordSurfaceTraceImpl::TraceSurfaceEdge(*this, SurfaceEID, bReverse);
}
TArray<UE::Geometry::FIntrinsicMesh::FSurfacePoint> FIntrinsicMesh::TraceSurfaceEdge(int32 SurfaceEID, double CoalesceThreshold, bool bReverse) const
{
const FDynamicMesh3& SurfaceMesh = *this->GetExtrinsicMesh();
const FIntrinsicMesh& IntrinsicMesh = *this;
TArray<FEdgeAndCrossingIdx> EdgeAndCrossingIdxs = this->GetImplicitEdgeCrossings(SurfaceEID, bReverse);
const int32 NumEdgeAndXIdx = EdgeAndCrossingIdxs.Num();
TArray<UE::Geometry::FIntrinsicMesh::FSurfacePoint> ResultTraceArray;
if (NumEdgeAndXIdx == 0)
{
return ResultTraceArray;
}
// find start (.A) and end (.B) trace mesh vids for this edge
const FIndex2i OrderedTraceEdgeV = [&]
{
const FIndex2i EdgeV = SurfaceMesh.GetEdgeV(SurfaceEID);
if (bReverse)
{
return FIndex2i(EdgeV.B, EdgeV.A);
}
else
{
return EdgeV;
}
}();
// the start and end are vertices in both SurfaceMesh and IntrinsicMesh, furthermore when vertices exist on both meshes they have the same VID
const FSurfacePoint TraceStartSurfacePoint(OrderedTraceEdgeV.A);
const FSurfacePoint TraceEndSurfacePoint(OrderedTraceEdgeV.B);
ResultTraceArray.Add(TraceStartSurfacePoint);
// due to (possible) intrinsic mesh edge splits, the trace of the surface edge across the intrinsic mesh has to be broken
// into sections that follow split intrinsic mesh edges, and those that cross the faces of intrinsic mesh triangles.
{
FSurfacePoint LocalStartSurfacePoint = TraceStartSurfacePoint;
TArray<int32> IntrinsicEdgesCrossed;
for (int32 i = 1; i < NumEdgeAndXIdx; ++i)
{
const FEdgeAndCrossingIdx& EdgeIDandXIdx = EdgeAndCrossingIdxs[i];
if (EdgeIDandXIdx.CIdx != 0)
{
IntrinsicEdgesCrossed.Add(EdgeIDandXIdx.EID);
}
else if (IntrinsicEdgesCrossed.Num() > 0)
{
// current point is the end point of local face crossing.
// convert to surface point.
const FSurfacePoint LocalEndSurfacePoint( IntrinsicMesh.GetTriangle(EdgeIDandXIdx.TID)[EdgeIDandXIdx.EID]);
// compute crossings
FNormalCoordSurfaceTraceImpl::ConvertEdgesCrossed( LocalStartSurfacePoint, LocalEndSurfacePoint, IntrinsicEdgesCrossed,
IntrinsicMesh, SurfaceMesh, CoalesceThreshold,
[](int32 vid){return FSurfacePoint(vid);}, ResultTraceArray);
// add surface point
ResultTraceArray.Add(LocalEndSurfacePoint);
// empty the temp crossings.
IntrinsicEdgesCrossed.Reset();
LocalStartSurfacePoint = LocalEndSurfacePoint;
}
else
{
// convert to surface point.
const FSurfacePoint LocalEndSurfacePoint(IntrinsicMesh.GetTriangle(EdgeIDandXIdx.TID)[EdgeIDandXIdx.EID]);
// add surface point
ResultTraceArray.Add(LocalEndSurfacePoint);
LocalStartSurfacePoint = LocalEndSurfacePoint;
}
}
// should have processed all edge crossings.
checkSlow(IntrinsicEdgesCrossed.Num() == 0);
// ended on the last vertex.
checkSlow(ResultTraceArray.Last().Position.VertexPosition.VID == OrderedTraceEdgeV.B);
}
return MoveTemp(ResultTraceArray);
}
UE::Geometry::EMeshResult UE::Geometry::FIntrinsicMesh::FlipEdge(int32 EID, FEdgeFlipInfo& EdgeFlipInfo)
{
using namespace FIntrinsicMeshImplUtils;
if (!IsEdge(EID))
{
return EMeshResult::Failed_NotAnEdge;
}
if (IsBoundaryEdge(EID))
{
return EMeshResult::Failed_IsBoundaryEdge;
}
// state before flip, needed when updating the normal coords after the flip
const FIndex2i EdgeT = GetEdgeT(EID);
const FIndex3i TAEIDs = GetTriEdges(EdgeT.A);
const FIndex3i TBEIDs = GetTriEdges(EdgeT.B);
const FIndex2i OppVs = GetEdgeOpposingV(EID);
EMeshResult FlipResult = MyBase::FlipEdge(EID, EdgeFlipInfo);
if (FlipResult == EMeshResult::Ok)
{
// update the normal coords
NormalCoordinates.OnFlipEdge(EdgeT.A, TAEIDs, OppVs.A, EdgeT.B, TBEIDs, OppVs.B, EID);
// if the flip produced an intrinsic edge that is a segment of a surface edge, update the
// coorindate to -1 to help tracing.
if (NormalCoordinates.NormalCoord[EID] == 0 && OppVs.A != OppVs.B)
{
FSurfacePoint SurfacePoints[2] = {GetVertexSurfacePoint(OppVs.A), GetVertexSurfacePoint(OppVs.B)};
if (AreOnSameSurfaceEdge(SurfacePoints[0], SurfacePoints[1], *NormalCoordinates.SurfaceMesh))
{
NormalCoordinates.NormalCoord[EID] = -1;
}
}
}
return FlipResult;
}
UE::Geometry::EMeshResult UE::Geometry::FIntrinsicMesh::PokeTriangle(int32 IntrinsicTID, const FVector3d& BaryCoordinates, FPokeTriangleInfo& PokeInfo)
{
// The intrinsic poke creates 3 new intrinsic edges and "normal coordinates"(the number of crossing surface edges) need to be created for each.
//
// also, "roundabout" data is recored for any new intrinsic edge that is adjacent to an intrinsic vertex that is also a surface vertex.
// this roundabout data indicates the next surface edge when traveling ccw about the vertex from the new edge.
//
// lastly the surface position of the new intrinsic vertex is computed and recored.
//
// note: to compute the surface position of the new intrinsic vertex, the intersection (convex polygon) between the original intrinsic triangle and the surface triangle
// that supports the new vertex is first identified
using namespace FIntrinsicMeshImplUtils;
if (!IsTriangle(IntrinsicTID))
{
return EMeshResult::Failed_NotATriangle;
}
// state before the poke
const FIndex3i OriginalVIDs = GetTriangle(IntrinsicTID);
const FIndex3i OriginalEdges = GetTriEdges(IntrinsicTID);
const FVector3d OriginalTriEdgeLengths = GetTriEdgeLengths(IntrinsicTID);
const int32 IndexOf = VectorUtil::Min3Index(OriginalTriEdgeLengths);
// fail if the smallest side of the triangle is too small
//[todo] consider permuting the triangle so the zero side is the longest.
if (OriginalTriEdgeLengths[IndexOf] < TMathUtilConstants<double>::ZeroTolerance)
{
return EMeshResult::Failed_Unsupported;
}
// unwrap the intrinsic triangle to a 2d plane.
// edge0 runs along the positive x-axis
const FVector2d IntrinsicTri2D[3] = { FVector2d(0.,0.),
FVector2d(OriginalTriEdgeLengths[0], 0.),
ComputeOpposingVert2d(OriginalTriEdgeLengths[0], OriginalTriEdgeLengths[1], OriginalTriEdgeLengths[2]) };
auto AsR2Position = [&IntrinsicTri2D](const FVector3d& BCoords)
{
return BCoords[0] * IntrinsicTri2D[0] + BCoords[1] * IntrinsicTri2D[1] + BCoords[2] * IntrinsicTri2D[2];
};
// position of the new vertex relative to the flattened intrinsic triangle.
const FVector2d PokedPos = AsR2Position(BaryCoordinates);
// Utility: converts distance along a directed edge of an the intrinsic triangle to barycentric coords.
auto EdgeDistanceToBaryCoords = [&OriginalTriEdgeLengths](int32 TriSide, double Distance)
{
const double EdgeLength = OriginalTriEdgeLengths[TriSide];
const double Alpha = (EdgeLength > TMathUtilConstants<double>::ZeroTolerance) ? Distance / EdgeLength : 0.5;
FVector3d Result(0., 0., 0.);
Result[TriSide] = (1. - Alpha);
Result[(TriSide + 1) % 3] = Alpha;
return Result;
};
// -- find the surface edges that cross the intrinsic triangle.
// for each side of the intrinsic triangle, generate a list of places where the surfaces edge cross the intrinsic edge
// storing the location as a FPointCorrespondence
// point described both relative to the intrinsic triangle and to the surface mesh.
struct FPointCorrespondence
{
FVector2d LocalPos; // position relative to flattened intrinsic triangle
FVector3d IntrinsicBC; // barycentric coordinates relative to intrinsic triangle ( duplicates info in LocalPos, but easier to work with)
FSurfacePoint SurfacePos; // position relative to surface mesh
};
// for each directed side the intrinsic triangle, recored all the surface edge crossings and their relative locations along the side.
TMap<int32, FPointCorrespondence> SurfaceEdgeXings[3];
{
auto GetSurfaceEdgeCrossingLocations = [&](const int32 Side)
{
const int32 IntrinsicEID = OriginalEdges[Side];
const FDynamicMesh3& SurfaceMesh = *NormalCoordinates.SurfaceMesh;
const double CoalesceThreshold = 0.;
const bool bReverse = (GetEdgeT(IntrinsicEID).A != IntrinsicTID);
// sequence of surface points corresponding to surface edges intersecting this intrinsic edge.
const TArray<FSurfacePoint> EdgeAsSurfacePoints = TraceEdge(IntrinsicEID, CoalesceThreshold, bReverse);
// record the surface point and the distance along this triangle side to the intersection
// key with the surface edge id.
TMap<int32, FPointCorrespondence> CrossingLocations;
const int32 NumSPoints = EdgeAsSurfacePoints.Num();
if (NumSPoints > 0)
{
bool bTmp;
FVector3d Positions[2];
Positions[0] = AsR3Position(EdgeAsSurfacePoints[0], SurfaceMesh, bTmp);
int32 Cur = 1;
double AccumulatedDistance = 0.;
for (int i = 1; i < NumSPoints; ++i)
{
const FSurfacePoint& SurfacePoint = EdgeAsSurfacePoints[i];
Positions[Cur] = AsR3Position(SurfacePoint, SurfaceMesh, bTmp);
const double LineElementLength = (Positions[Cur] - Positions[1 - Cur]).Length();
AccumulatedDistance += LineElementLength;
if (SurfacePoint.PositionType == IntrinsicCorrespondenceUtils::FSurfacePoint::EPositionType::Edge)
{
const int32 SurfaceEID = SurfacePoint.Position.EdgePosition.EdgeID;
const FVector3d IntrinsicBaryCoords = EdgeDistanceToBaryCoords(Side, AccumulatedDistance);
FPointCorrespondence XingCorrespondence = {AsR2Position(IntrinsicBaryCoords), IntrinsicBaryCoords, SurfacePoint};
CrossingLocations.Add(SurfaceEID, XingCorrespondence);
}
Cur = 1 - Cur;
}
}
return MoveTemp(CrossingLocations);
};
for (int32 i = 0; i < 3; ++i)
{
SurfaceEdgeXings[i] = GetSurfaceEdgeCrossingLocations(i);
}
}
// the intersection of a surface edge with the intrinsic triangle.
struct FSurfaceEdgeSegment
{
// end points where this surface edge intersects the boundary of the intrinsic triangle.
FPointCorrespondence P0;
FPointCorrespondence P1;
// corresponding surface mesh edge id
int32 SurfaceEID = -1;
};
// after the poke, new intrinsic edges will connect the new vertex to the corners of the original triangle.
// number these as {0, 1, 2} according to the corners of the IntrinsicTri2D
// compute "normal coordinates", i.e. find the number of times each new intrinsic edge is intersected by surface mesh edge segments
// additionally, (for each new intrinsic edge) identify the intersecting surface edge that was closest to the new vertex.
int32 NewEdgeNormCoords[3] = { 0, 0, 0 };
FSurfaceEdgeSegment ClosestSurfaceEdgeSegment[3]; // per new intrinsic edge
{
auto UpdateNormCoordsAndClosest = [&NewEdgeNormCoords, &ClosestSurfaceEdgeSegment](const FPointCorrespondence& P0, const FPointCorrespondence& P1, int32 SurfaceEID, int32 ZeroOneOrTwo)
{
NewEdgeNormCoords[ZeroOneOrTwo] += 1;
FSurfaceEdgeSegment& ClosestSegment = ClosestSurfaceEdgeSegment[ZeroOneOrTwo];
const bool bHaveValidClosest = (ClosestSegment.SurfaceEID != -1);
if (bHaveValidClosest)
{
// by construction the surface edge segments can not intersect in the face of an intrinsic triangle
// so we need only check a single point of the old segment against the one defined by P0 to P1
const FVector2d OldSegmentCenter = 0.5 * (ClosestSegment.P0.LocalPos + ClosestSegment.P1.LocalPos);
const double SideTest = UE::Geometry::Orient(P0.LocalPos, P1.LocalPos, OldSegmentCenter);
if (SideTest > 0) // old surface segment is to the right of this new one segment
{
ClosestSegment.P0 = P0;
ClosestSegment.P1 = P1;
ClosestSegment.SurfaceEID = SurfaceEID;
}
}
else
{
// just update since this is the first intersecting surface edge
ClosestSegment.P0 = P0;
ClosestSegment.P1 = P1;
ClosestSegment.SurfaceEID = SurfaceEID;
}
};
for (int32 Side = 0; Side < 3; ++Side)
{
const int32 NextSide = (Side + 1) % 3;
const int32 NextNextSide = (Side + 2) % 3;
for (const TPair<int32, FPointCorrespondence>& XingPair : SurfaceEdgeXings[Side])
{
const int32& SurfaceEID = XingPair.Key;
const FPointCorrespondence& P0 = XingPair.Value;
if ( FPointCorrespondence* P1Ptr = SurfaceEdgeXings[NextSide].Find(SurfaceEID)) // the surface edge exits the next side
{
const FPointCorrespondence& P1 = *P1Ptr;
const double SignedArea = UE::Geometry::Orient(P0.LocalPos, P1.LocalPos, PokedPos); //same as (P1-P0)X(PokedPos - P0)
if (SignedArea <= 0) // surface segment crosses two new intrinsic edges.
{
// [todo] better treatment for == case?
UpdateNormCoordsAndClosest(P0, P1, SurfaceEID, Side);
UpdateNormCoordsAndClosest(P0, P1, SurfaceEID, NextNextSide);
}
if (SignedArea > 0) // surface segment crosses one new intrinsic edges.
{
UpdateNormCoordsAndClosest(P1, P0, SurfaceEID, NextSide);
}
}
else if (SurfaceEdgeXings[NextNextSide].Contains(SurfaceEID) == false) // the surface edge must exit the opp vertex
{
FVector3d P1BC(0., 0., 0.); P1BC[NextNextSide] = 1.;
const FPointCorrespondence P1 = { IntrinsicTri2D[NextNextSide], P1BC, IntrinsicVertexPositions[OriginalVIDs[NextNextSide]] };;
const double SignedArea = UE::Geometry::Orient(P0.LocalPos, P1.LocalPos, PokedPos); // same as (P1-P0)X(PokedPos - P0)
if (SignedArea <= 0) // surface segment crosses one new intrinsic edges.
{
// [todo] better treatment for == case?
UpdateNormCoordsAndClosest(P0, P1, SurfaceEID, Side);
}
if (SignedArea > 0) // surface segment crosses one new intrinsic edges.
{
UpdateNormCoordsAndClosest(P1, P0, SurfaceEID, NextSide);
}
}
}
}
}
// intersection of the intrinsic mesh triangle (prior to poke) and the surface mesh triangle that ultimately supports the new vertex
// forms a convex polygon. Convert the vertices of this polygon to FPointCorresponce.
const TArray<FPointCorrespondence> BoundingConvexPolyVerts = [&]
{
TArray<FPointCorrespondence> TmpBoundaryPoints;
// method to add Correspondence points. Makes sure we don't add a point twice
// note, only need to check surface verts due to our usage and the fact surface edges only meet at surface verts.
TSet<int32> VisitedSurfaceVerts;
auto AddPointCorrespondence = [&](const FPointCorrespondence& PointCorrespondence)
{
const FSurfacePoint& SP = PointCorrespondence.SurfacePos;
if (IsVertexPoint(SP))
{
const int32 SurfaceVID = SP.Position.VertexPosition.VID;
if (!VisitedSurfaceVerts.Contains(SurfaceVID))
{
TmpBoundaryPoints.Add(PointCorrespondence);
VisitedSurfaceVerts.Add(SurfaceVID);
}
}
else
{
TmpBoundaryPoints.Add(PointCorrespondence);
}
};
TSet<int32> VisitedSurfaceEdges;
for (int32 i = 0; i < 3; ++i)
{
// if it exists, this surface edge blocks
// the new intrinsic vertex's view of the i-th original tri corner.
const FSurfaceEdgeSegment& SurfaceEdgeSegment = ClosestSurfaceEdgeSegment[i];
const int32 SurfaceEdgeID = SurfaceEdgeSegment.SurfaceEID;
if (SurfaceEdgeID == -1) // no edge blocks..
{
const FVector2d& CornerLocalPos = IntrinsicTri2D[i];
const int32 VID = OriginalVIDs[i];
const FSurfacePoint& CornerSP = IntrinsicVertexPositions[VID];
FVector3d IntrinsicBC(0., 0., 0.);
IntrinsicBC[i] = 1.;
FPointCorrespondence PointCorrespondence = { CornerLocalPos, IntrinsicBC, CornerSP };
AddPointCorrespondence(PointCorrespondence);
}
else
{
if (!VisitedSurfaceEdges.Contains(SurfaceEdgeID))
{
VisitedSurfaceEdges.Add(SurfaceEdgeID);
AddPointCorrespondence(SurfaceEdgeSegment.P0);
AddPointCorrespondence(SurfaceEdgeSegment.P1);
}
}
}
return MoveTemp(TmpBoundaryPoints);
}();
// Identify the surface triangle that is common to the bounding convex polygon verts.
const int32 SurfaceTID = [&]
{
const FDynamicMesh3& SurfaceMesh = *NormalCoordinates.SurfaceMesh;
// special case: are the points just the corners of a surface triangle?
if (BoundingConvexPolyVerts.Num() == 3)
{
if ( IsVertexPoint(BoundingConvexPolyVerts[0].SurfacePos)
&& IsVertexPoint(BoundingConvexPolyVerts[1].SurfacePos)
&& IsVertexPoint(BoundingConvexPolyVerts[2].SurfacePos) )
{
int32 SurfaceVIDs[3] = { BoundingConvexPolyVerts[0].SurfacePos.Position.VertexPosition.VID,
BoundingConvexPolyVerts[1].SurfacePos.Position.VertexPosition.VID,
BoundingConvexPolyVerts[2].SurfacePos.Position.VertexPosition.VID
};
return SurfaceMesh.FindTriangle(SurfaceVIDs[0], SurfaceVIDs[1], SurfaceVIDs[2]);
}
}
// find surface triangles corresponding to one of the surface points.
// note: a surface point will correspond to 1, 2, or many surface triangles (face point, edge point, or vertex point).
const TArray<int32> CandidateTIDs = [&]
{
TArray<int32> TmpTIDs;
// are any of the surface point already on a surface triangle face?
int32 TID = -1;
for (const FPointCorrespondence& BoundaryPoint : BoundingConvexPolyVerts)
{
const FSurfacePoint& SurfacePos = BoundaryPoint.SurfacePos;
if (IsFacePoint(SurfacePos))
{
TID = SurfacePos.Position.TriPosition.TriID;
break;
}
}
if (TID != -1)
{
TmpTIDs.Add(TID);
}
else
{
const FSurfacePoint& SurfacePos = BoundingConvexPolyVerts[0].SurfacePos;
// get all the surface triangles adjacent to this surface point
TmpTIDs = GetAdjacentTriangles(SurfacePos, SurfaceMesh);
}
return MoveTemp(TmpTIDs);
}();
// determine which candidate triangle is actually adjacent to all the surface points.
const int32 MutuallyAdjacentTID = [&]
{
for (int32 CandidateTID : CandidateTIDs)
{
bool bIsAdjacentToAll = true;
// check adjacency to the boundary points.
for (const FPointCorrespondence& BoundaryPoint : BoundingConvexPolyVerts)
{
const FSurfacePoint& SurfacePos = BoundaryPoint.SurfacePos;
bIsAdjacentToAll = bIsAdjacentToAll && IsOnSurfaceTriangle(SurfacePos, CandidateTID, SurfaceMesh);
}
if (bIsAdjacentToAll)
{
return CandidateTID;
}
}
return -1;
}();
return MutuallyAdjacentTID;
}();
if (SurfaceTID == -1)
{
return EMeshResult::Failed_Unsupported;
}
// construct the surface barycentric coords for the poked position.
const FVector3d PokedSurfaceBaryCoords = [&]
{
const int32 NumBoundaryPts = BoundingConvexPolyVerts.Num();
// In the barycentric space of the intrinsic triangle:
// find the (min norm) linear combination convex-poly vertices that is equivalent to the new vertex location
TArray<double> MinNormSolution;
TArray<double> MatRows[3];
for (int32 i = 0; i < 3; ++i)
{
MatRows[i].AddUninitialized(NumBoundaryPts);
for (int32 j = 0; j < NumBoundaryPts; ++j)
{
const FVector3d& IntrinsicBarycentric = BoundingConvexPolyVerts[j].IntrinsicBC;
MatRows[i][j] = IntrinsicBarycentric[i];
}
}
const bool bValidInterpolation = ComputeMinNormSolution(MatRows[0], MatRows[1], MatRows[2], BaryCoordinates, MinNormSolution);
// convert the surface points to barycentric coords relative to the surface triangle.
TArray<FVector3d> SurfaceBaryCoords;
{
const FDynamicMesh3& SurfaceMesh = *NormalCoordinates.SurfaceMesh;
SurfaceBaryCoords.Reserve(NumBoundaryPts);
for (const FPointCorrespondence& BoundaryPoint : BoundingConvexPolyVerts)
{
const FSurfacePoint& SurfacePos = BoundaryPoint.SurfacePos;
SurfaceBaryCoords.Add(AsBarycenteric(SurfacePos, SurfaceTID, SurfaceMesh));
}
}
// interpolate the surface barycentric coordinates (using the coefficients from the intrinsic barycenterics)
// to construct the surface barycentric coordinates of the new vertex location.
// note: if interpolation failed just use the center of the polygon.
FVector3d SurfaceBarycoods(0., 0., 0.);
if (bValidInterpolation)
{
for (int32 i = 0; i < NumBoundaryPts; ++i)
{
SurfaceBarycoods += MinNormSolution[i] * SurfaceBaryCoords[i];
}
}
else
{
// just use the "center" of the convex polygon
for (int32 i = 0; i < NumBoundaryPts; ++i)
{
SurfaceBarycoods += SurfaceBaryCoords[i];
}
SurfaceBarycoods *= 1. / double(NumBoundaryPts);
}
return SurfaceBarycoods;
}();
// surface point for the new (poked) vertex
const FSurfacePoint PokedSurfacePoint(SurfaceTID, PokedSurfaceBaryCoords);
// update the topology.
// Add a new vertex and faces to the IntrinsicMesh.
// Note: the r3 position will be wrong initially since this poke will just interpolate the corners of the intrinsic tri.
// we fix this position as the last step in this function
EMeshResult PokeResult = MyBase::PokeTriangle(IntrinsicTID, BaryCoordinates, PokeInfo);
if (PokeResult != EMeshResult::Ok)
{
return PokeResult;
}
FIndex3i NewTris(IntrinsicTID, PokeInfo.NewTriangles[0], PokeInfo.NewTriangles[1]);
const int32 NewVID = PokeInfo.NewVertex;
// Need to update intrinsic information for the 3 triangles that resulted from the poke
// 1) a)update the number of surface edge crossings for each new intrinsic edge
// b)update the roundabout data for any new intrinsic edge that is adj to a surface vertex
// 2) update the surface position of the new vertex.
{
// set/update number of surface edge crossings for the new edges.
{
// original tri is and verts (a, b,c) and edges (a2b, b2c, c2a)
// is updated by the poke to have verts (a, b, new) and edges (a2b, b2new, new2a)
const FIndex3i T0EIDs = GetTriEdges(NewTris[0]);
const FIndex3i T1EIDs = GetTriEdges(NewTris[1]);
// set the correct number of surface edges the cross each new intrinsic edge.
NormalCoordinates.NormalCoord.InsertAt(NewEdgeNormCoords[0], T0EIDs[2]); // a2new
NormalCoordinates.NormalCoord.InsertAt(NewEdgeNormCoords[2], T1EIDs[1]); // b2new
NormalCoordinates.NormalCoord.InsertAt(NewEdgeNormCoords[1], T0EIDs[1]); // c2new
}
// update the roundabout information.
{
// edge order: a2b, b2c, c2a
const FIndex3i OriginalRO = NormalCoordinates.RoundaboutOrder[IntrinsicTID];
// copy the roundabout information to the new triangles with default -1 for the new directed edges.
FIndex3i NewTrisRO[3] = { FIndex3i(OriginalRO[0], -1, -1) // NewTri[0] edge order: a2b, b2new, new2a
,FIndex3i(OriginalRO[1], -1, -1) // NewTri[1] edge order: b2c, c2new, new2b
,FIndex3i(OriginalRO[2], -1, -1) // NewTri[2] edge order: c2a, a2new, new2c
};
// update roundabout order for any new directed edge that starts at an implicit vertex that is also a surface vertex
// Note: by construction the new intrinsic vertex does not correspond to a vertex on the surface mesh so we need only consider a2new, b2new, c2new.
const FSurfacePoint OriginalTriSPs[3] = { IntrinsicVertexPositions[OriginalVIDs[0]],
IntrinsicVertexPositions[OriginalVIDs[1]],
IntrinsicVertexPositions[OriginalVIDs[2]] };
for (int32 i = 0; i < 3; ++i)
{
if (IsVertexPoint(OriginalTriSPs[i]))
{
const FIndex3i TriEIDs = GetTriEdges(NewTris[i]);
const int32 Radj = NewTrisRO[i][0]; // {R_ab, R_bc, R_ca } for i = 0, 1, or 2
const int32 Eopp = NormalCoordinates.NumCornerEmanatingRefEdges(TriEIDs, 0); // {Ebn_a, Ecn_b, Ean_c} for i = 0, 1, or 2
const int32 Rnew = (Radj + Eopp) % NormalCoordinates.RefVertDegree[OriginalVIDs[i]];
NewTrisRO[(i + 2) % 3][1] = Rnew;
}
}
NormalCoordinates.RoundaboutOrder.InsertAt(NewTrisRO[0], NewTris[0]);
NormalCoordinates.RoundaboutOrder.InsertAt(NewTrisRO[1], NewTris[1]);
NormalCoordinates.RoundaboutOrder.InsertAt(NewTrisRO[2], NewTris[2]);
}
}
// update the position of the new vertex ( R3 position and the surface point )
bool bIsValid;
const FVector3d R3Pos = IntrinsicCorrespondenceUtils::AsR3Position(PokedSurfacePoint, *NormalCoordinates.SurfaceMesh, bIsValid);
Vertices[NewVID] = R3Pos;
IntrinsicVertexPositions.InsertAt(PokedSurfacePoint, NewVID);
return EMeshResult::Ok;
}
UE::Geometry::EMeshResult UE::Geometry::FIntrinsicMesh::SplitEdge(int32 EdgeAB, FEdgeSplitInfo& SplitInfo, double SplitParameterT)
{
using namespace FIntrinsicMeshImplUtils;
if (!IsEdge(EdgeAB))
{
return EMeshResult::Failed_NotAnEdge;
}
const FDynamicMesh3& SurfaceMesh = *NormalCoordinates.SurfaceMesh;
const FEdge OriginalEdge = GetEdge(EdgeAB);
// is the target intrinsic edge equivalent to a segment of a surface edge?
const bool bIsOnSurfaceEdge = NormalCoordinates.IsSurfaceEdgeSegment(EdgeAB);
// say new vertex is f.
const int32 TID0 = OriginalEdge.Tri[0];
const int32 IndexOfe = GetTriEdges(TID0).IndexOf(EdgeAB);
const int32 TID1 = OriginalEdge.Tri[1];
const bool bIsBoundary = (TID1 == -1);
const int32 IndexOf1e = (!bIsBoundary) ? GetTriEdges(TID1).IndexOf(EdgeAB) : -1;
// Info about the original T0 tri, reordered to make the split edge the first edge..
const FIndex3i OriginalT0VIDs = Permute(IndexOfe, GetTriangle(TID0));
const FIndex3i OriginalT0Edges = Permute(IndexOfe, GetTriEdges(TID0));
const FVector3d OriginalT0EdgeLengths = Permute(IndexOfe, GetTriEdgeLengths(TID0)); // as (|ab|, |bc|, |ca|)
// which, if any, of the original intrinsic edges are coincident with surface edges
// in the order { edgeAB, edgeBC, edgeCA, edgeDB }
const bool Kroneckers[4] = { NormalCoordinates.IsSurfaceEdgeSegment(OriginalT0Edges[0]),
NormalCoordinates.IsSurfaceEdgeSegment(OriginalT0Edges[1]),
NormalCoordinates.IsSurfaceEdgeSegment(OriginalT0Edges[2]),
(bIsBoundary) ? false : NormalCoordinates.IsSurfaceEdgeSegment(Permute(IndexOf1e, GetTriEdges(TID1))[2])};
const double IntrinsicEdgeSplitDistance = SplitParameterT * OriginalT0EdgeLengths[0];
// description of surface edge intersection with intrinsic edge AB
struct FEdgeDistanceCorrespondence
{
int32 SurfaceEID = -1; // surface edge
double DistOnEdge; // distance measured along the intrinsic edge
FSurfacePoint SurfacePoint; // location on the surface mesh
};
auto AsR3Pos = [&SurfaceMesh](const FSurfacePoint& SP)
{
bool bTemp;
return AsR3Position(SP, SurfaceMesh, bTemp);
};
// -- prior to actually doing the edge split, we find the two points (surface edge intersections) that bracket the proposed edge split location
// and pre-compute the normal coordinates for the 4 edges that are changed by the split
// initialize the bounding points with the ends of the edge being split.
FEdgeDistanceCorrespondence BoundingPoints[2] = { {-1, 0., IntrinsicVertexPositions[OriginalT0VIDs[0]]},
{-1, OriginalT0EdgeLengths[0], IntrinsicVertexPositions[OriginalT0VIDs[1]]} };
int32 UpdatedNormCoord = 0; // after split edgeAB becomes edge [a, f]
int32 NewEdgeNormCoords[3] = { 0, 0, 0}; //N_fb, N_fc, N_fd
{
if (NormalCoordinates.NumEdgeCrossing(EdgeAB) == 0) // no surface edges cross the intrinsic edge being split
{
// potentially propagating -1 from NormalCoord[EdgeAB] may explicitly mark these as a segment of EdgeAB
UpdatedNormCoord = NormalCoordinates.NormalCoord[EdgeAB]; // becomes edgeAF
NewEdgeNormCoords[0] = NormalCoordinates.NormalCoord[EdgeAB]; // new edge, edgeFB
NewEdgeNormCoords[1] = FMath::Max(NormalCoordinates.NumEdgeCrossing(OriginalT0Edges[1]), NormalCoordinates.NumEdgeCrossing(OriginalT0Edges[2]));
// poke makes t0 = {a, f, c} and t2 = { f, b, c} ( note: t1 = {f, a, d} t3 = f, d, b} don't exist in the boundary edge case )
if (!bIsBoundary)
{
// Info about the original T1 tri, reordered to make the split edge the first edge..
const FIndex3i OriginalT1Edges = Permute(IndexOf1e, GetTriEdges(TID1));
const FVector3d OriginalT1EdgeLengths = Permute(IndexOf1e, GetTriEdgeLengths(TID1)); // as (|ba|, |ad|, |db})
NewEdgeNormCoords[2] = FMath::Max(NormalCoordinates.NumEdgeCrossing(OriginalT1Edges[1]), NormalCoordinates.NumEdgeCrossing(OriginalT1Edges[2]));
}
}
else
{
const int32 IntrinsicEID = OriginalT0Edges[0];
const FIndex3i OriginalT1Edges = (TID1 != -1) ? Permute(IndexOf1e, GetTriEdges(TID1)) : FIndex3i(-1, -1, -1);
// sequence of surface points corresponding to surface edges intersecting intrinsic edge ab.
// ordered relative to OriginalEdge.Tri[0];
const TArray<FSurfacePoint> ABEdgeAsSurfacePoints = [&]
{
const double CoalesceThreshold = 0.;
const bool bReverse = false;
return TraceEdge(IntrinsicEID, CoalesceThreshold, bReverse);
}();
auto GetExitEdge = [&](const int32 TriID, const int32 P)
{
int32 ExitEID = -1;
if (TriID != -1)
{
const TTuple<int32, int32> ExitEIDandP = FNormalCoordSurfaceTraceImpl::GetCrossingExit(*this, NormalCoordinates, TriID, EdgeAB, P);
ExitEID = (ExitEIDandP.Get<1>() != 0) ? ExitEIDandP.Get<0>() : -1;
}
return ExitEID;
};
// account for surface edges that cross the intrinsic edgeAB at the endpoints and the new edges, ie edgeFC (or edgeFD)
{
const int32 Ebc_a = NormalCoordinates.NumCornerEmanatingRefEdges(OriginalT0Edges, 0); // surface edges that start at 'a' and exit side bc
const int32 Eca_b = NormalCoordinates.NumCornerEmanatingRefEdges(OriginalT0Edges, 1); // surface edges that start at 'b' and exit side ca
NewEdgeNormCoords[1] += Eca_b + Eca_b; // new edge - edgeFC
if (!bIsBoundary)
{
const int32 Ead_b = NormalCoordinates.NumCornerEmanatingRefEdges(OriginalT1Edges, 0); // surface edges that start at 'b' and exit side ad
const int32 Edb_a = NormalCoordinates.NumCornerEmanatingRefEdges(OriginalT1Edges, 1); // surface edges that start at 'a' and exit side db
NewEdgeNormCoords[2] += Ead_b + Edb_a; // new edge - edgeFD
}
}
// count the number of surface edges that cross both edgeAB and the edgeFC, and those that cross edgeAB and edgeFD
// Also identify the two surface edge crossing of edgeAB that bracket the vertex produced by the split.
{
// double buffer.
int32 Cur = 1;
FVector3d Positions[2];
Positions[0] = AsR3Pos(ABEdgeAsSurfacePoints[0]);
const int32 NumSPoints = ABEdgeAsSurfacePoints.Num();
double AccumulatedDistance = 0;
for (int32 i= 1; i < NumSPoints - 1; ++i) // the first and last points are the implicit edge start/end.
{
const FSurfacePoint& SurfacePoint = ABEdgeAsSurfacePoints[i];
Positions[Cur] = AsR3Pos(SurfacePoint);
const double LineElementLength = (Positions[Cur] - Positions[1 - Cur]).Length();
AccumulatedDistance += LineElementLength;
Cur = 1 - Cur; // swap double buffer.
// which side does this surface edge exit?
const int32 T0ExitEID = GetExitEdge(TID0, i);
const int32 T1ExitEID = GetExitEdge(TID1, NumSPoints - i);
if (AccumulatedDistance < IntrinsicEdgeSplitDistance) // crossing to left of split
{
UpdatedNormCoord += 1; // edgeAF
checkSlow(IsEdgePoint(SurfacePoint));
int32 SurfaceEID = SurfacePoint.Position.EdgePosition.EdgeID;
BoundingPoints[0] = {SurfaceEID, AccumulatedDistance, SurfacePoint};
if (OriginalT0Edges[1] == T0ExitEID)
{
NewEdgeNormCoords[1] +=1; // new edge - edgeFC
}
if (T1ExitEID != -1 && OriginalT1Edges[2] == T1ExitEID)
{
NewEdgeNormCoords[2] += 1; // new edge - edgeFD
}
}
else // crossing to right of split
{
NewEdgeNormCoords[0] += 1; // edgeFB
if (AccumulatedDistance < BoundingPoints[1].DistOnEdge)
{
checkSlow(IsEdgePoint(SurfacePoint));
int32 SurfaceEID = SurfacePoint.Position.EdgePosition.EdgeID;
BoundingPoints[1] = { SurfaceEID, AccumulatedDistance, SurfacePoint };
}
if (OriginalT0Edges[2] == T0ExitEID)
{
NewEdgeNormCoords[1] += 1; // new edge - edgeFC
}
if (T1ExitEID != -1 && OriginalT1Edges[1] == T1ExitEID)
{
NewEdgeNormCoords[2] += 1; // new edge - edgeFD
}
}
}
}
}
}
// do the actual edge split
UE::Geometry::EMeshResult SplitResult = MyBase::SplitEdge(EdgeAB, SplitInfo, SplitParameterT);
if (SplitResult != EMeshResult::Ok)
{
return SplitResult;
}
// update the number of edge crossings for the edge configuration resulting from the split
// original edge[a,b] is now [a,f] new edges are [f,b], [f,c] and [f,d]
// Note: this must be done before updating the roundabout information
NormalCoordinates.NormalCoord[SplitInfo.OriginalEdge] = UpdatedNormCoord;
NormalCoordinates.NormalCoord.InsertAt(NewEdgeNormCoords[0], SplitInfo.NewEdges[0]);
NormalCoordinates.NormalCoord.InsertAt(NewEdgeNormCoords[1], SplitInfo.NewEdges[1]);
if (!bIsBoundary)
{
NormalCoordinates.NormalCoord.InsertAt(NewEdgeNormCoords[2], SplitInfo.NewEdges[2]);
}
// fix the roundabout orders
if (!bIsBoundary) // for 4 triangles.
{
const FIndex3i OriginalT0RO = Permute(IndexOfe, NormalCoordinates.RoundaboutOrder[TID0]); // {ROa2b, ROb2c, ROc2a}
const FIndex3i OriginalT1RO = Permute(IndexOf1e, NormalCoordinates.RoundaboutOrder[TID1]); // {ROb2a, ROa2d, ROd2b}
const FIndex3i OriginalT1VIDs = Permute(IndexOf1e, GetTriangle(TID1)); // {b, a, d}
// initialize with the correct RO information for the outer diamond c2a, a2d, b2c, d2b
const int32 Rc2a = OriginalT0RO[2];
const int32 Ra2d = OriginalT1RO[1];
const int32 Rd2b = OriginalT1RO[2];
const int32 Rb2c = OriginalT0RO[1];
const int32 Ra2b = OriginalT0RO[0];
// T0 and T1 after split {a,f, c} and {f, a, d}.. although permuted to the original order.
FIndex3i UpdatedRO[2] = { FIndex3i(-1, -1, Rc2a),
FIndex3i(-1, Ra2d, -1) };
// tris {f,b, c} and {f, d, b}
FIndex3i NewTrisRO[2] = { FIndex3i(-1, Rb2c, -1),
FIndex3i(-1, Rd2b, -1) };
// correct the roundabout order consistent with the new point f being just inside the original T0
// directed edge a2f RO: UpdatedRO[0][0]
{
const int32 A = OriginalT0VIDs[0];
if (IsVertexPoint(IntrinsicVertexPositions[A]))
{
UpdatedRO[0][0] = Ra2b;
}
}
// directed edge b2f RO: NewTriRO[1][2]
{
const int32 B = OriginalT0VIDs[1];
if (IsVertexPoint(IntrinsicVertexPositions[B]))
{
const FIndex3i NewTri0Edges = GetTriEdges(SplitInfo.NewTriangles[0]);
const int32 Kronecker_bc = (int32)Kroneckers[1];
const int32 Ecf_b = NormalCoordinates.NumCornerEmanatingRefEdges(NewTri0Edges, 1); // num surface edges coming from b and crossing edgeCF.
NewTrisRO[1][2] = (Rb2c + Ecf_b + Kronecker_bc) % NormalCoordinates.RefVertDegree[B];
}
}
// directed edges c2f RO: NewTriRO[0][2]
{
const int32 C = OriginalT0VIDs[2];
if (IsVertexPoint(IntrinsicVertexPositions[C]))
{
const FIndex3i T0UpdatedEdges = Permute(IndexOfe, GetTriEdges(TID0));
const int32 Kronecker_ca = (int32)Kroneckers[2];
const int32 Eaf_c = NormalCoordinates.NumCornerEmanatingRefEdges(T0UpdatedEdges, 2); // num surface edges coming from c and crossing edgeAF.
const int32 Rc2f = (Rc2a + Eaf_c + Kronecker_ca) % NormalCoordinates.RefVertDegree[C];
NewTrisRO[0][2] = Rc2f;
}
}
// directed edge d2f RO: UpdatedRO[1][2]
{
const int32 D = OriginalT1VIDs[2];
if (IsVertexPoint(IntrinsicVertexPositions[D]))
{
const FIndex3i NewTri1Edges = GetTriEdges(SplitInfo.NewTriangles[1]);
const int32 Kronecker_bd = (int32)Kroneckers[3];
const int32 Ebf_d = NormalCoordinates.NumCornerEmanatingRefEdges(NewTri1Edges, 1); // num surface edges coming from d and crossing edgeBF
const int32 Rd2f = (Rd2b + Ebf_d + Kronecker_bd) % NormalCoordinates.RefVertDegree[D];
UpdatedRO[1][2] = Rd2f;
}
}
NormalCoordinates.RoundaboutOrder[TID0] = Permute( (3-IndexOfe)%3, UpdatedRO[0]);
NormalCoordinates.RoundaboutOrder[TID1] = Permute( (3-IndexOf1e)%3, UpdatedRO[1]);
NormalCoordinates.RoundaboutOrder.InsertAt(NewTrisRO[0], SplitInfo.NewTriangles[0]);
NormalCoordinates.RoundaboutOrder.InsertAt(NewTrisRO[1], SplitInfo.NewTriangles[1]);
}
else // only 2 triangles.
{
const FIndex3i OriginalT0RO = Permute(IndexOfe, NormalCoordinates.RoundaboutOrder[TID0]);
// initialize with the correct RO information for the outer diamond c2a, a2d, b2c, d2b
const int32 Ra2b = OriginalT0RO[0];
const int32 Rb2c = OriginalT0RO[1];
const int32 Rc2a = OriginalT0RO[2];
FIndex3i UpdatedRO = FIndex3i(-1, -1, Rc2a); // {ROa2f, ROf2c, ROc2a}
FIndex3i NewTriRO = FIndex3i(-1, Rb2c, -1); // {Rof2b, ROb2c, ROc2f} tris {f,b, c}
// directed edge a2f RO: UpdatedRO[0][0]
{
const int32 A = OriginalT0VIDs[0];
if (IsVertexPoint(IntrinsicVertexPositions[A]))
{
UpdatedRO[0] = Ra2b;
}
}
// directed edges c2f RO: NewTriRO[2]
{
const int32 C = OriginalT0VIDs[2];
if(IsVertexPoint(IntrinsicVertexPositions[C]))
{
const FIndex3i T0UpdatedEdges = Permute(IndexOfe, GetTriEdges(TID0));
const int32 Kronecker_ca = (int32)Kroneckers[2];
const int32 Eaf_c = NormalCoordinates.NumCornerEmanatingRefEdges(T0UpdatedEdges, 2); // num surface edges coming from c and crossing edgeAF.
const int32 Rc2f = (Rc2a + Eaf_c + Kronecker_ca) % NormalCoordinates.RefVertDegree[C];
NewTriRO[2] = Rc2f;
}
}
NormalCoordinates.RoundaboutOrder[TID0] = Permute((3-IndexOfe)%3, UpdatedRO);
NormalCoordinates.RoundaboutOrder.InsertAt(NewTriRO, SplitInfo.NewTriangles[0]);
}
// update the location of the new vertex, both as surface position and R3.
{
// the two surface points on the split edge that bracket the new vertex
const FSurfacePoint& LeftSP = BoundingPoints[0].SurfacePoint;
const FSurfacePoint& RightSP = BoundingPoints[1].SurfacePoint;
if (bIsOnSurfaceEdge)
{
// the bounding surface points are either surface vert points or surface edge points
// find the surface edge
const int32 SurfaceEID = [&]
{
int32 SurfaceEID = -1;
if (IsEdgePoint(LeftSP))
{
SurfaceEID = LeftSP.Position.EdgePosition.EdgeID;
}
else if (IsEdgePoint(RightSP))
{
SurfaceEID = RightSP.Position.EdgePosition.EdgeID;
}
else
{
// neither bounding point was an "edge" point,
// so they must both be "vertex" points
checkSlow(IsVertexPoint(LeftSP) && IsVertexPoint(RightSP));
const int32 SurfaceVIDs[2] = { LeftSP.Position.VertexPosition.VID, RightSP.Position.VertexPosition.VID};
SurfaceEID = SurfaceMesh.FindEdge(SurfaceVIDs[0], SurfaceVIDs[1]);
checkSlow(SurfaceEID != -1);
}
return SurfaceEID;
}();
// compute alpha location relative to the surface edge.
const FEdge SurfaceEdge = SurfaceMesh.GetEdge(SurfaceEID);
const FIndex2i SurfaceEdgeV = SurfaceEdge.Vert;
// orient the surface edge in the same direction as the intrinsic edge ( the intrinsic edge was ordered by its T0)
// note: the intrinsic edge vertices can not be the same because the intrinsic edge is a segment of the surface edge
const int32 StartV = [&]
{
if (IsVertexPoint(LeftSP))
{
return (int32)(LeftSP.Position.VertexPosition.VID == SurfaceEdgeV.B);
}
else if (IsVertexPoint(RightSP))
{
return (int32)(RightSP.Position.VertexPosition.VID != SurfaceEdgeV.B);
}
checkSlow(IsEdgePoint(RightSP) && IsEdgePoint(LeftSP));
return (int32)(RightSP.Position.EdgePosition.Alpha > LeftSP.Position.EdgePosition.Alpha);
}();
// surface edge end points, ordered correctly.
const FVector3d SurfaceEdgeEndPts[2] = {SurfaceMesh.GetVertex(SurfaceEdgeV[StartV]), SurfaceMesh.GetVertex(SurfaceEdgeV[1-StartV])};
const double SurfaceEdgeLength = ( SurfaceEdgeEndPts[1] - SurfaceEdgeEndPts[0] ).Length();
const double SurfaceDistToSplit = ( AsR3Pos(LeftSP) - SurfaceEdgeEndPts[0] ).Length() + IntrinsicEdgeSplitDistance;
const double R3Alpha = 1. - SurfaceDistToSplit / SurfaceEdgeLength;
checkSlow(SurfaceDistToSplit <= SurfaceEdgeLength);
// compute the R3 position of the new vertex
const FVector3d R3Pos = R3Alpha * SurfaceEdgeEndPts[0] + (1. - R3Alpha) * SurfaceEdgeEndPts[1];
// record the R3 position and the surface position.
Vertices[SplitInfo.NewVertex] = R3Pos;
const double SurfaceAlpha = (StartV == 0) ? R3Alpha : 1. - R3Alpha;
IntrinsicVertexPositions.InsertAt(FSurfacePoint(SurfaceEID, SurfaceAlpha), SplitInfo.NewVertex);
}
else
{
// compute the r3 positions of the boundary points
const FVector3d LeftR3 = AsR3Pos(LeftSP);
const FVector3d RightR3 = AsR3Pos(RightSP);
// compute the local alpha of the new vertex between the boundary points
const double BoundingPtsSeperation = (LeftR3 - RightR3).Length();
const double SplitToRightBoundary = BoundingPoints[1].DistOnEdge - IntrinsicEdgeSplitDistance;
const double Alpha = SplitToRightBoundary / BoundingPtsSeperation;
checkSlow(Alpha >= 0. && Alpha <=1.);
// find the surface triangle the new vertex lives on
const int32 SurfaceTID = [&]
{
int32 TID = -1;
if (IsFacePoint(LeftSP))
{
TID = LeftSP.Position.TriPosition.TriID;
}
else if (IsFacePoint(RightSP))
{
TID = RightSP.Position.TriPosition.TriID;
}
if (TID == -1)
{
TArray<int32> AdjSurfaceTIDs = GetAdjacentTriangles(LeftSP, SurfaceMesh);
for (const int32 CandidtateTID : AdjSurfaceTIDs)
{
if (IsOnSurfaceTriangle(RightSP, CandidtateTID, SurfaceMesh))
{
TID = CandidtateTID;
break;
}
}
}
return TID;
}();
checkSlow(SurfaceTID != -1);
// compute the barycentric coordinates of the boundary points relative to the surface triangle
const FVector3d LeftBC = AsBarycenteric(LeftSP, SurfaceTID, SurfaceMesh);
const FVector3d RightBC = AsBarycenteric(RightSP, SurfaceTID, SurfaceMesh);
// compute the R3 and Barycentric location of the new vertex
const FVector3d R3Pos = Alpha * LeftR3 + (1.-Alpha) * RightR3;
const FVector3d SurfaceBCPos = Alpha * LeftBC + (1.-Alpha) * RightBC;
// record the R3 position and the surface position.
Vertices[SplitInfo.NewVertex] = R3Pos;
IntrinsicVertexPositions.InsertAt(FSurfacePoint(SurfaceTID, SurfaceBCPos), SplitInfo.NewVertex);
}
}
return EMeshResult::Ok;
}