// 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.Linq; namespace Deltares.DamEngine.Data.Geometry; /// /// Class for geometry surfaces. /// /// [Serializable] public class GeometrySurface : GeometryObject { private GeometryLoop outerLoop = new GeometryLoop(); /// /// Empty constructor /// public GeometrySurface() {} /// /// Initializes a new instance of the class. /// /// The loop. public GeometrySurface(GeometryLoop loop) { outerLoop = loop; } /// /// The circumference of the surface. /// public GeometryLoop OuterLoop { get { return outerLoop; } set { outerLoop = value; } } /// /// All internal loops encompassed by . /// public List InnerLoops { get; } = new List(); /// /// Placeholder for the previous outer loop. /// public GeometryLoop PreviousOuterLoop { get; private set; } = new GeometryLoop(); public List PreviousInnerLoops { get; } = new List(); /// /// Determines if there are any inner loops in this surface /// /// true when at least 1 inner loop is available else false public bool HasInnerLoops() { return InnerLoops.Any(); } /// /// Sets the outer loop to loop whilst storing the "old" value as previousOuterLoop. /// /// public void SetOuterLoop(GeometryLoop loop) { if (loop == OuterLoop) { return; } PreviousOuterLoop = OuterLoop; OuterLoop = loop; } /// /// Add to if it isn't already /// in that collection. /// public void AddInnerLoop(GeometryLoop aLoop) { if (!InnerLoops.Contains(aLoop) && !HasIdenticalInnerLoop(aLoop)) { InnerLoops.Add(aLoop); } } /// /// Remove all inner loops from the surface whilst updating the previous inner loops list. /// public void RemoveAllInnerLoops() { PreviousInnerLoops.Clear(); if (InnerLoops.Count <= 0) { return; } PreviousInnerLoops.AddRange(InnerLoops); InnerLoops.Clear(); } /// /// determine top of geometry surface outerloop as GeometryPointString /// /// public GeometryPointString DetermineTopGeometrySurface() { return DetermineTopCurves(); } /// /// determine bottom of geometry surface outerloop as GeometryPointString /// /// public GeometryPointString DetermineBottomGeometrySurface() { return DetermineBottomCurves(); } /// /// Creates a clone of the geometry surface /// /// The cloned GeometrySurface public GeometrySurface Clone() { // Only clone the name, the outer loop and the inner loops are not to be cloned here! var clonedGeometrySurface = new GeometrySurface { Name = Name }; return clonedGeometrySurface; } /// /// Gets the geometry bounds. /// /// public override GeometryBounds GetGeometryBounds() { return OuterLoop.GetGeometryBounds(); } private bool HasIdenticalInnerLoop(GeometryLoop loop) { return InnerLoops.Any(innerLoop => innerLoop.HasSameCurves(loop)); } /// /// Determine points at the top side of the geometry surface /// /// private GeometryPointString DetermineTopCurves() { IEnumerable minXPoints = OuterLoop.DeterminePointsAtX(OuterLoop.GetMinX()); IEnumerable maxXPoints = OuterLoop.DeterminePointsAtX(OuterLoop.GetMaxX()); //verticals at start are omitted Point2D startTopPoint = minXPoints.OrderByDescending(p => p.Z).First(); //verticals at end are omitted Point2D endTopPoint = maxXPoints.OrderByDescending(p => p.Z).First(); var topPoints = new GeometryPointString(); int currentIndex = OuterLoop.Points.IndexOf(startTopPoint); FillTopOrBottomPoints(topPoints, endTopPoint, currentIndex); return topPoints; } /// /// determine curves at the bottom side of the geometry surface /// /// private GeometryPointString DetermineBottomCurves() { //create copy, leave original IEnumerable minXPoints = OuterLoop.DeterminePointsAtX(OuterLoop.GetMinX()); IEnumerable maxXPoints = OuterLoop.DeterminePointsAtX(OuterLoop.GetMaxX()); //verticals at start are omitted Point2D startBottomPoint = minXPoints.OrderBy(p => p.Z).First(); //verticals at end are omitted Point2D endBottomPoint = maxXPoints.OrderBy(p => p.Z).First(); var bottomPoints = new GeometryPointString(); int currentIndex = OuterLoop.Points.IndexOf(endBottomPoint); FillTopOrBottomPoints(bottomPoints, startBottomPoint, currentIndex); // As the curves are sorted clockwise, the bottom curves are always orientated from right to left // while this result of this method must be from left to right so reverse the curves bottomPoints.Points.Reverse(); return bottomPoints; } private void FillTopOrBottomPoints(GeometryPointString linePoints, Point2D endPoint, int currentIndex) { while (!linePoints.Points.Contains(endPoint)) { linePoints.Points.Add(OuterLoop.Points[currentIndex++]); if (currentIndex >= OuterLoop.Points.Count) { currentIndex = 0; } } for (var i = 0; i < linePoints.Points.Count; i++) { linePoints.Points[i] = new Point2D(linePoints.Points[i].X, linePoints.Points[i].Z); } } }