Files
UnrealEngine/Engine/Source/Runtime/Datasmith/CADKernel/Base/Private/Topo/TopologicalEdge.cpp
2025-05-18 13:04:45 +08:00

1465 lines
40 KiB
C++

// 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<FRestrictionCurve>& InCurve, const TSharedRef<FTopologicalVertex>& InVertex1, const TSharedRef<FTopologicalVertex>& 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<FRestrictionCurve>& InCurve, const TSharedRef<FTopologicalVertex>& InVertex1, const TSharedRef<FTopologicalVertex>& InVertex2)
: StartVertex(InVertex1)
, EndVertex(InVertex2)
, Curve(InCurve)
, Length3D(-1)
{
Boundary = Curve->GetBoundary();
ensureCADKernel(Boundary.IsValid());
}
FTopologicalEdge::FTopologicalEdge(const TSharedRef<FRestrictionCurve>& InCurve, const FLinearBoundary& InBoundary)
: Boundary(InBoundary)
, Curve(InCurve)
, Length3D(-1)
{
TArray<double> Coordinates = { Boundary.Min, Boundary.Max };
TArray<FCurvePoint> Points;
Curve->EvaluatePoints(Coordinates, Points);
StartVertex = FTopologicalVertex::Make(Points[0].Point);
EndVertex = FTopologicalVertex::Make(Points[1].Point);
}
FTopologicalEdge::FTopologicalEdge(const TSharedRef<FSurface>& InSurface, const FVector2d& InCoordinateVertex1, const TSharedRef<FTopologicalVertex>& InVertex1, const FVector2d& InCoordinateVertex2, const TSharedRef<FTopologicalVertex>& InVertex2)
: StartVertex(InVertex1)
, EndVertex(InVertex2)
, Length3D(-1)
{
TSharedRef<FSegmentCurve> Curve2D = FEntity::MakeShared<FSegmentCurve>(InCoordinateVertex1, InCoordinateVertex2, 2);
Curve = FEntity::MakeShared<FRestrictionCurve>(InSurface, Curve2D);
}
void FTopologicalEdge::LinkVertex()
{
StartVertex->AddConnectedEdge(*this);
EndVertex->AddConnectedEdge(*this);
if (IsDegenerated())
{
StartVertex->Link(*EndVertex);
}
}
bool FTopologicalEdge::CheckVertices()
{
TArray<double> Coordinates = { Boundary.Min, Boundary.Max };
TArray<FVector> Points;
Curve->Approximate3DPoints(Coordinates, Points);
double ToleranceGeo = GetTolerance3D();
TFunction<bool(TSharedPtr<FTopologicalVertex>, FVector)> CheckExtremityGap = [&](TSharedPtr<FTopologicalVertex> 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> FTopologicalEdge::Make(const TSharedRef<FRestrictionCurve>& InCurve, const TSharedRef<FTopologicalVertex>& InVertex1, const TSharedRef<FTopologicalVertex>& InVertex2, const FLinearBoundary& InBoundary)
{
TSharedRef<FTopologicalEdge> Edge = FEntity::MakeShared<FTopologicalEdge>(InCurve, InVertex1, InVertex2, InBoundary);
return ReturnIfValid(Edge, /*bCheckVertices*/ true);
}
TSharedPtr<FTopologicalEdge> FTopologicalEdge::Make(const TSharedRef<FRestrictionCurve>& InCurve, const TSharedRef<FTopologicalVertex>& InVertex1, const TSharedRef<FTopologicalVertex>& InVertex2)
{
TSharedRef<FTopologicalEdge> Edge = FEntity::MakeShared<FTopologicalEdge>(InCurve, InVertex1, InVertex2);
return ReturnIfValid(Edge, /*bCheckVertices*/ true);
}
TSharedPtr<FTopologicalEdge> FTopologicalEdge::Make(const TSharedRef<FRestrictionCurve>& InCurve, const FLinearBoundary& InBoundary)
{
TSharedRef<FTopologicalEdge> Edge = FEntity::MakeShared<FTopologicalEdge>(InCurve, InBoundary);
return ReturnIfValid(Edge, /*bCheckVertices*/ false);
}
TSharedPtr<FTopologicalEdge> FTopologicalEdge::Make(const TSharedRef<FRestrictionCurve>& InCurve)
{
return Make(InCurve, InCurve->GetBoundary());
}
TSharedPtr<FTopologicalEdge> FTopologicalEdge::Make(const TSharedRef<FSurface>& InSurface, const FVector2d& InCoordinateVertex1, const TSharedRef<FTopologicalVertex>& InVertex1, const FVector2d& InCoordinateVertex2, const TSharedRef<FTopologicalVertex>& InVertex2)
{
TSharedRef<FTopologicalEdge> Edge = FEntity::MakeShared<FTopologicalEdge>(InSurface, InCoordinateVertex1, InVertex1, InCoordinateVertex2, InVertex2);
return ReturnIfValid(Edge, /*bCheckVertices*/ false);
}
TSharedPtr<FTopologicalEdge> FTopologicalEdge::ReturnIfValid(TSharedRef<FTopologicalEdge>& InEdge, bool bCheckVertices)
{
if (InEdge->CheckIfDegenerated())
{
InEdge->Delete();
return TSharedPtr<FTopologicalEdge>();
}
if (bCheckVertices && !InEdge->CheckVertices())
{
InEdge->Delete();
return TSharedPtr<FTopologicalEdge>();
}
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<bool(EOrientation)> 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<FTopologicalEdge, FEdgeLink>::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<FTopologicalEdge*>(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<FVector>& 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<double>& 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<double>& InTwinCoordinates, TArray<double>& 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<FVector> Cutting3DPoints;
TwinEdge.ApproximatePoints(InTwinCoordinates, Cutting3DPoints);
ProjectTwinEdgePoints(Cutting3DPoints, IsSameDirection(TwinEdge), OutLocalCoordinates);
}
void FTopologicalEdge::TransformLocalCoordinatesToActiveEdgeCoordinates(const TArray<double>& InLocalCoordinates, TArray<double>& OutActiveEdgeCoordinates) const
{
const FTopologicalEdge& ActiveEdge = *GetLinkActiveEntity();
return ActiveEdge.TransformTwinEdgeCoordinatesToLocalCoordinates(*this, InLocalCoordinates, OutActiveEdgeCoordinates);
}
void FTopologicalEdge::TransformActiveEdgeCoordinatesToLocalCoordinates(const TArray<double>& InActiveEdgeCoordinates, TArray<double>& 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<FTopologicalEdge>& InTwinEdge, const TArray<double>& InTwinEdgePointCoords, TArray<FVector2d>& OutPoints2D)
{
if (&InTwinEdge.Get() == this)
{
Curve->Approximate2DPoints(InTwinEdgePointCoords, OutPoints2D);
}
else
{
TArray<FVector> Points3D;
InTwinEdge->ApproximatePoints(InTwinEdgePointCoords, Points3D);
TArray<double> 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<double> CuttingPointCoordinates = GetCuttingPointCoordinates();
ensureCADKernel(CuttingPointCoordinates.Num() > 1);
CuttingPointCoordinates.RemoveAt(0);
CuttingPointCoordinates.Pop();
TArray<FVector>& 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<const FVertexLink> Vertex1Edge = GetStartVertex()->GetLink();
TSharedPtr<const FVertexLink> 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<FEdgeMesh>(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<double> PreMeshEdgeCuttingPointCoords = PreMeshedTwin->GetCuttingPointCoordinates();
TArray<double> CuttingPointCoords;
TransformTwinEdgeCoordinatesToLocalCoordinates(*PreMeshedTwin, PreMeshEdgeCuttingPointCoords, CuttingPointCoords);
const TArray<FCuttingPoint>& 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<double> FTopologicalEdge::GetCuttingPointCoordinates() const
{
TArray<double> CuttingPointCoordinates;
CuttingPointCoordinates.Reserve(GetCuttingPoints().Num());
for (const FCuttingPoint& CuttingPoint : GetCuttingPoints())
{
CuttingPointCoordinates.Add(CuttingPoint.Coordinate);
}
return MoveTemp(CuttingPointCoordinates);
}
TArray<double> FTopologicalEdge::GetPreElementLengths() const
{
const TArray<FCuttingPoint>& CuttingPoints = GetCuttingPoints();
const int32 CuttingPointCount = CuttingPoints.Num();
TArray<FVector> InnerNodes;
if (CuttingPointCount > 2)
{
TArray<double> 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> FTopologicalEdge::CreateEdgeByMergingEdges(const double SmallEdgeTolerance, TArray<FOrientedEdge>& Edges, const TSharedRef<FTopologicalVertex>& StartVertex, const TSharedRef<FTopologicalVertex>& EndVertex)
{
TSharedRef<FSurface> CarrierSurface = Edges[0].Entity->GetCurve()->GetCarrierSurface();
// check if all curves are 2D NURBS
bool bAreNurbs = true;
int32 NurbsMaxDegree = 0;
TArray<TSharedPtr<FNURBSCurve>> NurbsCurves;
NurbsCurves.Reserve(Edges.Num());
bool bCanRemove = true;
for (FOrientedEdge& Edge : Edges)
{
if (Edge.Entity->GetCurve()->Get2DCurve()->GetCurveType() != ECurve::Nurbs)
{
return TSharedPtr<FTopologicalEdge>();
}
double EdgeLength = Edge.Entity->Length();
if (bCanRemove && EdgeLength < SmallEdgeTolerance)
{
NurbsCurves.Emplace(TSharedPtr<FNURBSCurve>());
bCanRemove = false;
continue; // the edge will be ignored
}
bCanRemove = true;
// Find the max degree of the NURBS
TSharedPtr<FNURBSCurve> NURBS = NurbsCurves.Emplace_GetRef(StaticCastSharedRef<FNURBSCurve>(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<FTopologicalEdge>();
}
}
bool bEdgeNeedToBeExtend = false;
int32 PoleCount = 0;
double LastCoordinate = 0;
for (int32 Index = 0; Index < Edges.Num(); Index++)
{
TSharedPtr<FNURBSCurve>& 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<FTopologicalEdge>();
}
}
else
{
NURBS = FEntity::MakeShared<FNURBSCurve>(*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<FTopologicalEdge>();
}
NurbsCurves[NeigborIndex]->ExtendTo(FVector(Target, 0.));
}
}
}
TArray<double> NewNodalVector;
TArray<double> NewWeights;
TArray<FVector> NewPoles;
NurbsMaxDegree++;
NewNodalVector.Reserve(PoleCount + NurbsMaxDegree);
NewPoles.Reserve(PoleCount + NurbsMaxDegree);
bool bIsRational = false;
for (const TSharedPtr<FNURBSCurve>& NurbsCurve : NurbsCurves)
{
if (!NurbsCurve.IsValid())
{
continue;
}
if (NurbsCurve->IsRational())
{
bIsRational = true;
break;
}
}
if (bIsRational)
{
NewWeights.Reserve(PoleCount + NurbsMaxDegree);
for (const TSharedPtr<FNURBSCurve>& 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<FNURBSCurve>& NurbsCurve : NurbsCurves)
{
if (!NurbsCurve.IsValid())
{
continue;
}
if (!NewPoles.IsEmpty())
{
NewPoles.Pop();
}
NewPoles.Append(NurbsCurve->GetPoles());
}
}
for (const TSharedPtr<FNURBSCurve>& 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<FNURBSCurve> MergedNURBS = FEntity::MakeShared<FNURBSCurve>(NurbsMaxDegree - 1, NewNodalVector, NewPoles, NewWeights, 2);
// Make new edge and delete the old ones ===================================================
TSharedRef<FRestrictionCurve> RestrictionCurve = FEntity::MakeShared<FRestrictionCurve>(CarrierSurface, MergedNURBS);
TSharedPtr<FTopologicalEdge> NewEdge = Make(RestrictionCurve, StartVertex, EndVertex);
if (!NewEdge.IsValid())
{
return TSharedPtr<FTopologicalEdge>();
}
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<FTopologicalVertex>& NewVertex)
{
NewVertex->AddConnectedEdge(*this);
TSharedPtr<FTopologicalVertex>& 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<FTopologicalVertex>& 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<FVector2d>& Polyline2D = Curve->Polyline.Get2DPoints();
const TArray<FVector>& Polyline3D = Curve->Polyline.GetPoints();
const TArray<double>& 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<FTopologicalEdge>& NewEdge)
{
if (GetTwinEntityCount() > 1)
{
return nullptr;
}
TSharedRef<FTopologicalVertex> 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<FTopologicalFace*> FTopologicalEdge::GetLinkedFaces() const
{
TArray<FTopologicalFace*> NeighborFaces;
NeighborFaces.Reserve(GetTwinEntities().Num());
for (FTopologicalEdge* TwinEdge : GetTwinEntities())
{
FTopologicalFace* NeighborFace = TwinEdge->GetFace();
NeighborFaces.Add(NeighborFace);
}
return MoveTemp(NeighborFaces);
}
}