// 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.") {}
}
}