// Copyright (C) Stichting Deltares 2025. All rights reserved. // // This file is part of the Dam Engine. // // The Dam Engine is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . // // All names, logos, and references to "Deltares" are registered trademarks of // Stichting Deltares and remain full property of Stichting Deltares at all times. // All rights reserved. using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Deltares.DamEngine.Data.Standard; namespace Deltares.DamEngine.Data.Geometry; // ---------------------------------------------------------- // The main geometry regeneration manager public class GeometryGenerator { private readonly Dictionary geometryCurveForwardsIsUsed = new(); private readonly Dictionary geometryCurveReversedIsUsed = new(); private readonly GeometryData geometryData; private readonly Dictionary> geometryLoopDirections = new(); /// /// Regenerates the geometry. /// public GeometryGenerator(GeometryData aGeometryData) { geometryData = aGeometryData; } /// /// Regenerates the geometry. /// public void GenerateGeometry() { SetupCurveSurfaceAssociations(); RegenerateAllCurvesIntersection(); geometryLoopDirections.Clear(); int curvesCount = geometryData.Curves.Count; // initialise IsUsed of all curves to false for (var index1 = 0; index1 < curvesCount; index1++) { SetIsUsed(geometryData.Curves[index1], CurveDirection.Forward, false); SetIsUsed(geometryData.Curves[index1], CurveDirection.Reverse, false); } // detect surfaces... the plaxis algorithm DetectSurfaces(); } /// /// Removes the curve from both the dictionaries geometryCurveForwardsIsUsed and geometryCurveReversedIsUsed. /// /// public void RemoveIsUsedCurve(GeometryCurve aCurve) { if ((geometryCurveForwardsIsUsed.ContainsKey(aCurve))) { geometryCurveForwardsIsUsed.Remove(aCurve); } if ((geometryCurveReversedIsUsed.ContainsKey(aCurve))) { geometryCurveReversedIsUsed.Remove(aCurve); } } /// /// Setups the curve surface associations. /// public void SetupCurveSurfaceAssociations() { SetUpGeometryLoopDirections(); // only try to connect curves to surfaces when there are loops (i.e. surfaces) if (geometryLoopDirections.Count > 0) { SetupCurveSurfaceAssociation(); } } /// /// Fill the bookkeeping dictionaries to keep track of used curves /// /// /// /// public void SetIsUsed(GeometryCurve curve, CurveDirection direction, bool isUsed) { bool isKeyPresent = geometryCurveForwardsIsUsed.ContainsKey(curve); if (direction == CurveDirection.Forward) { if (!isKeyPresent) { geometryCurveForwardsIsUsed.Add(curve, isUsed); } else { geometryCurveForwardsIsUsed[curve] = isUsed; } } else { if (!isKeyPresent) { geometryCurveReversedIsUsed.Add(curve, isUsed); } else { geometryCurveReversedIsUsed[curve] = isUsed; } } } /// /// Determines which of the two points is closest to the specified line. /// Updates the output parameters to indicate the result. /// /// The first point. /// The second point. /// The line to compare the distances to. /// A reference to a boolean indicating whether the first point is closest to the line. /// A reference to a boolean indicating whether the second point is closest to the line. protected internal static void DeterminePointClosestToLine(Point2D point1, Point2D point2, GeometryCurve line, ref bool isPoint1ClosestToLine, ref bool isPoint2ClosestToLine) { double distance1 = Routines2D.CalculateDistanceToLine(point1.X, point1.Z, line.HeadPoint.X, line.HeadPoint.Z, line.EndPoint.X, line.EndPoint.Z); double distance2 = Routines2D.CalculateDistanceToLine(point2.X, point2.Z, line.HeadPoint.X, line.HeadPoint.Z, line.EndPoint.X, line.EndPoint.Z); if (distance1 > distance2 && distance1 > GeometryConstants.Accuracy) { isPoint1ClosestToLine = false; } if (distance1 < distance2 && distance2 > GeometryConstants.Accuracy) { isPoint2ClosestToLine = false; } } /// /// Regenerates all the Parallel curves. /// /// /// /// /// protected internal bool RegenerateParallelCurves(GeometryCurve line1, GeometryCurve line2, ref bool isCurveInserted) { // check if the lines are parallel and on // |----o------|----o or o----|------o----| or // |----o------o----| or o----|------|----o // |o----o----------| or |----------o----o| Point2D localPoint1 = line1.HeadPoint; Point2D localPoint2 = line1.EndPoint; Point2D localPoint3 = line2.HeadPoint; Point2D localPoint4 = line2.EndPoint; bool isPoint3OnLine1 = Routines2D.DoesPointExistInLine(localPoint1, localPoint2, localPoint3, GeometryConstants.Accuracy); bool isPoint4OnLine1 = Routines2D.DoesPointExistInLine(localPoint1, localPoint2, localPoint4, GeometryConstants.Accuracy); bool isPoint1OnLine2 = Routines2D.DoesPointExistInLine(localPoint3, localPoint4, localPoint1, GeometryConstants.Accuracy); bool isPoint2OnLine2 = Routines2D.DoesPointExistInLine(localPoint3, localPoint4, localPoint2, GeometryConstants.Accuracy); bool isPoint1SameAsPoint3 = localPoint1.LocationEquals(localPoint3); bool isPoint1SameAsPoint4 = localPoint1.LocationEquals(localPoint4); bool isPoint2SameAsPoint3 = localPoint2.LocationEquals(localPoint3); bool isPoint2SameAsPoint4 = localPoint2.LocationEquals(localPoint4); if ((isPoint2SameAsPoint3 && isPoint1SameAsPoint4) || (isPoint2SameAsPoint4 && isPoint1SameAsPoint3)) { geometryData.Curves.Remove(line1); return true; } CheckForNearParallelLines(line1, line2, ref isPoint1OnLine2, ref isPoint2OnLine2, ref isPoint3OnLine1, ref isPoint4OnLine1); // check for parallel if ((isPoint1OnLine2 && isPoint2OnLine2) || (isPoint3OnLine1 && isPoint4OnLine1) || ((isPoint3OnLine1 || isPoint4OnLine1) && (isPoint1OnLine2 || isPoint2OnLine2))) { if (CheckCasesWhereLine1AndLine2Overlap(line1, line2, ref isCurveInserted, isPoint3OnLine1, isPoint4OnLine1, isPoint1SameAsPoint3, isPoint2SameAsPoint3, isPoint2OnLine2, isPoint1SameAsPoint4, isPoint2SameAsPoint4)) { return true; } if (CheckCasesWhereLine2IsInLine1(line1, line2, ref isCurveInserted, isPoint3OnLine1, isPoint4OnLine1, isPoint1SameAsPoint3, isPoint2SameAsPoint4, isPoint2SameAsPoint3, isPoint1SameAsPoint4)) { return true; } if (CheckCasesWhereLine1IsInLine2(line1, line2, ref isCurveInserted, isPoint1OnLine2, isPoint2OnLine2, isPoint1SameAsPoint3, isPoint2SameAsPoint4, isPoint2SameAsPoint3, isPoint1SameAsPoint4)) { return true; } if (CheckCasesWhereLine2IsInLine1WithOnePointEqual(line1, line2, isPoint4OnLine1, isPoint3OnLine1, isPoint1SameAsPoint3, isPoint2SameAsPoint3, isPoint2SameAsPoint4)) { return true; } if (CheckCasesWhereLine1IsInLine2WithOnePointEqual(line1, line2, isPoint1OnLine2, isPoint2OnLine2, isPoint1SameAsPoint3, isPoint1SameAsPoint4, isPoint2SameAsPoint4)) { return true; } } return false; } /// /// Adds the curve to the list in given direction. /// /// /// /// /// private bool AddCurve(GeometryCurve curve, CurveDirection direction, GeometryLoop editedLoop) { if (FindCurveIndex(editedLoop, curve, direction) > -1) { return false; } editedLoop.CurveList.Add(curve); if (!geometryLoopDirections.ContainsKey(editedLoop)) { geometryLoopDirections.Add(editedLoop, new List()); } geometryLoopDirections[editedLoop].Add(direction); return true; } // ------------------------------------------------------------------------------------- // Loop Detection (Surface Creation) Algorithm from Plaxis /// /// Creates the surface. /// /// a loop. /// private GeometrySurface CreateSurface(GeometryLoop loop) { if (loop.IsContinuous() && loop.HasArea()) { var newSurface = new GeometrySurface(); newSurface.SetOuterLoop(loop); if (!geometryData.HasSurfaceWithSameOuterLoop(newSurface.OuterLoop)) { geometryData.Surfaces.Add(newSurface); } return newSurface; } return null; } /// /// Finds the index of the curve. /// /// a geometry loop. /// a curve. /// a direction. /// private int FindCurveIndex(GeometryLoop geometryLoop, GeometryCurve curve, CurveDirection direction) { if (!geometryLoop.CurveList.Contains(curve)) { return -1; } int curvesCount = geometryLoop.CurveList.Count; for (var index = 0; index < curvesCount; index++) { // Note Bka: Checking the direction does allow one curve to be added to ONE loop twice! // This produces some strange surfaces (see LoopDetectionCase5) but that seems to be required if (geometryLoop.CurveList[index] == curve && geometryLoopDirections[geometryLoop][index] == direction) { return index; } } return -1; } /// /// Checks if a given geometry curve is marked as used in a specified direction. /// /// The geometry curve to evaluate. /// The direction of the curve (Forward or Reverse). /// True if the specified curve is marked as used in the provided direction; otherwise, false. private bool GetIsUsed(GeometryCurve curve, CurveDirection direction) { if (direction == CurveDirection.Forward) { return geometryCurveForwardsIsUsed[curve]; } return geometryCurveReversedIsUsed[curve]; } /// /// Makes sure that the geometry is correct by removing all double points from Points and /// NewlyEffectedPoints list, replaces their references in curves and NewlyEffectedCurves /// and by adding missing points to the Points list when needed. /// Also deletes "zero" curves (begin-point = endpoint) also from surface loops and newlyeffectedcurrves. /// Finally it removes invalid surfaces, i.e. surface that do not have a proper area. /// private void EnsureCorrectGeometry() { var doublePoints = new Dictionary(); EnsureAllPointsUsedInCurvesAreInPointsList(); IdentifyAndRegisterDoublePoints(doublePoints); ReplaceDoublePointReferencesInAllCurves(doublePoints); RemoveInvalidCurvesAndUpdateSurfaces(); RemoveInvalidSurfaces(); RemoveDoublePointsAndUpdateEffectedPoints(doublePoints); } /// /// Removes duplicate points from the geometry and updates the affected points' references accordingly. /// /// /// A dictionary mapping duplicate points to their corresponding original points. /// private void RemoveDoublePointsAndUpdateEffectedPoints(Dictionary doublePoints) { foreach (Point2D point in doublePoints.Keys) { geometryData.Points.Remove(point); if (geometryData.NewlyEffectedPoints.Contains(point)) { geometryData.NewlyEffectedPoints.Remove(point); } } } /// /// Removes invalid surfaces that do not have a defined area in their outer loop. /// private void RemoveInvalidSurfaces() { foreach (GeometrySurface surface in geometryData.Surfaces.ToArray()) { if (!surface.OuterLoop.HasArea()) { geometryData.Surfaces.Remove(surface); } } } /// /// Removes invalid curves from the geometry and updates associated surfaces accordingly. /// Invalid curves are those where the start and end points are the same. /// private void RemoveInvalidCurvesAndUpdateSurfaces() { foreach (GeometryCurve curve in geometryData.Curves.ToArray()) { if (curve.HeadPoint == curve.EndPoint) { geometryData.Curves.Remove(curve); if (geometryData.NewlyEffectedCurves.Contains(curve)) { geometryData.NewlyEffectedCurves.Remove(curve); } foreach (GeometrySurface surface in geometryData.Surfaces) { surface.OuterLoop.CurveList.Remove(curve); foreach (GeometryLoop loop in surface.InnerLoops) { loop.CurveList.Remove(curve); } } } } } /// /// Replaces all references to double points in all curves with their respective original points. /// /// A dictionary mapping each duplicate point to its corresponding original point. private void ReplaceDoublePointReferencesInAllCurves(Dictionary doublePoints) { foreach (Point2D doublePoint in doublePoints.Keys) { ReplaceDoublePointReferencesInCurves(doublePoints, doublePoint); ReplaceDoublePointReferencesInNewlyEffectedCurves(doublePoints, doublePoint); } } /// /// Replaces references to double points in newly effected curves with their associated single point references. /// /// A dictionary containing mappings of double points to their replacement single points. /// The specific double point whose references are to be replaced in the newly effected curves. private void ReplaceDoublePointReferencesInNewlyEffectedCurves(Dictionary doublePoints, Point2D doublePoint) { foreach (GeometryCurve curve in geometryData.NewlyEffectedCurves) { if (curve.HeadPoint == doublePoint) { curve.HeadPoint = doublePoints[doublePoint]; } if (curve.EndPoint == doublePoint) { curve.EndPoint = doublePoints[doublePoint]; } } } /// /// Replaces references to double points in the curves with their corresponding single-point mappings. /// /// /// A dictionary containing mappings of double points to their corresponding single points. /// /// /// The specific double point whose references are to be replaced in the curves. /// private void ReplaceDoublePointReferencesInCurves(Dictionary doublePoints, Point2D doublePoint) { foreach (GeometryCurve curve in geometryData.Curves) { if (curve.HeadPoint == doublePoint) { curve.HeadPoint = doublePoints[doublePoint]; } if (curve.EndPoint == doublePoint) { curve.EndPoint = doublePoints[doublePoint]; } } } /// /// Identifies and registers duplicate points in the geometry data based on their location. /// /// /// A dictionary where duplicate points will be registered. Each key represents a duplicate /// point, while the corresponding value represents the original point it duplicates. /// private void IdentifyAndRegisterDoublePoints(Dictionary doublePoints) { for (var i = 0; i < geometryData.Points.Count; i++) { for (var j = 0; j < i; j++) { if (i != j && geometryData.Points[i].LocationEquals(geometryData.Points[j])) { // register the double point and the original point doublePoints[geometryData.Points[i]] = geometryData.Points[j]; break; } } } } /// /// Ensures all points referenced as head or end points in curves are present /// in the list of points. If a referenced point is missing, it is either replaced /// with an existing point at the same location or added to the list of points. /// private void EnsureAllPointsUsedInCurvesAreInPointsList() { foreach (GeometryCurve curve in geometryData.Curves) { if (!geometryData.Points.Contains(curve.HeadPoint)) { Point2D point = geometryData.GetPointAtLocation(curve.HeadPoint); if (point != null) { curve.HeadPoint = point; } else { geometryData.Points.Add(curve.HeadPoint); } } if (!geometryData.Points.Contains(curve.EndPoint)) { Point2D point = geometryData.GetPointAtLocation(curve.EndPoint); if (point != null) { curve.EndPoint = point; } else { geometryData.Points.Add(curve.EndPoint); } } } } /// /// Detects and identifies all possible closed surfaces within the geometry data by analyzing curves and their orientations. /// private void DetectSurfaces() { try { // declare some variables int curvesCount = geometryData.Curves.Count; var newLoopList = new List(); var attachedCurveList = new List(); // start the first iteration for (var index = 0; index < curvesCount * 2; index++) { int curveIndex = index / 2; // look for current curve GeometryCurve currentCurve = geometryData.Curves[curveIndex]; if (currentCurve == null) { continue; } // get the direction of the current curve if (DetermineCurveDirection(index, currentCurve, out CurveDirection currentCurveDirection)) { continue; } // create new loop var newLoop = new GeometryLoop(); newLoopList.Add(newLoop); // initialise LoopBeginCurve/direction GeometryCurve loopBeginCurve = geometryData.Curves[curveIndex]; CurveDirection loopBeginDirection = currentCurveDirection; ProcessCurvesForLoopCompletion(currentCurve, currentCurveDirection, newLoop, attachedCurveList, curvesCount, loopBeginCurve, loopBeginDirection); } // create surfaces! CreateSurfaces(newLoopList); } #if DEBUG catch (Exception ex) { Debug.WriteLine(ex); } #else catch (Exception) { throw; } #endif } /// /// Processes curves to complete a loop by identifying and appending the appropriate curve sequence. /// /// The current geometry curve being processed. /// The direction of the current curve. /// The new geometry loop being constructed. /// The list of attached curves considered during processing. /// The total number of curves in the geometry data. /// The geometry curve at the beginning of the loop. /// The direction of the curve at the beginning of the loop. private void ProcessCurvesForLoopCompletion(GeometryCurve currentCurve, CurveDirection currentCurveDirection, GeometryLoop newLoop, List attachedCurveList, int curvesCount, GeometryCurve loopBeginCurve, CurveDirection loopBeginDirection) { while (true) { // set the IsUsed status SetIsUsed(currentCurve, currentCurveDirection, true); // add the current curve to new loop TryToAddCurve(currentCurve, currentCurveDirection, newLoop); Point2D curveEndPoint = currentCurve.GetEndPoint(currentCurveDirection); attachedCurveList.Clear(); var minAngle = 365.0; var minIndex = 0; // find all the curves that are connected to the Current Curve at curveEndPoint IdentifyAttachedCurves(curvesCount, currentCurve, curveEndPoint, attachedCurveList); // make sure there is at least one connected curve EnsureAttachedCurve(attachedCurveList); // if we have a set of curves, find the one that turns right the most minIndex = FindConnectedCurveWithMostRightAngle(attachedCurveList, minIndex, currentCurve, currentCurveDirection, minAngle); DirectionCurve pickedDirectionCurve = attachedCurveList[minIndex]; if (CheckWhetherLoopIsFinished(pickedDirectionCurve, loopBeginCurve, loopBeginDirection)) { break; } // assign the CurrentCurve from the picked one currentCurve = pickedDirectionCurve.Curve; currentCurveDirection = pickedDirectionCurve.Direction; } } /// /// Checks whether the loop has been completed by comparing the current curve and direction with the starting curve and direction. /// /// The current curve and its direction being evaluated. /// The initial curve of the loop. /// The initial direction of the loop's starting curve. /// True if the current curve and direction match the starting curve and direction, indicating the loop is finished; otherwise, false. private static bool CheckWhetherLoopIsFinished(DirectionCurve pickedDirectionCurve, GeometryCurve loopBeginCurve, CurveDirection loopBeginDirection) { if (pickedDirectionCurve.Curve == loopBeginCurve && pickedDirectionCurve.Direction == loopBeginDirection) { return true; } return false; } /// /// Ensures that there is at least one attached curve in the provided list. /// Throws an exception if the list is empty. /// /// The list of direction curves to check for attached curves. /// Thrown when no connected curve is found in the provided list. private static void EnsureAttachedCurve(List attachedCurveList) { if (attachedCurveList.Count == 0) // no curves found { throw new ArgumentException("DetectSurfaces: No connected Curve found"); } } /// /// Attempts to add a curve to the specified geometry loop. Throws an exception if the curve-direction pair already exists in the loop. /// /// The geometry curve to be added to the loop. /// The direction of the curve to be added. /// The geometry loop where the curve is to be added. /// Thrown when the curve-direction pair is already present in the loop. private void TryToAddCurve(GeometryCurve currentCurve, CurveDirection currentCurveDirection, GeometryLoop newLoop) { if (!AddCurve(currentCurve, currentCurveDirection, newLoop)) { // the curve wasn't added because the curve-direction pair was already present in loop. // problem case - break here, else we'd get a hang! throw new ArgumentException("DetectSurfaces: The curve could not be added to the loop"); } } /// /// Determines the direction of a geometry curve based on its index and current usage state. /// /// The index of the curve to evaluate. /// The current geometry curve to check. /// The determined direction of the current curve as an output parameter. /// Returns true if the curve is marked as used in the determined direction; otherwise, false. private bool DetermineCurveDirection(int index, GeometryCurve currentCurve, out CurveDirection currentCurveDirection) { if (index % 2 == 0) { if (GetIsUsed(currentCurve, CurveDirection.Forward)) { currentCurveDirection = CurveDirection.Forward; return true; } currentCurveDirection = CurveDirection.Forward; } else { if (GetIsUsed(currentCurve, CurveDirection.Reverse)) { currentCurveDirection = CurveDirection.Reverse; return true; } currentCurveDirection = CurveDirection.Reverse; } return false; } /// /// Finds the connected curve from the list of attached curves that forms the most rightward angle /// relative to the given curve and its direction. /// /// The list of curves that are connected to the given curve. /// The current index of the curve forming the most rightward angle. /// The current base curve being analyzed. /// The direction of the current base curve. /// The minimum angle found so far during analysis. /// The index of the curve in the attachedCurveList that forms the most rightward angle. private static int FindConnectedCurveWithMostRightAngle(List attachedCurveList, int minIndex, GeometryCurve currentCurve, CurveDirection currentCurveDirection, double minAngle) { if (attachedCurveList.Count > 1) { minIndex = -1; Point2D point1 = currentCurve.GetEndPoint(currentCurveDirection); Point2D point2 = currentCurve.GetHeadPoint(currentCurveDirection); for (var index2 = 0; index2 < attachedCurveList.Count; index2++) { var point3 = new Point2D(); var point4 = new Point2D(); point3.X = attachedCurveList[index2].GetHeadPoint().X; point3.Z = attachedCurveList[index2].GetHeadPoint().Z; point4.X = attachedCurveList[index2].GetEndPoint().X; point4.Z = attachedCurveList[index2].GetEndPoint().Z; double angle = Routines2D.FindAngle(point1, point2, point3, point4); if (angle < minAngle) { minAngle = angle; minIndex = index2; } } } return minIndex; } /// /// Identifies the curves that are connected to a given curve endpoint and adds them to the provided list. /// /// The total number of curves available for processing. /// The currently processed curve to find connections for. /// The endpoint of the current curve to check for connections. /// The list where attached curves will be added. private void IdentifyAttachedCurves(int curvesCount, GeometryCurve currentCurve, Point2D curveEndPoint, List attachedCurveList) { for (var index2 = 0; index2 < curvesCount; index2++) { GeometryCurve curve = geometryData.Curves[index2]; if (curve.LocationEquals(currentCurve)) // lets not get the reverse direction of the current curve here { continue; } if (curve.HeadPoint == curveEndPoint) { attachedCurveList.Add(new DirectionCurve(curve, CurveDirection.Forward)); } else if (curve.EndPoint == curveEndPoint) { attachedCurveList.Add(new DirectionCurve(curve, CurveDirection.Reverse)); } } } /// /// Creates surfaces from the provided list of geometry loops, validating each loop /// and assigning surfaces to the corresponding curves as needed. /// /// The list of geometry loops to process for surface creation. private void CreateSurfaces(List newLoopList) { int loopsCount = newLoopList.Count; int curvesCount; GeometrySurface newSurface; var newSurfaceList = new List(); for (var index = 0; index < loopsCount; index++) { GeometryLoop loop = newLoopList[index]; curvesCount = loop.CurveList.Count; if (!ValidateLoopCurves(curvesCount, loop)) { continue; } // if the loop is clockwise, create a surface if (loop.IsClockWise() && !CheckIfLoopEnclosesOpenPolyline(loop)) { newSurface = CreateSurface(loop); if (newSurface != null) { newSurfaceList.Add(newSurface); } } } // clear the left and right surfaces for all curves (some curves will have redundant data) curvesCount = geometryData.Curves.Count; for (var index = 0; index < curvesCount; index++) { geometryData.Curves[index].SurfaceAtRight = null; geometryData.Curves[index].SurfaceAtLeft = null; } // for the new surfaces -- assign the left/right surfaces for comprising curves, and find inner loops int surfacesCount = newSurfaceList.Count; for (var index = 0; index < surfacesCount; index++) { newSurface = newSurfaceList[index]; AssignSurfaceAtLeftOrRightToCurves(newSurface); object newSurfaceObject = newSurface /*new object()*/; CheckAndAddInnerLoops(ref newSurfaceObject); } } /// /// Validates the curves of a geometry loop to determine if the loop is eligible for surface creation. /// /// The number of curves in the loop. /// The geometry loop to validate. /// True if the loop curves meet the criteria for surface creation; otherwise, false. private static bool ValidateLoopCurves(int curvesCount, GeometryLoop loop) { if (curvesCount < 2) // dont create a surface for loops that have less than 2 curves { return false; } if (curvesCount == 2) // if only 2 curves in loop, make sure they are distinct (non-repeated) { if (loop.CurveList[0] == loop.CurveList[1]) { return false; } } if (!loop.IsContinuous() || !loop.HasArea()) { return false; } return true; } /// /// Determines whether the specified loop encloses an open polyline. /// /// The geometry loop to check. /// True if the loop encloses an open polyline or has insufficient curves; otherwise, false. private bool CheckIfLoopEnclosesOpenPolyline(GeometryLoop loop) { int curvesCount = loop.CurveList.Count; if (curvesCount < 3) { return true; } for (var index = 0; index < curvesCount; index++) { GeometryCurve curve = loop.CurveList[index]; CurveDirection direction = geometryLoopDirections[loop][index]; var foundOppDirection = false; for (var index1 = 0; index1 < curvesCount; index1++) { if (index == index1) { continue; } if (loop.CurveList[index1] == curve && geometryLoopDirections[loop][index] != direction) { foundOppDirection = true; break; } } if (!foundOppDirection) { return false; } } return true; } /// /// Assigns the surface at left or right to its curves on the outerloop. This tells at which side of the curve the surface is present. /// So after this, for each curve in the outerloop of this surface, it is known at which side the surface is located. /// /// The surface. private void AssignSurfaceAtLeftOrRightToCurves(GeometrySurface surface) { GeometryLoop loop = surface.OuterLoop; int curvesCount = loop.CurveList.Count; var isClockwise = true; try { isClockwise = loop.IsClockWise(); } catch (GeometryLoop.NotEnoughUniquePointsException e) { Debug.WriteLine(e.Message); } catch (InvalidOperationException e) { Debug.WriteLine(e.Message); } for (var index = 0; index < curvesCount; index++) { if (isClockwise) { if (geometryLoopDirections[loop][index] == CurveDirection.Forward) { loop.CurveList[index].SurfaceAtRight = surface; } else { loop.CurveList[index].SurfaceAtLeft = surface; } } else { if (geometryLoopDirections[loop][index] == CurveDirection.Forward) { loop.CurveList[index].SurfaceAtLeft = surface; } else { loop.CurveList[index].SurfaceAtRight = surface; } } } } /// /// Associates each curve with corresponding surfaces on its left and right sides /// by clearing current associations and assigning surface relationships to all curves. /// private void SetupCurveSurfaceAssociation() { // clear the data int count = geometryData.Curves.Count; for (var i = 0; i < count; i++) { geometryData.Curves[i].SurfaceAtLeft = null; geometryData.Curves[i].SurfaceAtRight = null; } // reset count = geometryData.Surfaces.Count; for (var i = 0; i < count; i++) { AssignSurfaceAtLeftOrRightToCurves(geometryData.Surfaces[i]); } } /// /// Sets up the directions for geometry loops by clearing any existing mapping and initializing /// directions for all available geometry loops. /// private void SetUpGeometryLoopDirections() { geometryLoopDirections.Clear(); foreach (GeometryLoop loop in geometryData.Loops) { if (!geometryLoopDirections.ContainsKey(loop)) { SetUpGeometryLoopDirections(loop); } } } /// /// Sets up the direction of curves in a geometry loop. /// /// The geometry loop for which the curve directions will be determined and set. private void SetUpGeometryLoopDirections(GeometryLoop loop) { if (loop.CurveList.Count > 0) { Point2D loopPoint; geometryLoopDirections.Add(loop, new List()); // get the first curve if (loop.CurveList[0].EndPoint == loop.CurveList[1].HeadPoint || loop.CurveList[0].EndPoint == loop.CurveList[1].EndPoint) { geometryLoopDirections[loop].Add(CurveDirection.Forward); loopPoint = loop.CurveList[0].EndPoint; } else { geometryLoopDirections[loop].Add(CurveDirection.Reverse); loopPoint = loop.CurveList[0].HeadPoint; } // the rest of the curves for (var index1 = 1; index1 < loop.CurveList.Count; index1++) { if (loopPoint == loop.CurveList[index1].HeadPoint) { geometryLoopDirections[loop].Add(CurveDirection.Forward); loopPoint = loop.CurveList[index1].EndPoint; } else { geometryLoopDirections[loop].Add(CurveDirection.Reverse); loopPoint = loop.CurveList[index1].HeadPoint; } } } } /// /// Checks and adds inner loop to the new surface. /// /// private void CheckAndAddInnerLoops(ref object newSurfaceObject) { var newSurface = (GeometrySurface) newSurfaceObject; int surfaceCount = geometryData.Surfaces.Count; GeometryLoop newLoop = newSurface.OuterLoop; int newPointCount = newLoop.CurveList.Count; List newPolygon = newLoop.Points; newSurface.RemoveAllInnerLoops(); for (var index = 0; index < surfaceCount; index++) { if (newSurface.Equals(geometryData.Surfaces[index])) { continue; } var innerPointCount = 0; var outerPointCount = 0; var hasOnPointCount = 0; GeometryLoop loop = geometryData.Surfaces[index].OuterLoop; List polygon = loop.Points; int existingLoopPointCount = polygon.Count; // check if newloop is an inner loop within the existing loop innerPointCount = CheckIfNewLoopIsInnerLoopOfGivenLoop(newPointCount, loop, newPolygon, innerPointCount); // check if existing loop is an inner loop to the new loop, making the newloop an outerloop for that. outerPointCount = CheckIfLoopIsInnerLoopOfNewLoop(existingLoopPointCount, newLoop, polygon, outerPointCount, ref hasOnPointCount); //Add New Loop as inner loop to the existing Surface if (innerPointCount == newPointCount) { geometryData.Surfaces[index].AddInnerLoop(newLoop); } //Add Inner Loop to the New Surface if ((outerPointCount == existingLoopPointCount) || ((outerPointCount > 0) && (existingLoopPointCount == (outerPointCount + hasOnPointCount)))) { newSurface.AddInnerLoop(loop); } } } /// /// Checks if an existing loop is an inner loop of a new loop. /// /// The number of points in the existing loop. /// The new loop being tested against. /// The list of points in the existing loop polygon. /// The count of points that are outside the new loop. /// A reference to the count of points that lie on the boundary of the new loop. /// The updated count of points that are outside the new loop. private static int CheckIfLoopIsInnerLoopOfNewLoop(int existingLoopPointCount, GeometryLoop newLoop, List polygon, int outerPointCount, ref int hasOnPointCount) { for (var innerIndex1 = 0; innerIndex1 < existingLoopPointCount; innerIndex1++) { PointInPolygon location = Routines2D.CheckIfPointIsInPolygon(newLoop, polygon[innerIndex1].X, polygon[innerIndex1].Z); if (location == PointInPolygon.InsidePolygon) { outerPointCount++; } else if (location == PointInPolygon.OnPolygonEdge) { hasOnPointCount++; } } return outerPointCount; } /// /// Checks if a new loop is an inner loop of a given existing loop by evaluating the points of the new loop against the polygon of the existing loop. /// /// The number of points in the new loop. /// The existing loop to check against. /// The points of the new loop. /// The count of points from the new loop that are inside the existing loop polygon. /// The updated count of points from the new loop that are inside the existing loop polygon. private static int CheckIfNewLoopIsInnerLoopOfGivenLoop(int newPointCount, GeometryLoop loop, List newPolygon, int innerPointCount) { for (var innerIndex = 0; innerIndex < newPointCount; innerIndex++) { PointInPolygon location = Routines2D.CheckIfPointIsInPolygon(loop, newPolygon[innerIndex].X, newPolygon[innerIndex].Z); if (location == PointInPolygon.InsidePolygon) { innerPointCount++; } } return innerPointCount; } /// /// Regenerates all the curves that have intersections. /// Find all intersections between curves and split them at the intersection points, adding curves where needed. /// Find all parallel curves and split them at the intersection pointss, adding curves where needed. /// At the end, clean up the points and curves. /// private void RegenerateAllCurvesIntersection() { var isCurveInserted = false; var hasCurveBeenSplit = true; while (hasCurveBeenSplit) { hasCurveBeenSplit = false; var geometryCurveList = new List(geometryData.NewlyEffectedCurves); geometryData.NewlyEffectedCurves.Clear(); foreach (GeometryCurve geometryCurve1 in geometryCurveList) { foreach (GeometryCurve geometryCurve2 in geometryData.Curves.ToArray()) { isCurveInserted = FindAndProcessCurveIntersections(geometryCurve1, geometryCurve2, isCurveInserted, ref hasCurveBeenSplit); } } EnsureCorrectGeometry(); } MergePoints(); DeleteInvalidAndDuplicateCurves(); } /// /// Finds and processes intersections between two geometry curves, /// potentially splitting the curves or inserting new sections if necessary. /// /// The first geometry curve involved in the intersection test. /// The second geometry curve involved in the intersection test. /// A flag indicating whether a curve section has been inserted after processing intersections. /// A reference flag indicating whether any splitting has occurred for the curves during processing. /// True if processing of the curve intersections results in changes (insertion or splitting), otherwise false. private bool FindAndProcessCurveIntersections(GeometryCurve geometryCurve1, GeometryCurve geometryCurve2, bool isCurveInserted, ref bool hasCurveBeenSplit) { Point2D geometryCurve1HeadPoint = geometryCurve1.HeadPoint; Point2D geometryCurve1EndPoint = geometryCurve1.EndPoint; Point2D geometryCurve2HeadPoint = geometryCurve2.HeadPoint; Point2D geometryCurve2EndPoint = geometryCurve2.EndPoint; if (!geometryCurve1.LocationEquals(geometryCurve2) && !RegenerateParallelCurves(geometryCurve2, geometryCurve1, ref isCurveInserted) && Routines2D.DetermineIf2DLinesIntersectStrickly(geometryCurve1HeadPoint, geometryCurve1EndPoint, geometryCurve2HeadPoint, geometryCurve2EndPoint, out Point2D intersectionPoint) == LineIntersection.Intersects) { if (!Routines2D.DetermineIfPointsCoincide(geometryCurve1HeadPoint.X, geometryCurve1HeadPoint.Z, intersectionPoint.X, intersectionPoint.Z, 0.001) && !Routines2D.DetermineIfPointsCoincide(geometryCurve1EndPoint.X, geometryCurve1EndPoint.Z, intersectionPoint.X, intersectionPoint.Z, 0.001)) { Point2D point = geometryData.CreatePointAndAddItToTheProperListsWhenNeeded(new Point2D(intersectionPoint.X, intersectionPoint.Z)); SplitCurve(geometryCurve1, point); hasCurveBeenSplit = true; } if (!Routines2D.DetermineIfPointsCoincide(geometryCurve2HeadPoint.X, geometryCurve2HeadPoint.Z, intersectionPoint.X, intersectionPoint.Z, 0.001) && !Routines2D.DetermineIfPointsCoincide(geometryCurve2EndPoint.X, geometryCurve2EndPoint.Z, intersectionPoint.X, intersectionPoint.Z, 0.001)) { Point2D point = geometryData.CreatePointAndAddItToTheProperListsWhenNeeded(new Point2D(intersectionPoint.X, intersectionPoint.Z)); SplitCurve(geometryCurve2, point); hasCurveBeenSplit = true; } } return isCurveInserted; } /// /// Merges coinciding points in the geometry data to eliminate redundancy and maintain geometric consistency. /// private void MergePoints() { int count = geometryData.Points.Count; var aConnectedAtHeadCurveList = new List(); var aConnectedAtEndCurveList = new List(); List list = geometryData.NewlyEffectedPoints.Select((Func) (newPoint => geometryData.Points.IndexOf(newPoint))).ToList(); list.Reverse(); foreach (int index1 in list) { if (index1 >= 0 && index1 < geometryData.Points.Count) { for (int index2 = count - 1; index2 >= 0; --index2) { if ((index2 != index1) && (Routines2D.DetermineIfPointsCoincide( geometryData.Points[index1].X, geometryData.Points[index1].Z, geometryData.Points[index2].X, geometryData.Points[index2].Z, 0.001))) { aConnectedAtHeadCurveList.Clear(); aConnectedAtEndCurveList.Clear(); DetermineConnectedCurves(geometryData.Points[index1], ref aConnectedAtHeadCurveList, ref aConnectedAtEndCurveList); AssignPointToCurves(aConnectedAtHeadCurveList, geometryData.Points[index2], true); AssignPointToCurves(aConnectedAtEndCurveList, geometryData.Points[index2], false); --count; geometryData.DeletePointAndPossibleObsoleteCurveItsPartOf(geometryData.Points[index1]); break; } } } } geometryData.NewlyEffectedPoints.Clear(); } /// /// Determines the slope of a given geometry curve. /// /// The geometry curve for which the slope is to be calculated. /// Returns the slope of the curve as a double. If the curve is vertical, returns double.MaxValue. private static double DetermineSlope(GeometryCurve line) { if (line.HeadPoint.X.IsNearEqual(line.EndPoint.X)) { return double.MaxValue; } return (line.HeadPoint.Z - line.EndPoint.Z) / (line.HeadPoint.X - line.EndPoint.X); } /// /// Checks whether line1 is completely within line2 based on the provided geometric conditions. /// /// The first geometry curve to be evaluated. /// The second geometry curve to be evaluated. /// Indicates if a curve has been inserted as a result of this check. /// Specifies whether the first point of line1 is on line2. /// Specifies whether the second point of line1 is on line2. /// Specifies whether the first point of line1 is the same as the third point of line2. /// Indicates whether the second point of line1 is the same as the fourth point of line2. /// Indicates whether the second point of line1 is the same as the third point of line2. /// Indicates whether the first point of line1 is the same as the fourth point of line2. /// /// Returns true if line1 is determined to be completely within line2 and modifies the curve accordingly. Returns false otherwise. /// private bool CheckCasesWhereLine1IsInLine2(GeometryCurve line1, GeometryCurve line2, ref bool isCurveInserted, bool isPoint1OnLine2, bool isPoint2OnLine2, bool isPoint1SameAsPoint3, bool isPoint2SameAsPoint4, bool isPoint2SameAsPoint3, bool isPoint1SameAsPoint4) { // p3----p1------p2----p4 if (isPoint1OnLine2 && isPoint2OnLine2 && !isPoint1SameAsPoint3 && !isPoint2SameAsPoint4 && !isPoint2SameAsPoint3 && !isPoint1SameAsPoint4) { double distance1 = Routines2D.Compute2DDistance(line2.HeadPoint.X, line2.HeadPoint.Z, line1.HeadPoint.X, line1.HeadPoint.Z); double distance2 = Routines2D.Compute2DDistance(line2.HeadPoint.X, line2.HeadPoint.Z, line1.EndPoint.X, line1.EndPoint.Z); GeometryCurve newLine; if (distance1 < distance2) { newLine = SplitCurve(line2, line1.HeadPoint); newLine.HeadPoint = line1.EndPoint; } else { newLine = SplitCurve(line2, line1.EndPoint); newLine.HeadPoint = line1.HeadPoint; } isCurveInserted = true; return true; } return false; } /// /// Checks the cases where the second line (line2) is completely contained within the first line (line1). /// Modifies or generates new geometric curves as necessary based on specific conditions. /// /// The first geometric curve. /// The second geometric curve to check whether it is inside the first line. /// A flag indicating whether a new curve was inserted during the operation, passed by reference. /// Indicates whether the third point of the second line lies on the first line. /// Indicates whether the fourth point of the second line lies on the first line. /// Indicates whether the head point of the first line matches the third point of the second line. /// Indicates whether the end point of the first line matches the fourth point of the second line. /// Indicates whether the second point of the first line matches the third point of the second line. /// Indicates whether the first point of the first line matches the fourth point of the second line. /// True if the second line is contained within the first line and the operation was successfully processed; otherwise, false. private bool CheckCasesWhereLine2IsInLine1(GeometryCurve line1, GeometryCurve line2, ref bool isCurveInserted, bool isPoint3OnLine1, bool isPoint4OnLine1, bool isPoint1SameAsPoint3, bool isPoint2SameAsPoint4, bool isPoint2SameAsPoint3, bool isPoint1SameAsPoint4) { // p1----p3------p4----p2 if (isPoint3OnLine1 && isPoint4OnLine1 && !isPoint1SameAsPoint3 && !isPoint2SameAsPoint4 && !isPoint2SameAsPoint3 && !isPoint1SameAsPoint4) { double distance1 = Routines2D.Compute2DDistance(line1.HeadPoint.X, line1.HeadPoint.Z, line2.HeadPoint.X, line2.HeadPoint.Z); double distance2 = Routines2D.Compute2DDistance(line1.HeadPoint.X, line1.HeadPoint.Z, line2.EndPoint.X, line2.EndPoint.Z); GeometryCurve newLine; if (distance1 < distance2) { newLine = SplitCurve(line1, line2.HeadPoint); newLine.HeadPoint = line2.EndPoint; } else { newLine = SplitCurve(line1, line2.EndPoint); newLine.HeadPoint = line2.HeadPoint; } isCurveInserted = true; return true; } return false; } /// /// Checks and handles cases where two geometry curves overlap. /// /// The first geometry curve. /// The second geometry curve. /// A reference to a boolean indicating if a new curve is inserted due to overlap resolution. /// Indicates if the third point of line2 lies on line1. /// Indicates if the fourth point of line2 lies on line1. /// Indicates if the first point of line1 is identical to the third point of line2. /// Indicates if the second point of line1 is identical to the third point of line2. /// Indicates if the second point of line1 lies on line2. /// Indicates if the first point of line1 is identical to the fourth point of line2. /// Indicates if the second point of line1 is identical to the fourth point of line2. /// Returns true if overlap cases are detected and handled; otherwise, false. private bool CheckCasesWhereLine1AndLine2Overlap(GeometryCurve line1, GeometryCurve line2, ref bool isCurveInserted, bool isPoint3OnLine1, bool isPoint4OnLine1, bool isPoint1SameAsPoint3, bool isPoint2SameAsPoint3, bool isPoint2OnLine2, bool isPoint1SameAsPoint4, bool isPoint2SameAsPoint4) { GeometryCurve newLine; // p1----p3------p2----p4 if (isPoint3OnLine1 && !isPoint4OnLine1 && !isPoint1SameAsPoint3 && !isPoint2SameAsPoint3) { newLine = SplitCurve(line1, line2.HeadPoint); line2.HeadPoint = isPoint2OnLine2 ? newLine.EndPoint : line1.HeadPoint; isCurveInserted = true; return true; } // p1----p4------p2----p3 if (isPoint4OnLine1 && !isPoint3OnLine1 && !isPoint1SameAsPoint4 && !isPoint2SameAsPoint4) { newLine = SplitCurve(line1, line2.EndPoint); line2.EndPoint = isPoint2OnLine2 ? newLine.EndPoint : line1.HeadPoint; isCurveInserted = true; return true; } return false; } /// /// Checks specific cases where one line is completely within another, /// and one endpoint of the second line is equal to an internal point of the first line. /// /// The first geometry curve to check against. /// The second geometry curve to check within the first curve. /// Indicates whether the fourth point of line2 lies on line1. /// Indicates whether the third point of line2 lies on line1. /// Indicates whether the first point of line1 is the same as the third point of line2. /// Indicates whether the second point of line1 is the same as the third point of line2. /// Indicates whether the second point of line1 is the same as the fourth point of line2. /// True if line2 is determined to be within line1 under the specified conditions; otherwise, false. private static bool CheckCasesWhereLine2IsInLine1WithOnePointEqual(GeometryCurve line1, GeometryCurve line2, bool isPoint4OnLine1, bool isPoint3OnLine1, bool isPoint1SameAsPoint3, bool isPoint2SameAsPoint3, bool isPoint2SameAsPoint4) { if (isPoint4OnLine1 && isPoint3OnLine1) { // Line2 on Line1 like p1--p3--p4--p2, p2--p3--p4--p1 etc. if (isPoint1SameAsPoint3) { // p1/p3----p4----p2 --> c1(p4, p2) & c2(p3, p4) line1.HeadPoint = line2.EndPoint; } else if (isPoint2SameAsPoint3) { // p2/p3----p4----p1 --> c1(p1, p4) & c2(p3, p4) line1.EndPoint = line2.EndPoint; } else if (isPoint2SameAsPoint4) { // p2/p4----p3----p1 --> c1(p1, p3) & c2(p3, p4) line1.EndPoint = line2.HeadPoint; } else { // p1/p4----p3----p2 --> c1(p3, p2) & c2(p3, p4) line1.HeadPoint = line2.HeadPoint; } return true; } return false; } /// /// Checks and handles cases where the first line segment exists within /// the second line segment with one endpoint being equal. /// /// The first geometry curve to analyze. /// The second geometry curve to analyze. /// Indicates whether the starting point of the first curve is on the second curve. /// Indicates whether the ending point of the first curve is on the second curve. /// Indicates whether the starting point of the first curve is the same as a reference third point. /// Indicates whether the starting point of the first curve is the same as a reference fourth point. /// Indicates whether the ending point of the first curve is the same as a reference fourth point. /// True if line1 is found within line2 based on the specified conditions; otherwise, false. private static bool CheckCasesWhereLine1IsInLine2WithOnePointEqual(GeometryCurve line1, GeometryCurve line2, bool isPoint1OnLine2, bool isPoint2OnLine2, bool isPoint1SameAsPoint3, bool isPoint1SameAsPoint4, bool isPoint2SameAsPoint4) { // Line1 on Line2 like p3--p1--p2--p4, p4--p1--p2--p3 etc. if (isPoint1OnLine2 && isPoint2OnLine2) { if (isPoint1SameAsPoint3) { //p3/p1--p2--p4 line2.HeadPoint = line1.EndPoint; } else if (isPoint1SameAsPoint4) { //p4/p1--p2--p3 line2.EndPoint = line1.EndPoint; } else if (isPoint2SameAsPoint4) { //p4/p2--p1--p3 line2.EndPoint = line1.HeadPoint; } else { //p3/p2--p1--p4 line2.HeadPoint = line1.HeadPoint; } return true; } return false; } /// /// Checks if two geometry curves are near-parallel, adjusting the provided flags /// based on the geometric relationships between the curves. /// /// The first geometry curve to compare. /// The second geometry curve to compare. /// A flag indicating whether the head point of the first curve is on the second curve. /// A flag indicating whether the end point of the first curve is on the second curve. /// A flag indicating whether the head point of the second curve is on the first curve. /// A flag indicating whether the end point of the second curve is on the first curve. private static void CheckForNearParallelLines(GeometryCurve line1, GeometryCurve line2, ref bool isPoint1OnLine2, ref bool isPoint2OnLine2, ref bool isPoint3OnLine1, ref bool isPoint4OnLine1) { double slope1 = DetermineSlope(line1); double slope2 = DetermineSlope(line2); const double accuracyAngle = 5E-12; if (Math.Abs(Math.Abs(slope1) - Math.Abs(slope2)) > accuracyAngle) { if (isPoint1OnLine2 && isPoint2OnLine2) { // lines are not (exactly) parallel but within the geometry accuracy off each other DeterminePointClosestToLine(line1.HeadPoint, line1.EndPoint, line2, ref isPoint1OnLine2, ref isPoint2OnLine2); } if (isPoint3OnLine1 && isPoint4OnLine1) { // lines are not (exactly) parallel but within the geometry accuracy off each other DeterminePointClosestToLine(line2.HeadPoint, line2.EndPoint, line1, ref isPoint3OnLine1, ref isPoint4OnLine1); } } } /// /// Splits the given curve at the specified point on the curve and creates a new curve from the split point to the original curve's end point. /// /// The source curve that will be split. /// The point on the source curve where the split will occur. /// A new curve created as a result of the split, or null if the split point matches the curve's start or end point. private GeometryCurve SplitCurve(GeometryCurve srcCurve, Point2D pointOnCurve) { if (pointOnCurve.LocationEquals(srcCurve.HeadPoint) || pointOnCurve.LocationEquals(srcCurve.EndPoint)) { return null; } GeometryCurve newCurve = geometryData.CreateCurve(pointOnCurve, srcCurve.EndPoint); srcCurve.EndPoint = newCurve.HeadPoint; newCurve.AssignSurfacesFromCurve(srcCurve); if (!geometryData.DoesCurveExist(newCurve)) { geometryData.Curves.Add(newCurve); } return newCurve; } /// /// Determines the curves connected to a specified point, separating them into those connected at the head and those connected at the end. /// /// The point for which connected curves are to be determined. /// A reference to the list where curves connected at the head will be stored. /// A reference to the list where curves connected at the end will be stored. private void DetermineConnectedCurves( Point2D point, ref List connectedAtHeadCurveList, ref List connectedAtEndCurveList) { connectedAtHeadCurveList.Clear(); connectedAtEndCurveList.Clear(); int count = geometryData.Curves.Count; for (var index = 0; index < count; ++index) { GeometryCurve curve = geometryData.Curves[index]; if (curve.HeadPoint == point) { connectedAtHeadCurveList.Add(curve); } if (curve.EndPoint == point) { connectedAtEndCurveList.Add(curve); } } } /// /// Assigns a point to the head or end points of a list of geometry curves. /// /// The list of geometry curves to assign the point to. /// The point to assign to the curves. /// If set to true, assigns the point to the head of each curve; otherwise, assigns it to the end. private static void AssignPointToCurves(List aCurveList, Point2D aPoint, bool aHead) { int count = aCurveList.Count; if (count < 1) { return; } for (var i = 0; i < count; ++i) { if (aHead) { aCurveList[i].HeadPoint = aPoint; } else { aCurveList[i].EndPoint = aPoint; } } } /// /// Deletes invalid and duplicate curves from the geometry data. /// Invalid curves are those where the head and end points are identical. /// Duplicate curves are those having the same start and end points, or are reversed duplicates of one another. /// private void DeleteInvalidAndDuplicateCurves() { List curvesToDelete = geometryData.Curves.Where((Func) (curve => curve.HeadPoint == curve.EndPoint)).ToList(); foreach (GeometryCurve aCurve in curvesToDelete) { geometryData.DeleteCurve(aCurve, true); } curvesToDelete.Clear(); for (var i = 0; i < geometryData.Curves.Count; ++i) { GeometryCurve curve1 = geometryData.Curves[i]; for (int j = i + 1; j < geometryData.Curves.Count; ++j) { GeometryCurve curve2 = geometryData.Curves[j]; if ((curve1.HeadPoint == curve2.HeadPoint && curve1.EndPoint == curve2.EndPoint) || (curve1.HeadPoint == curve2.EndPoint && curve1.EndPoint == curve2.HeadPoint)) { curvesToDelete.Add(curve2); } } } foreach (GeometryCurve aCurve in curvesToDelete) { geometryData.DeleteCurve(aCurve, true); } } #region Nested type: DirectionCurve /// /// Represents a curve along with its associated direction. /// /// /// The struct encapsulates a object and its /// corresponding . It provides methods to retrieve the start and end points /// of the curve in the specified direction. This struct is primarily used in geometry processing tasks /// involving directional curves. /// private readonly struct DirectionCurve { internal DirectionCurve(GeometryCurve aCurve, CurveDirection aDirection) { Curve = aCurve; Direction = aDirection; } internal GeometryCurve Curve { get; } internal CurveDirection Direction { get; } internal Point2D GetHeadPoint() { return Curve.GetHeadPoint(Direction); } internal Point2D GetEndPoint() { return Curve.GetEndPoint(Direction); } } #endregion }