// 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& 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 IsEnqueued; TArray 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& Filter) : IsEnqueued(false, (int32)IDEnumerable.Vector->GetMaxIndex()) { // parallel evaluation that assumes Filter is expensive const int32 MaxID = (int32)IDEnumerable.Vector->GetMaxIndex(); TArray 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 IsEnqueued; TQueue 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 int32 FlipToDelaunayImpl(MeshType& Mesh, TSet& Uncorrected, const int32 MaxFlipCount, double Threshold = -TMathUtilConstants::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 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 bool GetAdjElementsContainingSurfaceEdge(const IntrinsicMeshType& IntrinsicMesh, const int32 IntrinsicVID, TArray>& IntrisicTIDs, TArray& 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(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 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> TIDs; TArray 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& 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 void ContinueFromEdgePointCrossing(const IntrinsicMeshType& IntrinsicMesh, int32 VID, int32 IncomingID, bool bIncomingIsEdge, TArray& 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 = {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 TTuple GetCrossingExit(const IntrinsicMeshType& IntrinsicMesh, const FNormalCoordinates& NCoords, const int32 TriID, const int32 EdgeIDin, const int32 Pin) { TTuple 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 void ContinueTraceSurfaceEdgeAcrossFaces( const IntrinsicMeshType& IntrinsicMesh, TArray& 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 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 void ContinueTraceSurfaceEdge(const IntrinsicMeshType& IntrinsicMesh, TArray& 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 TArray 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 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 TArray 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 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 void ConvertEdgesCrossed( const FSurfacePoint& StartSurfacePoint, const FSurfacePoint& EndSurfacePoint, const TArray& HostEdgesCrossed, const HostMeshType& HostMesh, const TraceMeshType& TraceMesh, double CoalesceThreshold, const TraceVIDToSurfacePointFtor& VIDToSurfacePoint, TArray& 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 ToStripIndexMap; // maps from mesh VID to triangle strip VID. TArray 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::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::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::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 TArray TraceEdgeOverHost( const int32 TraceEID, const TArray& HostEdgesCrossed, const HostMeshType& HostMesh, const TraceMeshType& TraceMesh, double CoalesceThreshold, bool bReverse, const TraceVIDToSurfacePointFtor& VIDToSurfacePoint) { TArray 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 TArray TraceEdge(const IntrinsicMeshType& IntrinsicMesh, int32 IntrinsicEID, double CoalesceThreshold, bool bReverse) { using FEdgeAndCrossingIdx = FNormalCoordinates::FEdgeAndCrossingIdx; TArray Result; if (!IntrinsicMesh.IsEdge(IntrinsicEID)) { return MoveTemp(Result); } const FNormalCoordinates& NormalCoordinates = IntrinsicMesh.GetNormalCoordinates(); TArray 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 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& Uncorrected, const int32 MaxFlipCount) { return FlipToDelaunayImpl(IntrinsicMesh, Uncorrected, MaxFlipCount); } int32 UE::Geometry::FlipToDelaunay(FSimpleIntrinsicEdgeFlipMesh& IntrinsicMesh, TSet& Uncorrected, const int32 MaxFlipCount) { return FlipToDelaunayImpl(IntrinsicMesh, Uncorrected, MaxFlipCount); } int32 UE::Geometry::FlipToDelaunay(FSimpleIntrinsicMesh& IntrinsicMesh, TSet& Uncorrected, const int32 MaxFlipCount) { return FlipToDelaunayImpl(IntrinsicMesh, Uncorrected, MaxFlipCount); } int32 UE::Geometry::FlipToDelaunay(FIntrinsicMesh& IntrinsicMesh, TSet& Uncorrected, const int32 MaxFlipCount) { return FlipToDelaunayImpl(IntrinsicMesh, Uncorrected, MaxFlipCount); } int32 UE::Geometry::FlipToDelaunay(FIntrinsicEdgeFlipMesh& IntrinsicMesh, TSet& 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::Pi - TMathUtilConstants::ZeroTolerance || TotalAngleAtOrgVert[1] > TMathUtilConstants::Pi - TMathUtilConstants::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::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 UE::Geometry::FIntrinsicTriangulation::TraceEdge(int32 EID, double CoalesceThreshold, bool bReverse) const { TArray 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& 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& 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::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::TraceEdge(int32 IntrinsicEID, double CoalesceThreshold, bool bReverse) const { return FNormalCoordIntrinsicTraceImpl::TraceEdge(*this, IntrinsicEID, CoalesceThreshold, bReverse); } TArray FIntrinsicEdgeFlipMesh::GetImplicitEdgeCrossings(const int32 SurfaceEID, const bool bReverse) const { return FNormalCoordSurfaceTraceImpl::TraceSurfaceEdge(*this, SurfaceEID, bReverse); } TArray FIntrinsicEdgeFlipMesh::TraceSurfaceEdge(int32 SurfaceEID, double CoalesceThreshold, bool bReverse) const { const FDynamicMesh3& HostMesh = *this->GetExtrinsicMesh(); const FIntrinsicEdgeFlipMesh& TraceMesh = *this; TArray 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 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 void UE::Geometry::TEdgeCorrespondence::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 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& 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 TArray TEdgeCorrespondence::TraceEdge(int32 IntrinsicEID, double CoalesceThreshold, bool bReverse) const { const FDynamicMesh3& HostMesh = *SurfaceMesh; const IntrinsicMeshType& TraceMesh = *IntrinsicMesh; const TArray& HostEdgeCrossings = SurfaceEdgesCrossed[IntrinsicEID]; return FNormalCoordSurfaceTraceImpl::TraceEdgeOverHost(IntrinsicEID, HostEdgeCrossings, HostMesh, TraceMesh, CoalesceThreshold, bReverse, [&TraceMesh](int32 VID) { return TraceMesh.GetVertexSurfacePoint(VID); }); } template struct UE::Geometry::TEdgeCorrespondence; template struct UE::Geometry::TEdgeCorrespondence; /**------------------------------------------------------------------------------ * 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 GetAdjacentTriangles(const IntrinsicCorrespondenceUtils::FSurfacePoint& SurfacePoint, const FDynamicMesh3& SurfaceMesh) { TArray 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 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& Row0, const TArray& Row1, const TArray& Row2, const FVector3d& BVector, TArray& 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::Abs(Det) < TMathUtil::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::Abs(Det) < TMathUtil::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 FIntrinsicMesh::TraceEdge(int32 IntrinsicEID, double CoalesceThreshold, bool bReverse) const { return FNormalCoordIntrinsicTraceImpl::TraceEdge(*this, IntrinsicEID, CoalesceThreshold, bReverse); } TArray FIntrinsicMesh::GetImplicitEdgeCrossings(const int32 SurfaceEID, const bool bReverse) const { return FNormalCoordSurfaceTraceImpl::TraceSurfaceEdge(*this, SurfaceEID, bReverse); } TArray FIntrinsicMesh::TraceSurfaceEdge(int32 SurfaceEID, double CoalesceThreshold, bool bReverse) const { const FDynamicMesh3& SurfaceMesh = *this->GetExtrinsicMesh(); const FIntrinsicMesh& IntrinsicMesh = *this; TArray EdgeAndCrossingIdxs = this->GetImplicitEdgeCrossings(SurfaceEID, bReverse); const int32 NumEdgeAndXIdx = EdgeAndCrossingIdxs.Num(); TArray 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 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::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::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 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 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 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& 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 BoundingConvexPolyVerts = [&] { TArray 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 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 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 CandidateTIDs = [&] { TArray 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 MinNormSolution; TArray 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 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 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 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 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; }