// 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;
}
}