// 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 Deltares.DamEngine.Data.Standard; namespace Deltares.DamEngine.Data.Geometry; /// /// Dataclass for the geometryloop. /// /// public class GeometryLoop : GeometryPointString { /// /// List of points that describe the physical surface line or surface. /// /// This property is not serialized. If you want to add point definitions, /// do so by adding/inserting a new element to . public override List Points { get { //explicit use of protected field to prevent stack overflow due to recursive call if (points.Count == 0) { points = PopulateLoopPointList(points); } return points; } } /// /// Gets the List of all Curves. /// public List CurveList { get; } = new List(); /// /// Determines whether this instance is loop. /// /// public bool IsLoop() { if (CurveList.Count <= 2) { return false; } GeometryCurve beginCurve = CurveList[0]; GeometryCurve endCurve = CurveList[^1]; // Just check that the first and last curve are connected if (beginCurve.HeadPoint == endCurve.HeadPoint || beginCurve.HeadPoint == endCurve.EndPoint || beginCurve.EndPoint == endCurve.HeadPoint || beginCurve.EndPoint == endCurve.EndPoint) { return true; } return false; } /// /// Determines whether this instance has area. /// /// public bool HasArea() { if (CurveList.Count < 3) { return false; } var points = new List(); points = PopulateLoopPointList(points); points.Add(points[0]); // Calculate area of polygon using Shoelace algorithm: var sum = 0.0; for (var i = 1; i < points.Count; i++) { sum += points[i - 1].X * points[i].Z - points[i - 1].Z * points[i].X; } return Math.Abs(sum) > 1e-6; } /// /// Determines whether [is clock wise]. /// /// /// /// Cannot determine if loop is clockwise if checked location forms a straight line with its neighboring vectors. public bool IsClockWise() { Clockwise isClockWise = Routines2D.IsClockWise(Points); if (isClockWise == Clockwise.NotEnoughUniquePoints) { throw new NotEnoughUniquePointsException(); } if (isClockWise == Clockwise.PointsOnLine) { throw new InvalidOperationException("Cannot determine if loop is clockwise if checked location forms a straight line with its neighboring vectors."); } return isClockWise == Clockwise.IsClockwise; } /// /// Determines whether this loop is continuous (i.e. the end point of a curve is the start point of the next curve). /// /// True if continuous, false if not public bool IsContinuous() { for (var i = 0; i < CurveList.Count - 1; i++) { if ((CurveList[i].EndPoint != CurveList[i + 1].HeadPoint) && (CurveList[i].EndPoint != CurveList[i + 1].EndPoint) && (CurveList[i].HeadPoint != CurveList[i + 1].HeadPoint) && (CurveList[i].HeadPoint != CurveList[i + 1].EndPoint)) { return false; } } return true; } /// /// See if a point lies in a closed surface /// /// /// public bool IsPointInLoopArea(Point2D aPoint) { return Routines2D.CheckIfPointIsInPolygon(this, aPoint.X, aPoint.Z) != PointInPolygon.OutsidePolygon; } /// /// Creates a clone of the geometry loop /// /// The cloned GeometryLoop public override GeometryLoop Clone() { GeometryPointString clonedGeometryPointString = base.Clone(); var clonedGeometryLoop = new GeometryLoop(); clonedGeometryLoop.Points.AddRange(clonedGeometryPointString.Points); foreach (GeometryCurve curve in CurveList) { clonedGeometryLoop.CurveList.Add(curve.Clone(clonedGeometryLoop.Points)); } return clonedGeometryLoop; } /// /// Populates the loop's GeometryPoint list. /// private List PopulateLoopPointList(List points) { // explicit use of protected field to prevent stack overflow due to recursive call if (points.Count > 0) { return points; } for (var index = 0; index < CurveList.Count; index++) { if (index == 0) { points.Add(CurveList[index].HeadPoint); points.Add(CurveList[index].EndPoint); } else { if (!AddEitherHeadOrEndPointOfCurveToPoints(index, points)) { if (points.Count == 2) { points.Reverse(); } AddEitherHeadOrEndPointOfCurveToPoints(index, points); } } } RemoveDoubleClosingPointWhenNeeded(); return points; } private bool AddEitherHeadOrEndPointOfCurveToPoints(int index, List points) { if (CurveList[index].HeadPoint.LocationEquals(points[points.Count - 1])) { points.Add(CurveList[index].EndPoint); return true; } if (!CurveList[index].EndPoint.LocationEquals(points[points.Count - 1])) { return false; } points.Add(CurveList[index].HeadPoint); return true; } private void RemoveDoubleClosingPointWhenNeeded() { if (points.Count > 0) { if (points[0] == points[points.Count - 1]) { points.RemoveAt(points.Count - 1); } } } /// /// Determines if all curves in the loop are equal to the curves of the given geometry loop. /// The order of the curves does not matter, nor their direction. /// /// /// true when there are curves and they are equal, else false public bool HasSameCurves(GeometryLoop geometryLoop) { foreach (GeometryCurve geometryCurve in CurveList) { if (!geometryLoop.HasSameCurveAtLocation(geometryCurve)) // .CurveList.Contains(geometryCurve)) { return false; } } return CurveList.Count > 2 && CurveList.Count == geometryLoop.CurveList.Count; } private bool HasSameCurveAtLocation(GeometryCurve geometryCurve) { foreach (GeometryCurve curve in CurveList) { if (curve.LocationEquals(geometryCurve)) { return true; } } return false; } /// /// Helper class NotEnoughUniquePointsException /// public class NotEnoughUniquePointsException : InvalidOperationException { /// /// Initializes a new instance of the class. /// public NotEnoughUniquePointsException() : base("At least 3 unique points are required to determine if the loop is running clockwise.") {} } }