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