// Copyright (C) Stichting Deltares 2024. 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.Linq; namespace Deltares.DamEngine.Data.Geometry; /// /// Offers helper methods for geometry operations. /// public static class GeometryHelper { /// /// Extends the geometry left to the given value of x. /// /// The geometry to extend /// The x position to move the boundary to. public static void ExtendGeometryLeft(GeometryData geometry, double toX) { if (toX > geometry.Left) { throw new ArgumentException("The new left boundary should be smaller than the current left boundary."); } Point2D[] leftPoints = geometry.GetLeftPoints().OrderBy(x => x.Z).ToArray(); List leftCurves = geometry.GetLeftCurves(); AddNewPointsCurvesLoopsAndSurfacesForExtension(geometry, toX, leftPoints, leftCurves); geometry.Rebox(); geometry.Left = toX; } /// /// Extends the geometry right to the given value of x. /// /// The geometry to extend /// The x position to move the boundary to. public static void ExtendGeometryRight(GeometryData geometry, double toX) { if (toX < geometry.Right) { throw new ArgumentException("The new right boundary should be larger than the current right boundary."); } Point2D[] rightPoints = geometry.GetRightPoints().OrderBy(x => x.Z).ToArray(); List rightCurves = geometry.GetRightCurves(); AddNewPointsCurvesLoopsAndSurfacesForExtension(geometry, toX, rightPoints, rightCurves); geometry.Rebox(); geometry.Right = toX; } private static void AddNewPointsCurvesLoopsAndSurfacesForExtension(GeometryData geometry, double toX, Point2D[] edgePoints, List edgeCurves) { var newBottomPoint = new Point2D(toX, edgePoints[0].Z); geometry.Points.Add(newBottomPoint); var newhorizontalCurve1 = new GeometryCurve(edgePoints[0], newBottomPoint); geometry.Curves.Add(newhorizontalCurve1); for (var i = 1; i < edgePoints.Length; i++) { if (edgePoints[i].LocationEquals(edgePoints[i - 1])) { continue; } var newSurface = new GeometrySurface(); var newTopPoint = new Point2D(toX, edgePoints[i].Z); geometry.Points.Add(newTopPoint); var newHorizontalCurve2 = new GeometryCurve(edgePoints[i], newTopPoint); geometry.Curves.Add(newHorizontalCurve2); var newVerticalCurve = new GeometryCurve(newBottomPoint, newTopPoint); geometry.Curves.Add(newVerticalCurve); var newLoop = new GeometryLoop(); newLoop.CurveList.Add(newhorizontalCurve1); newLoop.CurveList.Add(newVerticalCurve); newHorizontalCurve2.Reverse(); newLoop.CurveList.Add(newHorizontalCurve2); var existingVerticalCurve = GetCurvesWithPoints(edgePoints[i], edgePoints[i - 1], edgeCurves); newLoop.CurveList.Add(existingVerticalCurve); if (newLoop.IsContinuous()) { geometry.Loops.Add(newLoop); newSurface.OuterLoop = newLoop; geometry.Surfaces.Add(newSurface); } else { // Should not ever happen so can not be tested. throw new ArgumentException("The new loop is not a real continuous loop."); } newBottomPoint = newTopPoint; newhorizontalCurve1 = newHorizontalCurve2; } } /// /// Cuts the geometry left at the given value of x. /// /// The geometry. /// X position to cut the geometry. public static void CutGeometryLeft(GeometryData geometry, double atX) { GeometryCurve intersectionCurve = GetIntersectionCurveAt(geometry, atX); // check if a curve intersects the atX, if so move point. GeometryCurve[] curves = geometry.Curves.ToArray(); var splitPoints = new List(); foreach (GeometryCurve geometryCurve in curves) { var p1 = new Point2D(geometryCurve.HeadPoint.X, geometryCurve.HeadPoint.Z); var p2 = new Point2D(geometryCurve.EndPoint.X, geometryCurve.EndPoint.Z); // If head or endpoint is at atX, skip this curve if (Math.Abs(p1.X - atX) > GeometryConstants.Accuracy && Math.Abs(p2.X - atX) > GeometryConstants.TestAccuracy) { var p3 = new Point2D(intersectionCurve.HeadPoint.X, intersectionCurve.HeadPoint.Z); var p4 = new Point2D(intersectionCurve.EndPoint.X, intersectionCurve.EndPoint.Z); LineIntersection res = Routines2D.DetermineIf2DLinesIntersectStrickly(p1, p2, p3, p4, out Point2D resPoint); if (res == LineIntersection.Intersects) { var splitPoint = new Point2D(resPoint.X, resPoint.Z); // Add SplitPoint to the local list splitPoints.Add(splitPoint); } } } // Add the vertical curves between the split points AddCurvesBetweenSplitPoints(geometry, splitPoints); geometry.RegenerateGeometry(); // Remove all surfaces left of the new limit List surfs = GetSurfacesLeftOfNewLimit(geometry, atX); foreach (GeometrySurface aSurface in surfs) { DeleteSurface(geometry, aSurface); } // Remove all now obsolete curves and point at the left of the new limit Point2D[] points = geometry.Points.ToArray(); for (var i = 0; i < points.Length; i++) { if (points[i].X < atX - GeometryConstants.TestAccuracy) { geometry.DeletePointWithCurves(points[i]); } } geometry.Rebox(); } /// /// Cuts the geometry right at the given value of x. /// /// The geometry. /// X position to cut the geometry. public static void CutGeometryRight(GeometryData geometry, double atX) { GeometryCurve intersectionCurve = GetIntersectionCurveAt(geometry, atX); // check if a curve intersects the atX, if so move point. GeometryCurve[] curves = geometry.Curves.ToArray(); var splitPoints = new List(); foreach (GeometryCurve geometryCurve in curves) { var p1 = new Point2D(geometryCurve.HeadPoint.X, geometryCurve.HeadPoint.Z); var p2 = new Point2D(geometryCurve.EndPoint.X, geometryCurve.EndPoint.Z); // If head or endpoint is at atX, skip this curve if (Math.Abs(p1.X - atX) > GeometryConstants.Accuracy && Math.Abs(p2.X - atX) > GeometryConstants.Accuracy) { var p3 = new Point2D(intersectionCurve.HeadPoint.X, intersectionCurve.HeadPoint.Z); var p4 = new Point2D(intersectionCurve.EndPoint.X, intersectionCurve.EndPoint.Z); LineIntersection res = Routines2D.DetermineIf2DLinesIntersectStrickly(p1, p2, p3, p4, out Point2D resPoint); if (res == LineIntersection.Intersects) { var splitPoint = new Point2D(resPoint.X, resPoint.Z); // Add SplitPoint to the local list splitPoints.Add(splitPoint); } } } // Add the vertical curves between the split points AddCurvesBetweenSplitPoints(geometry, splitPoints); geometry.RegenerateGeometry(); // Remove all surfaces right of the new limit List surfs = GetSurfacesRightOfNewLimit(geometry, atX); foreach (GeometrySurface aSurface in surfs) { DeleteSurface(geometry, aSurface); } // Remove all now obsolete curves and points at the right of the new limit Point2D[] points = geometry.Points.ToArray(); for (var i = 0; i < points.Length; i++) { if (points[i].X > atX + GeometryConstants.TestAccuracy) { geometry.DeletePointWithCurves(points[i]); } } geometry.Rebox(); } private static void AddCurvesBetweenSplitPoints(GeometryData geometry, List splitPoints) { if (splitPoints.Count > 1) { SortSplitPoints(splitPoints); geometry.NewlyEffectedPoints.Add(splitPoints[0]); for (var i = 1; i < splitPoints.Count; i++) { var newCurve = new GeometryCurve(splitPoints[i - 1], splitPoints[i]); geometry.NewlyEffectedPoints.Add(splitPoints[i]); geometry.NewlyEffectedCurves.Add(newCurve); } } } private static void SortSplitPoints(List splitPoints) { splitPoints.Sort((a, b) => a.Z.CompareTo(b.Z)); } private static List GetSurfacesLeftOfNewLimit(GeometryData geometry, double atX) { List surfacesLeftOfLimits = new List(); foreach (GeometrySurface surface in geometry.Surfaces) { foreach (Point2D point in (IEnumerable) surface.OuterLoop.CalcPoints) { if (point.X < atX - GeometryConstants.TestAccuracy) { surfacesLeftOfLimits.Add(surface); break; } } } return surfacesLeftOfLimits; } private static List GetSurfacesRightOfNewLimit(GeometryData geometry, double atX) { List surfacesRightOfLimits = new List(); foreach (GeometrySurface surface in geometry.Surfaces) { foreach (Point2D point in (IEnumerable) surface.OuterLoop.CalcPoints) { if (point.X > atX + GeometryConstants.TestAccuracy) { surfacesRightOfLimits.Add(surface); break; } } } return surfacesRightOfLimits; } private static void DeleteSurface(GeometryData geometry, GeometrySurface aSurface) { GeometrySurface aData = aSurface; GeometryLoop outerLoop = aSurface.OuterLoop; List geometryCurveList = new List(); foreach (GeometryCurve curve in outerLoop.CurveList) { if (IsCurveAnExistingCurve(curve, aData)) geometryCurveList.Add(curve); else geometry.NewlyEffectedCurves.Add(curve); } foreach (GeometryCurve aCurve in geometryCurveList) geometry.DeleteCurve(aCurve, true); geometry.Remove( outerLoop); geometry.Remove( aSurface); geometry.NewlyEffectedCurves.Clear(); } private static bool IsCurveAnExistingCurve(GeometryCurve curve, GeometrySurface aData) { if (((curve.SurfaceAtLeft == null ? (1) : (curve.SurfaceAtLeft == aData ? 1 : 0)) & (curve.SurfaceAtRight == null ? 1 : (curve.SurfaceAtRight == aData ? 1 : 0))) != 0) { return true; } return false; } /// /// Gets the intersection curve which is a vertical helper line which can be used to find all intersections at a given x. /// /// The geometry. /// X position to create the intersection. /// private static GeometryCurve GetIntersectionCurveAt(GeometryData geometry, double atX) { var headPoint = new Point2D(atX, geometry.MaxGeometryPointsZ + 1); var endPoint = new Point2D(atX, geometry.MinGeometryPointsZ - 1); return new GeometryCurve(headPoint, endPoint); } private static GeometryCurve GetCurvesWithPoints(Point2D headPoint, Point2D endPoint, List curves) { int curveCount = curves.Count; // loop through the list of curves, check if point on line for (var index = 0; index < curveCount; index++) { GeometryCurve curve = curves[index]; // does the input point exist in this line (within aValue1 tolerance) if (Routines2D.DoesPointExistInLine(curve.HeadPoint, curve.EndPoint, headPoint, GeometryConstants.Accuracy)) { if (Routines2D.DoesPointExistInLine(curve.HeadPoint, curve.EndPoint, endPoint, GeometryConstants.Accuracy)) return curve; } } return null; } /// /// Check if a curve already exists in the geometry. /// /// /// /// public static GeometryCurve DoesCurveExist(GeometryData geometry, GeometryCurve newCurve) { foreach (GeometryCurve curve in geometry.Curves) { if (curve.HeadPoint.LocationEquals(newCurve.HeadPoint) && curve.EndPoint.LocationEquals(newCurve.EndPoint) || curve.HeadPoint.LocationEquals(newCurve.EndPoint) && curve.EndPoint.LocationEquals(newCurve.HeadPoint)) return curve; } return null; } }