// 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 CalcPoints { get { // explicit use of protected field to prevent stack overflow due to recursive call if (calcPoints.Count == 0) { PopulateLoopPointList(); SyncPoints(); } return calcPoints; } } /// /// 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; } // Make sure points from curves are also exist as points if (CalcPoints.Count > 0) { SyncPoints(); } var points = new List(); points.AddRange(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() { List polyGon = GetLocalPoint2DList(); Clockwise isClockWise = Routines2D.IsClockWise(polyGon); 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() { // Note: explicit use of CalcPoints to ensure calcPoints are populated List p = CalcPoints; if (p.Count > 0) { SyncPoints(); } GeometryPointString clonedGeometryPointString = base.Clone(); var clonedGeometryLoop = new GeometryLoop(); clonedGeometryLoop.Points.AddRange(clonedGeometryPointString.Points); clonedGeometryLoop.SyncCalcPoints(); foreach (GeometryCurve curve in CurveList) { clonedGeometryLoop.CurveList.Add(curve.Clone(clonedGeometryLoop.CalcPoints)); } return clonedGeometryLoop; } /// /// Gets the local 2D Points List. /// /// private List GetLocalPoint2DList() { return CalcPoints; } /// /// Populates the loop's GeometryPoint list. /// private void PopulateLoopPointList() { // explicit use of protected field to prevent stack overflow due to recursive call if (calcPoints.Count > 0) { return; } for (var index = 0; index < CurveList.Count; index++) { if (index == 0) { calcPoints.Add(CurveList[index].HeadPoint); calcPoints.Add(CurveList[index].EndPoint); } else { if (!AddEitherHeadOrEndPointOfCurveToCalcPoints(index)) { if (calcPoints.Count == 2) { calcPoints.Reverse(); } AddEitherHeadOrEndPointOfCurveToCalcPoints(index); } } } RemoveDoubleClosingPointWhenNeeded(); } private bool AddEitherHeadOrEndPointOfCurveToCalcPoints(int index) { // Compare by reference (is faster then by value) if (CurveList[index].HeadPoint == calcPoints[calcPoints.Count - 1]) { calcPoints.Add(CurveList[index].EndPoint); return true; } if (CurveList[index].EndPoint != calcPoints[calcPoints.Count - 1]) { return false; } calcPoints.Add(CurveList[index].HeadPoint); return true; } private void RemoveDoubleClosingPointWhenNeeded() { if (calcPoints.Count > 0) { if (calcPoints[0] == calcPoints[calcPoints.Count - 1]) { calcPoints.RemoveAt(calcPoints.Count - 1); } } } /// /// 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.") {} } }