// Copyright Epic Games, Inc. All Rights Reserved. #include "Topo/TopologicalEdge.h" #include "Geo/Curves/NURBSCurve.h" #include "Geo/Curves/SegmentCurve.h" #include "Geo/GeoEnum.h" #include "Geo/Sampling/PolylineTools.h" #include "Geo/Sampler/SamplerOnParam.h" #include "Math/BSpline.h" #include "Math/Point.h" #include "Math/SlopeUtils.h" #include "Mesh/Structure/EdgeMesh.h" #include "Mesh/Structure/ModelMesh.h" #include "Mesh/Structure/VertexMesh.h" #include "Topo/TopologicalLoop.h" #include "Topo/TopologicalLink.h" #include "Topo/TopologicalFace.h" #include "Topo/TopologicalVertex.h" namespace UE::CADKernel { FTopologicalEdge::FTopologicalEdge(const TSharedRef& InCurve, const TSharedRef& InVertex1, const TSharedRef& InVertex2, const FLinearBoundary& InBoundary) : StartVertex(InVertex1) , EndVertex(InVertex2) , Boundary(InBoundary) , Curve(InCurve) , Length3D(-1) { ensureCADKernel(Boundary.IsValid()); ensureCADKernel(Boundary.GetMin() < Boundary.GetMax()); } FTopologicalEdge::FTopologicalEdge(const TSharedRef& InCurve, const TSharedRef& InVertex1, const TSharedRef& InVertex2) : StartVertex(InVertex1) , EndVertex(InVertex2) , Curve(InCurve) , Length3D(-1) { Boundary = Curve->GetBoundary(); ensureCADKernel(Boundary.IsValid()); } FTopologicalEdge::FTopologicalEdge(const TSharedRef& InCurve, const FLinearBoundary& InBoundary) : Boundary(InBoundary) , Curve(InCurve) , Length3D(-1) { TArray Coordinates = { Boundary.Min, Boundary.Max }; TArray Points; Curve->EvaluatePoints(Coordinates, Points); StartVertex = FTopologicalVertex::Make(Points[0].Point); EndVertex = FTopologicalVertex::Make(Points[1].Point); } FTopologicalEdge::FTopologicalEdge(const TSharedRef& InSurface, const FVector2d& InCoordinateVertex1, const TSharedRef& InVertex1, const FVector2d& InCoordinateVertex2, const TSharedRef& InVertex2) : StartVertex(InVertex1) , EndVertex(InVertex2) , Length3D(-1) { TSharedRef Curve2D = FEntity::MakeShared(InCoordinateVertex1, InCoordinateVertex2, 2); Curve = FEntity::MakeShared(InSurface, Curve2D); } void FTopologicalEdge::LinkVertex() { StartVertex->AddConnectedEdge(*this); EndVertex->AddConnectedEdge(*this); if (IsDegenerated()) { StartVertex->Link(*EndVertex); } } bool FTopologicalEdge::CheckVertices() { TArray Coordinates = { Boundary.Min, Boundary.Max }; TArray Points; Curve->Approximate3DPoints(Coordinates, Points); double ToleranceGeo = GetTolerance3D(); TFunction, FVector)> CheckExtremityGap = [&](TSharedPtr Vertex, FVector Point) { double GapToVertex = FVector::Distance(Vertex->GetCoordinates(), Point); return (GapToVertex < ToleranceGeo); }; if (!CheckExtremityGap(StartVertex, Points[0])) { if (CheckExtremityGap(StartVertex, Points[1]) && CheckExtremityGap(EndVertex, Points[0])) { Swap(StartVertex, EndVertex); return true; } return false; } return CheckExtremityGap(EndVertex, Points[1]); } bool FTopologicalEdge::CheckIfDegenerated() const { bool bDegeneration2D = false; bool bDegeneration3D = false; Curve->CheckIfDegenerated(Boundary, bDegeneration2D, bDegeneration3D, Length3D); Max2DTolerance = Length3D * FactorToComputeMaxTol; if (bDegeneration3D) { SetAsDegenerated(); } return bDegeneration2D; } TSharedPtr FTopologicalEdge::Make(const TSharedRef& InCurve, const TSharedRef& InVertex1, const TSharedRef& InVertex2, const FLinearBoundary& InBoundary) { TSharedRef Edge = FEntity::MakeShared(InCurve, InVertex1, InVertex2, InBoundary); return ReturnIfValid(Edge, /*bCheckVertices*/ true); } TSharedPtr FTopologicalEdge::Make(const TSharedRef& InCurve, const TSharedRef& InVertex1, const TSharedRef& InVertex2) { TSharedRef Edge = FEntity::MakeShared(InCurve, InVertex1, InVertex2); return ReturnIfValid(Edge, /*bCheckVertices*/ true); } TSharedPtr FTopologicalEdge::Make(const TSharedRef& InCurve, const FLinearBoundary& InBoundary) { TSharedRef Edge = FEntity::MakeShared(InCurve, InBoundary); return ReturnIfValid(Edge, /*bCheckVertices*/ false); } TSharedPtr FTopologicalEdge::Make(const TSharedRef& InCurve) { return Make(InCurve, InCurve->GetBoundary()); } TSharedPtr FTopologicalEdge::Make(const TSharedRef& InSurface, const FVector2d& InCoordinateVertex1, const TSharedRef& InVertex1, const FVector2d& InCoordinateVertex2, const TSharedRef& InVertex2) { TSharedRef Edge = FEntity::MakeShared(InSurface, InCoordinateVertex1, InVertex1, InCoordinateVertex2, InVertex2); return ReturnIfValid(Edge, /*bCheckVertices*/ false); } TSharedPtr FTopologicalEdge::ReturnIfValid(TSharedRef& InEdge, bool bCheckVertices) { if (InEdge->CheckIfDegenerated()) { InEdge->Delete(); return TSharedPtr(); } if (bCheckVertices && !InEdge->CheckVertices()) { InEdge->Delete(); return TSharedPtr(); } InEdge->Finalize(); InEdge->LinkVertex(); return InEdge; } bool FTopologicalEdge::HasSameLengthAs(const FTopologicalEdge& Edge, double EdgeLengthTolerance) const { double Min; double Max; if (Length() < Edge.Length()) { Min = Length(); Max = Edge.Length(); } else { Min = Edge.Length(); Max = Length(); } if (Min / Max > 0.95) // 95 % { return true; } if ((Max - Min) < EdgeLengthTolerance) { return true; } return false; }; bool FTopologicalEdge::IsTangentAtExtremitiesWith(const FTopologicalEdge& Edge) const { TFunction IsTangentAtExtremities = [&](EOrientation Orientation) -> bool { FVector EdgeStartTangent; FVector EdgeEndTangent; FVector StartTangent; FVector EndTangent; GetTangentsAtExtremities(StartTangent, EndTangent, Orientation); Edge.GetTangentsAtExtremities(EdgeStartTangent, EdgeEndTangent, EOrientation::Front); double StartAngle = FVectorUtil::ComputeCosinus(EdgeStartTangent, StartTangent); double EndAngle = FVectorUtil::ComputeCosinus(EdgeEndTangent, EndTangent); if (StartAngle >= UE_DOUBLE_HALF_SQRT_3 && EndAngle >= UE_DOUBLE_HALF_SQRT_3) { return true; } else { return false; } }; const FTopologicalVertex* ActiveEdgeVertex1 = &*Edge.GetStartVertex()->GetLinkActiveEntity(); const FTopologicalVertex* ActiveEdgeVertex2 = &*Edge.GetEndVertex()->GetLinkActiveEntity(); const FTopologicalVertex* ActiveVertex1 = &*GetStartVertex()->GetLinkActiveEntity(); const FTopologicalVertex* ActiveVertex2 = &*GetEndVertex()->GetLinkActiveEntity(); if ((ActiveVertex1 == ActiveEdgeVertex1) && (ActiveVertex2 == ActiveEdgeVertex2)) { if (!IsTangentAtExtremities(EOrientation::Front)) { if (ActiveVertex1 != ActiveEdgeVertex2) { return false; } // Self connected case return IsTangentAtExtremities(EOrientation::Back); } return true; } else if ((ActiveVertex1 == ActiveEdgeVertex2) && (ActiveVertex2 == ActiveEdgeVertex1)) { return IsTangentAtExtremities(EOrientation::Back); } return false; } bool FTopologicalEdge::IsLinkableTo(const FTopologicalEdge& Edge, double EdgeLengthTolerance) const { if (IsDeleted() || Edge.IsDeleted() || IsDegenerated() || Edge.IsDegenerated()) { return false; } if (!HasSameLengthAs(Edge, EdgeLengthTolerance)) { return false; } return IsTangentAtExtremitiesWith(Edge); } void FTopologicalEdge::LinkIfCoincident(FTopologicalEdge& Twin, double EdgeLengthTolerance, double SquareJoiningTolerance) { if (IsDeleted() || Twin.IsDeleted()) { return; } // Degenerated twin edges are not linked if (IsDegenerated() || Twin.IsDegenerated()) { if (HasSameLengthAs(Twin, EdgeLengthTolerance)) { SetAsDegenerated(); Twin.SetAsDegenerated(); } return; } const FVector& Edge1Vertex1 = GetStartVertex()->GetBarycenter(); const FVector& Edge1Vertex2 = GetEndVertex()->GetBarycenter(); const FVector& Edge2Vertex1 = Twin.GetStartVertex()->GetBarycenter(); const FVector& Edge2Vertex2 = Twin.GetEndVertex()->GetBarycenter(); // Define the orientation const double SquareDistanceE1V1_E2V1 = GetStartVertex()->IsLinkedTo(Twin.GetStartVertex()) ? 0. : FVector::DistSquared(Edge1Vertex1, Edge2Vertex1); const double SquareDistanceE1V2_E2V2 = GetEndVertex()->IsLinkedTo(Twin.GetEndVertex()) ? 0. : FVector::DistSquared(Edge1Vertex2, Edge2Vertex2); const double SquareDistanceE1V1_E2V2 = GetStartVertex()->IsLinkedTo(Twin.GetEndVertex()) ? 0. : FVector::DistSquared(Edge1Vertex1, Edge2Vertex2); const double SquareDistanceE1V2_E2V1 = GetEndVertex()->IsLinkedTo(Twin.GetStartVertex()) ? 0. : FVector::DistSquared(Edge1Vertex2, Edge2Vertex1); const double SquareDistanceSameOrientation = SquareDistanceE1V1_E2V1 + SquareDistanceE1V2_E2V2; const double SquareDistanceReverseOrientation = SquareDistanceE1V1_E2V2 + SquareDistanceE1V2_E2V1; if (SquareDistanceSameOrientation < SquareDistanceReverseOrientation) { if (SquareDistanceE1V1_E2V1 < SquareJoiningTolerance) { GetStartVertex()->Link(*Twin.GetStartVertex()); } else { FMessage::Printf(Log, TEXT("Edge %d and Edge %d are to far (%f) to be connected\n"), GetId(), Twin.GetId(), sqrt(SquareDistanceE1V1_E2V1)); return; } if (SquareDistanceE1V2_E2V2 < SquareJoiningTolerance) { GetEndVertex()->Link(*Twin.GetEndVertex()); } else { FMessage::Printf(Log, TEXT("Edge %d and Edge %d are to far (%f) to be connected\n"), GetId(), Twin.GetId(), sqrt(SquareDistanceE1V2_E2V2)); return; } } else { if (SquareDistanceE1V1_E2V2 < SquareJoiningTolerance) { GetStartVertex()->Link(*Twin.GetEndVertex()); } else { FMessage::Printf(Log, TEXT("Edge %d and Edge %d are to far (%f) to be connected\n"), GetId(), Twin.GetId(), sqrt(SquareDistanceE1V1_E2V2)); return; } if (SquareDistanceE1V2_E2V1 < SquareJoiningTolerance) { GetEndVertex()->Link(*Twin.GetStartVertex()); } else { FMessage::Printf(Log, TEXT("Edge %d and Edge %d are to far (%f) to be connected\n"), GetId(), Twin.GetId(), sqrt(SquareDistanceE1V2_E2V1)); return; } } if (IsLinkableTo(Twin, EdgeLengthTolerance)) { MakeLink(Twin); } } void FTopologicalEdge::Link(FTopologicalEdge& Twin) { if (IsDegenerated() || Twin.IsDegenerated()) { SetAsDegenerated(); Twin.SetAsDegenerated(); return; } if (IsDeleted() || Twin.IsDeleted()) { return; } MakeLink(Twin); } void FTopologicalEdge::Disjoin() { RemoveFromLink(); GetStartVertex()->RemoveFromLink(); GetEndVertex()->RemoveFromLink(); } void FTopologicalEdge::Empty() { if (StartVertex.IsValid()) { StartVertex->RemoveConnectedEdge(*this); StartVertex->DeleteIfIsolated(); StartVertex.Reset(); } if (EndVertex.IsValid()) { EndVertex->RemoveConnectedEdge(*this); EndVertex->DeleteIfIsolated(); EndVertex.Reset(); } if (TopologicalLink.IsValid()) { TopologicalLink->RemoveEntity(*this); } Curve.Reset(); Loop = nullptr; Mesh.Reset(); TLinkable::Empty(); } FTopologicalFace* FTopologicalEdge::GetFace() const { if (Loop != nullptr) { return Loop->GetFace(); } return nullptr; } void FTopologicalEdge::ComputeCrossingPointCoordinates() { if (FMath::IsNearlyEqual(Boundary.GetMin(), Boundary.GetMax(), UE_DOUBLE_SMALL_NUMBER)) { // #cadkernel_check: Why could this happen? Shouldn't it be detected way earlier? return; } double Tolerance = GetTolerance3D(); { FSurfacicPolyline Presampling; FSurfacicCurveSamplerOnParam Sampler(*Curve, Boundary, Tolerance * 10., Tolerance, Presampling); Sampler.Sample(); Presampling.SwapCoordinates(CrossingPointUs); } ensureCADKernel(FMath::IsNearlyEqual(CrossingPointUs.Last(), Boundary.GetMax(), Curve->GetMinLinearTolerance())); // #cadkernel_check: To investigate - It looks like the sampler does not start and end the sample data with the boundary values??? if (!FMath::IsNearlyEqual(CrossingPointUs.Last(), Boundary.GetMax(), UE_DOUBLE_SMALL_NUMBER)) { CrossingPointUs.Add(Boundary.GetMax()); } // Check sampling: // the main idea is to avoid very small delta U between two or more points. // e.g. CrossingPointUs = {0, 0.25, 0.5, 0.50001, 0.75, 0.9999, 0.99995, 1} // If small delta Us are identified, they are smoothed out with the next // e.g. CrossingPointUs = {0, 0.25, 0.5, 0.50001, 0.75, 0.9999, 0.99995, 1} // => CrossingPointUs = {0, 0.25, 0.416, 0.583, 0.75, 0.83, 0.92, 1} double DeltaUMean = Boundary.Length() / (CrossingPointUs.Num() - 1); double DeltaUMin = DeltaUMean * 0.03; int32 IndexMin = 0; double LocalUMin = DeltaUMin; for (int32 Index = 1; Index < CrossingPointUs.Num(); ++Index) { double DeltaU = CrossingPointUs[Index] - CrossingPointUs[IndexMin]; if (DeltaU < LocalUMin) { LocalUMin += DeltaUMin; continue; } if ((Index - IndexMin) > 1) { if (IndexMin > 1) { IndexMin--; } double NewDelatU = DeltaU / (Index - IndexMin); for (int32 Andex = IndexMin + 1; Andex < Index; ++Andex) { CrossingPointUs[Andex] = CrossingPointUs[Andex - 1] + NewDelatU; } } IndexMin = Index; LocalUMin = DeltaUMin; } if (IndexMin != CrossingPointUs.Num() - 1) { IndexMin--; double DeltaU = CrossingPointUs[CrossingPointUs.Num() - 1] - CrossingPointUs[IndexMin]; double NewDelatU = DeltaU / (CrossingPointUs.Num() - 1 - IndexMin); for (int32 Index = IndexMin + 1; Index < CrossingPointUs.Num() - 1; ++Index) { CrossingPointUs[Index] = CrossingPointUs[Index - 1] + NewDelatU; } } } void FTopologicalEdge::SetStartVertex(const double NewCoordinate) { ensureCADKernel(Curve->GetUMax() > NewCoordinate); Boundary.SetMin(NewCoordinate); FCurvePoint OutPoint; Curve->EvaluatePoint(NewCoordinate, OutPoint); StartVertex->SetCoordinates(OutPoint.Point); } void FTopologicalEdge::SetEndVertex(const double NewCoordinate) { ensureCADKernel(Curve->GetUMin() < NewCoordinate); Boundary.SetMax(NewCoordinate); FCurvePoint OutPoint; Curve->EvaluatePoint(NewCoordinate, OutPoint); EndVertex->SetCoordinates(OutPoint.Point); } void FTopologicalEdge::SetStartVertex(const double NewCoordinate, const FVector& NewPoint3D) { ensureCADKernel(Curve->GetUMin() < NewCoordinate); Boundary.SetMin(NewCoordinate); StartVertex->SetCoordinates(NewPoint3D); } void FTopologicalEdge::SetEndVertex(const double NewCoordinate, const FVector& NewPoint3D) { ensureCADKernel(Curve->GetUMax() > NewCoordinate); Boundary.SetMax(NewCoordinate); EndVertex->SetCoordinates(NewPoint3D); } void FTopologicalEdge::ComputeLength() { Length3D = Curve->ApproximateLength(Boundary); Max2DTolerance = Length3D * FactorToComputeMaxTol; } double FTopologicalEdge::Length() const { if (Length3D < 0) { const_cast(this)->ComputeLength(); } return Length3D; } void FTopologicalEdge::GetTangentsAtExtremities(FVector& StartTangent, FVector& EndTangent, bool bForward) const { ensureCADKernel(Curve->Polyline.Size()); FDichotomyFinder Finder(Curve->Polyline.GetCoordinates()); int32 StartIndex = Finder.Find(Boundary.Min); int32 EndIndex = Finder.Find(Boundary.Max); const TArray& Polyline3D = Curve->Polyline.GetPoints(); if (bForward) { StartTangent = Polyline3D[StartIndex + 1] - Polyline3D[StartIndex]; EndTangent = Polyline3D[EndIndex] - Polyline3D[EndIndex + 1]; } else { EndTangent = Polyline3D[StartIndex + 1] - Polyline3D[StartIndex]; StartTangent = Polyline3D[EndIndex] - Polyline3D[EndIndex + 1]; } } void FTopologicalEdge::Sample(const double DesiredSegmentLength, TArray& OutCoordinates) const { Curve->Sample(Boundary, DesiredSegmentLength, OutCoordinates); } int32 FTopologicalEdge::EvaluateCuttingPointNum() { double Num = 0; for (int32 Index = 0; Index < CrossingPointUs.Num() - 1; Index++) { Num += ((CrossingPointUs[Index + 1] - CrossingPointUs[Index]) / CrossingPointDeltaUMaxs[Index]); } Num *= 1.5; return (int32)Num; } double FTopologicalEdge::TransformTwinEdgeCoordinateToLocalCoordinate(const FTopologicalEdge& TwinEdge, const double InTwinCoordinate) const { if (this == &TwinEdge) { return InTwinCoordinate; } if (IsDegenerated() || TwinEdge.IsDegenerated()) { // linear transform const bool bSameDirection = IsSameDirection(TwinEdge); const double Start = bSameDirection ? Boundary.GetMin() : Boundary.GetMax(); const double End = bSameDirection ? Boundary.GetMax() : Boundary.GetMin(); const double Distance = End - Start; if (FMath::IsNearlyZero(Distance)) { return Start; } const double TwinStart = TwinEdge.GetBoundary().GetMin(); const double TwinEnd = TwinEdge.GetBoundary().GetMax(); const double TwinDistance = TwinEnd - TwinStart; if (FMath::IsNearlyZero(TwinDistance)) { return Start; } return Start + (TwinStart - InTwinCoordinate) * Distance / TwinDistance; } FVector PointOnEdge = TwinEdge.GetCurve()->Approximate3DPoint(InTwinCoordinate); FVector ProjectedPoint; return Curve->GetCoordinateOfProjectedPoint(Boundary, PointOnEdge, ProjectedPoint); } double FTopologicalEdge::TransformLocalCoordinateToActiveEdgeCoordinate(const double InLocalCoordinate) const { if (IsActiveEntity()) { return InLocalCoordinate; } const FTopologicalEdge& ActiveEdge = *GetLinkActiveEntity(); return ActiveEdge.TransformTwinEdgeCoordinateToLocalCoordinate(*this, InLocalCoordinate); } double FTopologicalEdge::TransformActiveEdgeCoordinateToLocalCoordinate(const double InActiveEdgeCoordinate) const { if (IsActiveEntity()) { return InActiveEdgeCoordinate; } const FTopologicalEdge& ActiveEdge = *GetLinkActiveEntity(); return TransformTwinEdgeCoordinateToLocalCoordinate(ActiveEdge, InActiveEdgeCoordinate); } void FTopologicalEdge::TransformTwinEdgeCoordinatesToLocalCoordinates(const FTopologicalEdge& TwinEdge, const TArray& InTwinCoordinates, TArray& OutLocalCoordinates) const { if (this == &TwinEdge) { OutLocalCoordinates = InTwinCoordinates; return; } if (IsDegenerated() || TwinEdge.IsDegenerated()) { // linear transform const bool bSameDirection = IsSameDirection(TwinEdge); const double Start = bSameDirection ? Boundary.GetMin() : Boundary.GetMax(); const double End = bSameDirection ? Boundary.GetMax() : Boundary.GetMin(); const double Distance = End - Start; if (FMath::IsNearlyZero(Distance)) { OutLocalCoordinates.Init(Start, InTwinCoordinates.Num()); return; } const double TwinStart = TwinEdge.GetBoundary().GetMin(); const double TwinEnd = TwinEdge.GetBoundary().GetMax(); const double TwinDistance = TwinEnd - TwinStart; if (FMath::IsNearlyZero(TwinDistance)) { OutLocalCoordinates.Init(Start, InTwinCoordinates.Num()); return; } OutLocalCoordinates.Empty(InTwinCoordinates.Num()); const double Factor = Distance / TwinDistance; for (double TwinCoordinate : InTwinCoordinates) { OutLocalCoordinates.Add(Start + (TwinCoordinate - TwinStart) * Factor); } return; } TArray Cutting3DPoints; TwinEdge.ApproximatePoints(InTwinCoordinates, Cutting3DPoints); ProjectTwinEdgePoints(Cutting3DPoints, IsSameDirection(TwinEdge), OutLocalCoordinates); } void FTopologicalEdge::TransformLocalCoordinatesToActiveEdgeCoordinates(const TArray& InLocalCoordinates, TArray& OutActiveEdgeCoordinates) const { const FTopologicalEdge& ActiveEdge = *GetLinkActiveEntity(); return ActiveEdge.TransformTwinEdgeCoordinatesToLocalCoordinates(*this, InLocalCoordinates, OutActiveEdgeCoordinates); } void FTopologicalEdge::TransformActiveEdgeCoordinatesToLocalCoordinates(const TArray& InActiveEdgeCoordinates, TArray& OutLocalCoordinates) const { const FTopologicalEdge& ActiveEdge = *GetLinkActiveEntity(); return TransformTwinEdgeCoordinatesToLocalCoordinates(ActiveEdge, InActiveEdgeCoordinates, OutLocalCoordinates); } void FTopologicalEdge::SortImposedCuttingPoints() { Algo::Sort(ImposedCuttingPointUs, [](const FImposedCuttingPoint& C1, const FImposedCuttingPoint& C2) { return C1.Coordinate < C2.Coordinate; }); if (ImposedCuttingPointUs.Num() > 1) { int32 LastIndex = -1; int32 NewIndex = 0; for (int32 Index = 1; Index < ImposedCuttingPointUs.Num(); ++Index) { if (FMath::IsNearlyEqual(ImposedCuttingPointUs[NewIndex].Coordinate, ImposedCuttingPointUs[Index].Coordinate)) { if (LastIndex < 0) { LastIndex = NewIndex; } if (ImposedCuttingPointUs[NewIndex].OppositNodeIndex == -1) { Swap(ImposedCuttingPointUs[NewIndex], ImposedCuttingPointUs[Index]); } else if (ImposedCuttingPointUs[Index].OppositNodeIndex == -1) { } else if (ImposedCuttingPointUs[NewIndex].OppositNodeIndex == ImposedCuttingPointUs[Index].OppositNodeIndex) { ImposedCuttingPointUs[NewIndex].DeltaU = FMath::Max(ImposedCuttingPointUs[NewIndex].DeltaU, ImposedCuttingPointUs[Index].DeltaU); } else if ((LastIndex >= 0) && (LastIndex != NewIndex) && (ImposedCuttingPointUs[LastIndex].OppositNodeIndex == ImposedCuttingPointUs[Index].OppositNodeIndex)) { ImposedCuttingPointUs[LastIndex].DeltaU = FMath::Max(ImposedCuttingPointUs[LastIndex].DeltaU, ImposedCuttingPointUs[Index].DeltaU); } else { NewIndex++; } } else { LastIndex = -1; NewIndex++; if (NewIndex != Index) { Swap(ImposedCuttingPointUs[NewIndex], ImposedCuttingPointUs[Index]); } } } NewIndex++; ImposedCuttingPointUs.SetNum(NewIndex); } } void FTopologicalEdge::ProjectTwinEdgePointsOn2DCurve(const TSharedRef& InTwinEdge, const TArray& InTwinEdgePointCoords, TArray& OutPoints2D) { if (&InTwinEdge.Get() == this) { Curve->Approximate2DPoints(InTwinEdgePointCoords, OutPoints2D); } else { TArray Points3D; InTwinEdge->ApproximatePoints(InTwinEdgePointCoords, Points3D); TArray Coordinates; bool bSameDirection = IsSameDirection(*InTwinEdge); const double ToleranceOfProjection = Length3D * 0.1; Curve->ProjectTwinCurvePoints(Boundary, Points3D, bSameDirection, Coordinates, ToleranceOfProjection); Curve->Approximate2DPoints(Coordinates, OutPoints2D); } } void FTopologicalEdge::GenerateMeshElements(FModelMesh& MeshModel) { FTopologicalEdge& ActiveEdge = *GetLinkActiveEntity(); bool bSameDirection = IsSameDirection(ActiveEdge); FEdgeMesh& EdgeMesh = ActiveEdge.GetOrCreateMesh(MeshModel); int32 StartVertexNodeIndex = ActiveEdge.GetStartVertex()->GetOrCreateMesh(MeshModel).GetMesh(); int32 EndVertexNodeIndex = ActiveEdge.GetEndVertex()->GetOrCreateMesh(MeshModel).GetMesh(); TArray CuttingPointCoordinates = GetCuttingPointCoordinates(); ensureCADKernel(CuttingPointCoordinates.Num() > 1); CuttingPointCoordinates.RemoveAt(0); CuttingPointCoordinates.Pop(); TArray& Coordinates = EdgeMesh.GetNodeCoordinates(); ApproximatePoints(CuttingPointCoordinates, Coordinates); if (!bSameDirection) { Algo::Reverse(Coordinates); } EdgeMesh.RegisterCoordinates(); EdgeMesh.Mesh(StartVertexNodeIndex, EndVertexNodeIndex); MeshModel.AddMesh(EdgeMesh); ActiveEdge.SetMeshedMarker(); SetMeshedMarker(); } bool FTopologicalEdge::IsSameDirection(const FTopologicalEdge& Edge) const { if (!TopologicalLink) { return true; } if (TopologicalLink != Edge.GetLink()) { return true; } if (&Edge == this) { return true; } TSharedPtr Vertex1Edge = GetStartVertex()->GetLink(); TSharedPtr Vertex2Edge = GetEndVertex()->GetLink(); if (Vertex1Edge == Vertex2Edge) { if (Edge.IsDegenerated()) { return true; } FVector EdgeStartTangent; FVector EdgeEndTangent; Edge.GetTangentsAtExtremities(EdgeStartTangent, EdgeEndTangent, true); FVector StartTangent; FVector EndTangent; GetTangentsAtExtremities(StartTangent, EndTangent, true); double StartAngle = FVectorUtil::ComputeCosinus(StartTangent, EdgeStartTangent); double EndAngle = FVectorUtil::ComputeCosinus(EndTangent, EdgeEndTangent); if (StartAngle >= 0 && EndAngle >= 0) { return true; } if (StartAngle <= 0 && EndAngle <= 0) { return false; } Edge.SetAsDegenerated(); return true; } return Vertex1Edge == Edge.GetStartVertex()->GetLink(); } FEdgeMesh& FTopologicalEdge::GetOrCreateMesh(FModelMesh& ShellMesh) { if (!IsActiveEntity()) { return GetLinkActiveEdge()->GetOrCreateMesh(ShellMesh); } if (!Mesh) { Mesh = FEntity::MakeShared(ShellMesh, *this); } return *Mesh; } void FTopologicalEdge::RemovePreMesh() { if (IsMeshed()) { return; } CuttingPointUs.Empty(); ResetPreMeshed(); } const FTopologicalEdge* FTopologicalEdge::GetPreMeshedTwin() const { for (const FTopologicalEdge* Edge : GetLink()->GetTwinEntities()) { if (!Edge->GetCuttingPoints().IsEmpty()) { return Edge; } } return nullptr; } void FTopologicalEdge::AddImposedCuttingPointU(const double ImposedCuttingPointU, int32 OppositeNodeIndex, const double DeltaU) { if(Boundary.Contains(ImposedCuttingPointU)) { ImposedCuttingPointUs.Emplace(ImposedCuttingPointU, OppositeNodeIndex, DeltaU); } } void FTopologicalEdge::AddTwinsCuttingPoint(double Coord, double DeltaU) { if (FMath::IsNearlyEqual(Coord, Boundary.GetMin(), GetTolerance2DAt(Boundary.GetMin()))) { Coord = Boundary.GetMin(); } else if (FMath::IsNearlyEqual(Coord, Boundary.GetMax(), GetTolerance2DAt(Boundary.GetMax()))) { Coord = Boundary.GetMax(); } CuttingPointUs.Emplace(Coord, ECoordinateType::ImposedCoordinate, FPairOfIndex::Undefined, DeltaU); } void FTopologicalEdge::TransferCuttingPointFromMeshedEdge(bool bOnlyWithOppositeNode, FAddCuttingPointFunc AddCuttingPoint) { const FTopologicalEdge* PreMeshedTwin = GetPreMeshedTwin(); if ((PreMeshedTwin == nullptr) || (PreMeshedTwin == this)) { return; } TArray PreMeshEdgeCuttingPointCoords = PreMeshedTwin->GetCuttingPointCoordinates(); TArray CuttingPointCoords; TransformTwinEdgeCoordinatesToLocalCoordinates(*PreMeshedTwin, PreMeshEdgeCuttingPointCoords, CuttingPointCoords); const TArray& PreMeshEdgeCuttingPoints = PreMeshedTwin->GetCuttingPoints(); CuttingPointUs.Empty(PreMeshEdgeCuttingPoints.Num()); for (int32 Index = 0; Index < PreMeshEdgeCuttingPoints.Num(); ++Index) { const FCuttingPoint& PreMeshEdgeCuttingPoint = PreMeshEdgeCuttingPoints[Index]; if (bOnlyWithOppositeNode && PreMeshEdgeCuttingPoint.OppositNodeIndices[0] < 0) { continue; } const double PreMeshEdgeCuttingPointCoord = PreMeshEdgeCuttingPoint.Coordinate; const double PreMeshEdgeCuttingPointDeltaU = PreMeshEdgeCuttingPoint.IsoDeltaU; const double CuttingPointCoord = CuttingPointCoords[Index]; const double EdgeTol = GetTolerance2DAt(CuttingPointCoord); const double PreMeshEdgeTol = PreMeshedTwin->GetTolerance2DAt(PreMeshEdgeCuttingPointCoord); const double CuttingPointDeltaU = PreMeshEdgeCuttingPointDeltaU * EdgeTol / PreMeshEdgeTol; AddCuttingPoint(CuttingPointCoord, PreMeshEdgeCuttingPoint.Type, PreMeshEdgeCuttingPoint.OppositNodeIndices, CuttingPointDeltaU); } } TArray FTopologicalEdge::GetCuttingPointCoordinates() const { TArray CuttingPointCoordinates; CuttingPointCoordinates.Reserve(GetCuttingPoints().Num()); for (const FCuttingPoint& CuttingPoint : GetCuttingPoints()) { CuttingPointCoordinates.Add(CuttingPoint.Coordinate); } return MoveTemp(CuttingPointCoordinates); } TArray FTopologicalEdge::GetPreElementLengths() const { const TArray& CuttingPoints = GetCuttingPoints(); const int32 CuttingPointCount = CuttingPoints.Num(); TArray InnerNodes; if (CuttingPointCount > 2) { TArray Coordinates; Coordinates.Reserve(CuttingPointCount); for (int32 Index = 1; Index < CuttingPointCount - 1; ++Index) { Coordinates.Add(CuttingPoints[Index].Coordinate); } ApproximatePoints(Coordinates, InnerNodes); } const FVector& StartNode = GetStartVertex()->GetCoordinates(); const FVector& EndNode = GetEndVertex()->GetCoordinates(); return PolylineTools::ComputePolylineSegmentLengths(StartNode, InnerNodes, EndNode); } TSharedPtr FTopologicalEdge::CreateEdgeByMergingEdges(const double SmallEdgeTolerance, TArray& Edges, const TSharedRef& StartVertex, const TSharedRef& EndVertex) { TSharedRef CarrierSurface = Edges[0].Entity->GetCurve()->GetCarrierSurface(); // check if all curves are 2D NURBS bool bAreNurbs = true; int32 NurbsMaxDegree = 0; TArray> NurbsCurves; NurbsCurves.Reserve(Edges.Num()); bool bCanRemove = true; for (FOrientedEdge& Edge : Edges) { if (Edge.Entity->GetCurve()->Get2DCurve()->GetCurveType() != ECurve::Nurbs) { return TSharedPtr(); } double EdgeLength = Edge.Entity->Length(); if (bCanRemove && EdgeLength < SmallEdgeTolerance) { NurbsCurves.Emplace(TSharedPtr()); bCanRemove = false; continue; // the edge will be ignored } bCanRemove = true; // Find the max degree of the NURBS TSharedPtr NURBS = NurbsCurves.Emplace_GetRef(StaticCastSharedRef(Edge.Entity->GetCurve()->Get2DCurve())); int32 NurbsDegree = NURBS->GetDegree(); if (NurbsDegree > NurbsMaxDegree) { NurbsMaxDegree = NurbsDegree; } // Edge has restricted its curve ? FLinearBoundary EdgeBoundary = Edge.Entity->GetBoundary(); FLinearBoundary CurveBoundary = NURBS->GetBoundary(); double ParametricTolerance = NURBS->GetBoundary().ComputeMinimalTolerance(); if (!FMath::IsNearlyEqual(EdgeBoundary.Min, CurveBoundary.Min, ParametricTolerance) || !FMath::IsNearlyEqual(EdgeBoundary.Max, CurveBoundary.Max, ParametricTolerance)) { // ToDO, check if the next edge is not the complementary of this // cancel return TSharedPtr(); } } bool bEdgeNeedToBeExtend = false; int32 PoleCount = 0; double LastCoordinate = 0; for (int32 Index = 0; Index < Edges.Num(); Index++) { TSharedPtr& NURBS = NurbsCurves[Index]; if (!NURBS.IsValid()) { bEdgeNeedToBeExtend = true; continue; // the edge will be ignored } if (NURBS->GetDegree() < NurbsMaxDegree) { NURBS = BSpline::DuplicateNurbsCurveWithHigherDegree(NurbsMaxDegree, *NURBS); if (!NURBS.IsValid()) { // cancel return TSharedPtr(); } } else { NURBS = FEntity::MakeShared(*NURBS); } if (Edges[Index].Direction == EOrientation::Back) { NURBS->Invert(); } NURBS->SetStartNodalCoordinate(LastCoordinate); LastCoordinate = NURBS->GetBoundary().GetMax(); PoleCount += NURBS->GetPoleCount(); } if (bEdgeNeedToBeExtend) { for (int32 Index = 0; Index < Edges.Num(); Index++) { if (!NurbsCurves[Index].IsValid()) { double PreviousLength = Index > 0 ? Edges[Index - 1].Entity->Length() : 0; double NextLength = Index < Edges.Num() - 1 ? Edges[Index + 1].Entity->Length() : 0; double TargetCoordinate = 0; EOrientation FrontOrientation = PreviousLength > NextLength ? EOrientation::Front : EOrientation::Back; TargetCoordinate = Edges[Index].Direction == FrontOrientation ? Edges[Index].Entity->GetBoundary().GetMax() : Edges[Index].Entity->GetBoundary().GetMin(); if (PreviousLength > NextLength) { TargetCoordinate = Edges[Index].Direction == EOrientation::Front ? Edges[Index].Entity->GetBoundary().GetMax() : Edges[Index].Entity->GetBoundary().GetMin(); } else { TargetCoordinate = Edges[Index].Direction == EOrientation::Front ? Edges[Index].Entity->GetBoundary().GetMin() : Edges[Index].Entity->GetBoundary().GetMax(); } FVector2d Target = Edges[Index].Entity->Approximate2DPoint(TargetCoordinate); int32 NeigborIndex = PreviousLength > NextLength ? Index - 1 : Index + 1; if (NeigborIndex < 0 || NeigborIndex >= NurbsCurves.Num()) { return TSharedPtr(); } NurbsCurves[NeigborIndex]->ExtendTo(FVector(Target, 0.)); } } } TArray NewNodalVector; TArray NewWeights; TArray NewPoles; NurbsMaxDegree++; NewNodalVector.Reserve(PoleCount + NurbsMaxDegree); NewPoles.Reserve(PoleCount + NurbsMaxDegree); bool bIsRational = false; for (const TSharedPtr& NurbsCurve : NurbsCurves) { if (!NurbsCurve.IsValid()) { continue; } if (NurbsCurve->IsRational()) { bIsRational = true; break; } } if (bIsRational) { NewWeights.Reserve(PoleCount + NurbsMaxDegree); for (const TSharedPtr& NurbsCurve : NurbsCurves) { if (!NurbsCurve.IsValid()) { continue; } if (!NewPoles.IsEmpty()) { NewPoles.Pop(); NewWeights.Pop(); } NewPoles.Append(NurbsCurve->GetPoles()); if (NurbsCurve->IsRational()) { NewWeights.Append(NurbsCurve->GetWeights()); } else { for (int32 Index = 0; Index < NurbsCurve->GetPoles().Num(); ++Index) { NewWeights.Add(1.); } } } } else { for (const TSharedPtr& NurbsCurve : NurbsCurves) { if (!NurbsCurve.IsValid()) { continue; } if (!NewPoles.IsEmpty()) { NewPoles.Pop(); } NewPoles.Append(NurbsCurve->GetPoles()); } } for (const TSharedPtr& NurbsCurve : NurbsCurves) { if (!NurbsCurve.IsValid()) { continue; } if (NewNodalVector.IsEmpty()) { NewNodalVector.Append(NurbsCurve->GetNodalVector()); } else { NewNodalVector.SetNum(NewNodalVector.Num() - 1); NewNodalVector.Append(NurbsCurve->GetNodalVector().GetData() + NurbsMaxDegree, NurbsCurve->GetNodalVector().Num() - NurbsMaxDegree); } } TSharedRef MergedNURBS = FEntity::MakeShared(NurbsMaxDegree - 1, NewNodalVector, NewPoles, NewWeights, 2); // Make new edge and delete the old ones =================================================== TSharedRef RestrictionCurve = FEntity::MakeShared(CarrierSurface, MergedNURBS); TSharedPtr NewEdge = Make(RestrictionCurve, StartVertex, EndVertex); if (!NewEdge.IsValid()) { return TSharedPtr(); } FTopologicalLoop* Loop = Edges[0].Entity->GetLoop(); ensureCADKernel(Loop != nullptr); Loop->ReplaceEdges(Edges, NewEdge); for (const FOrientedEdge& OrientedEdge : Edges) { OrientedEdge.Entity->Delete(); } return NewEdge; } void FTopologicalEdge::ReplaceEdgeVertex(bool bIsStartVertex, TSharedRef& NewVertex) { NewVertex->AddConnectedEdge(*this); TSharedPtr& OldVertex = bIsStartVertex ? StartVertex : EndVertex; if (OldVertex->GetTwinEntityCount() > 1) { OldVertex->Link(*NewVertex); } OldVertex->RemoveConnectedEdge(*this); // Delete if no more connected to any edge OldVertex->DeleteIfIsolated(); OldVertex = NewVertex; } bool FTopologicalEdge::ExtendTo(bool bIsStartExtremity, const FVector2d& NewExtremityCoordinate, TSharedRef& NewVertex) { if (bIsStartExtremity ? FMath::IsNearlyEqual(Boundary.Min, Curve->GetBoundary().Min) : FMath::IsNearlyEqual(Boundary.Max, Curve->GetBoundary().Max)) { Curve->ExtendTo(NewExtremityCoordinate); } else { FVector ProjectedPoint; double UProjectedPoint = ProjectPoint(NewVertex->GetCoordinates(), ProjectedPoint); if (FVector::Distance(ProjectedPoint, NewVertex->GetCoordinates()) > GetTolerance3D()) { return false; } if (bIsStartExtremity) { Boundary.Min = UProjectedPoint; } else { Boundary.Max = UProjectedPoint; } } ReplaceEdgeVertex(bIsStartExtremity, NewVertex); Length3D = -1.; return true; } void FTopologicalEdge::ComputeEdge2DProperties(FEdge2DProperties& EdgeCharacteristics) { const TArray& Polyline2D = Curve->Polyline.Get2DPoints(); const TArray& Polyline3D = Curve->Polyline.GetPoints(); const TArray& Parameters = Curve->Polyline.GetCoordinates(); FDichotomyFinder Finder(Curve->Polyline.GetCoordinates()); int32 StartIndex = Finder.Find(Boundary.Min); int32 EndIndex = Finder.Find(Boundary.Max); for (int32 Index = StartIndex; Index <= EndIndex; ++Index) { double Slope = ComputeUnorientedSlope(Polyline2D[Index], Polyline2D[Index + 1], 0); if (Slope > 2.) { Slope = 4. - Slope; } EdgeCharacteristics.Add(Slope, FVector::Distance(Polyline3D[Index], Polyline3D[Index + 1])); } } FVector FTopologicalEdge::GetTangentAt(const FTopologicalVertex& InVertex) { if (&InVertex == StartVertex.Get()) { return Curve->GetTangentAt(Boundary.GetMin()); } else if (&InVertex == EndVertex.Get()) { FVector Tangent = Curve->GetTangentAt(Boundary.GetMax()); Tangent *= -1.; return Tangent; } else if (InVertex.GetLink() == StartVertex->GetLink()) { return Curve->GetTangentAt(Boundary.GetMin()); } else if (InVertex.GetLink() == EndVertex->GetLink()) { FVector Tangent = Curve->GetTangentAt(Boundary.GetMax()); Tangent *= -1.; return Tangent; } else { ensureCADKernel(false); return FVector::ZeroVector; } } FVector2d FTopologicalEdge::GetTangent2DAt(const FTopologicalVertex& InVertex) { if (&InVertex == StartVertex.Get()) { return Curve->GetTangent2DAt(Boundary.GetMin()); } else if (&InVertex == EndVertex.Get()) { FVector2d Tangent = Curve->GetTangent2DAt(Boundary.GetMax()); Tangent *= -1.; return Tangent; } else if (InVertex.GetLink() == StartVertex->GetLink()) { return Curve->GetTangent2DAt(Boundary.GetMin()); } else if (InVertex.GetLink() == EndVertex->GetLink()) { FVector2d Tangent = Curve->GetTangent2DAt(Boundary.GetMax()); Tangent *= -1.; return Tangent; } else { ensureCADKernel(false); return FVector2d::ZeroVector; } } void FTopologicalEdge::SpawnIdent(FDatabase& Database) { if (!FEntity::SetId(Database)) { return; } StartVertex->SpawnIdent(Database); EndVertex->SpawnIdent(Database); Curve->SpawnIdent(Database); if (TopologicalLink.IsValid()) { TopologicalLink->SpawnIdent(Database); } if (Mesh.IsValid()) { Mesh->SpawnIdent(Database); } } FTopologicalVertex* FTopologicalEdge::SplitAt(double SplittingCoordinate, const FVector& NewVertexCoordinate, bool bKeepStartVertexConnectivity, TSharedPtr& NewEdge) { if (GetTwinEntityCount() > 1) { return nullptr; } TSharedRef MiddelVertex = FTopologicalVertex::Make(NewVertexCoordinate); if (bKeepStartVertexConnectivity) { FLinearBoundary NewEdgeBoundary(SplittingCoordinate, Boundary.Max); NewEdge = Make(Curve.ToSharedRef(), MiddelVertex, EndVertex.ToSharedRef(), NewEdgeBoundary); } else { FLinearBoundary NewEdgeBoundary(Boundary.Min, SplittingCoordinate); NewEdge = Make(Curve.ToSharedRef(), StartVertex.ToSharedRef(), MiddelVertex, NewEdgeBoundary); } if (!NewEdge.IsValid()) { return nullptr; } if (bKeepStartVertexConnectivity) { EndVertex->RemoveConnectedEdge(*this); EndVertex = MiddelVertex; Boundary.Max = SplittingCoordinate; } else { StartVertex->RemoveConnectedEdge(*this); StartVertex = MiddelVertex; Boundary.Min = SplittingCoordinate; } MiddelVertex->AddConnectedEdge(*this); ComputeLength(); Loop->SplitEdge(*this, NewEdge, bKeepStartVertexConnectivity); return &MiddelVertex.Get(); } bool FTopologicalEdge::IsSharpEdge() const { double EdgeLength = Length(); double Step = Boundary.Length() / 7; FSurfacicPolyline Polyline; Polyline.Coordinates.Reserve(5); double CurrentStep = Step; for (int32 Index = 0; Index < 5; ++Index) { Polyline.Coordinates.Add(CurrentStep); CurrentStep += Step; } Polyline.bWithNormals = true; Curve->ApproximatePolyline(Polyline); FTopologicalEdge* TwinEdge = GetFirstTwinEdge(); bool bSameOrientation = IsSameDirection(*TwinEdge); FSurfacicPolyline TwinPolyline; TwinPolyline.bWithNormals = true; TwinPolyline.Coordinates.Reserve(5); TwinEdge->ProjectTwinEdgePoints(Polyline.Points3D, bSameOrientation, TwinPolyline.Coordinates); TwinEdge->ApproximatePolyline(TwinPolyline); int32 SharpPointCoount = 0; for (int32 Index = 0; Index < 5; ++Index) { double CosAngle = Polyline.Normals[Index] | TwinPolyline.Normals[Index]; if (CosAngle < 0.94) // 20 deg { return true; } } return false; } void FTopologicalEdge::Offset2D(const FVector2d& OffsetDirection) { Curve->Offset2D(OffsetDirection); } bool FTopologicalEdge::IsConnectedTo(const FTopologicalFace* Face) const { for (FTopologicalEdge* TwinEdge : GetTwinEntities()) { if (TwinEdge->GetFace() == Face) { return true; } } return false; } TArray FTopologicalEdge::GetLinkedFaces() const { TArray NeighborFaces; NeighborFaces.Reserve(GetTwinEntities().Num()); for (FTopologicalEdge* TwinEdge : GetTwinEntities()) { FTopologicalFace* NeighborFace = TwinEdge->GetFace(); NeighborFaces.Add(NeighborFace); } return MoveTemp(NeighborFaces); } }