// Copyright (C) Stichting Deltares 2023. 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.Diagnostics;
namespace Deltares.DamEngine.Data.Geometry;
// ----------------------------------------------------------
// The main geometry regeneration manager
public class GeometryGenerator
{
// aValue1 list that collects all surfaces that are at the right and left of effected curves (above)
// will be used to re-assign after regeneration
private readonly Dictionary geometryCurveForwardsIsUsed = new Dictionary();
private readonly Dictionary geometryCurveReversedIsUsed = new Dictionary();
private readonly GeometryData geometryData;
private readonly Dictionary> geometryLoopDirections = new Dictionary>();
// private readonly List intersectedCurveList = new List();
private readonly List newlyDetectedSurfaceList = new List();
private readonly Routines2D routines2D = new Routines2D();
///
/// Regenerates the geometry.
///
public GeometryGenerator(GeometryData aGeometryData)
{
geometryData = aGeometryData;
}
///
/// Regenerates the geometry.
///
public void GenerateGeometry()
{
FilterOutDoublePoints();
SetupCurveSurfaceAssociations();
var firstRegeneration = true;
while (true)
{
// break up all curves at intersections
//intersectedCurveList.Clear();
//RegenerateAllCurvesIntersection(); Only add if really needed
newlyDetectedSurfaceList.Clear();
geometryLoopDirections.Clear();
int curvesCount = geometryData.Curves.Count;
// initialise IsUsed of all curves to false
for (var index1 = 0; index1 < curvesCount; index1++)
{
SetIsUsed(geometryData.Curves[index1], CurveDirection.Forward, false);
SetIsUsed(geometryData.Curves[index1], CurveDirection.Reverse, false);
}
// detect surfaces... the plaxis algorithm
int result = DetectSurfaces(firstRegeneration);
// clear the effected & intersected curve list
//intersectedCurveList.Clear(); only when really needed
if (result < 0)
{
if (!firstRegeneration)
{
break;
}
}
else
{
break;
}
firstRegeneration = false;
}
}
///
/// Adds the curve to the list in given direction.
///
///
///
///
///
private bool AddCurve(GeometryCurve aCurve, CurveDirection aDirection, GeometryLoop aEditedLoop)
{
if (FindCurveIndex(aEditedLoop, aCurve, aDirection) > -1)
{
return false;
}
aEditedLoop.CurveList.Add(aCurve);
if (!geometryLoopDirections.ContainsKey(aEditedLoop))
{
geometryLoopDirections.Add(aEditedLoop, new List());
}
geometryLoopDirections[aEditedLoop].Add(aDirection);
return true;
}
// -------------------------------------------------------------------------------------
// Loop Detection (Surface Creation) Algorithm from Plaxis
///
/// Creates the surface.
///
/// a loop.
///
private GeometrySurface CreateSurface(GeometryLoop aLoop)
{
if (aLoop.IsLoop() && aLoop.HasArea())
{
var newSurface = new GeometrySurface();
//newSurface.SetOuterLoop(aLoop); #Bka: replaced with
newSurface.OuterLoop = aLoop;
geometryData.Surfaces.Add(newSurface);
return newSurface;
}
return null;
}
///
/// Finds the index of the curve.
///
/// a geometry loop.
/// a curve.
/// a direction.
///
private int FindCurveIndex(GeometryLoop aGeometryLoop, GeometryCurve aCurve, CurveDirection aDirection)
{
if (!aGeometryLoop.CurveList.Contains(aCurve))
{
return -1;
}
int curvesCount = aGeometryLoop.CurveList.Count;
for (var index = 0; index < curvesCount; index++)
{
// Note Bka: Checking the direction does allow one curve to be added to ONE loop twice!
// This produces some strange surfaces (see LoopDetectionCase5) but that seems to be required
if (aGeometryLoop.CurveList[index] == aCurve && geometryLoopDirections[aGeometryLoop][index] == aDirection)
{
return index;
}
}
return -1;
}
///
///
///
///
///
///
private void SetIsUsed(GeometryCurve aCurve, CurveDirection aDirection, bool aValue)
{
if (aDirection == CurveDirection.Forward)
{
if (!(geometryCurveForwardsIsUsed.ContainsKey(aCurve)))
{
geometryCurveForwardsIsUsed.Add(aCurve, aValue);
}
else
{
geometryCurveForwardsIsUsed[aCurve] = aValue;
}
}
else
{
if (!(geometryCurveReversedIsUsed.ContainsKey(aCurve)))
{
geometryCurveReversedIsUsed.Add(aCurve, aValue);
}
else
{
geometryCurveReversedIsUsed[aCurve] = aValue;
}
}
}
///
///
///
///
///
///
private bool GetIsUsed(GeometryCurve aCurve, CurveDirection aDirection)
{
if (aDirection == CurveDirection.Forward)
{
return geometryCurveForwardsIsUsed[aCurve];
}
return geometryCurveReversedIsUsed[aCurve];
}
///
/// Removes all double points, replaces them in curves and newlyeffectedpoints.
/// Also deletes "zero" curves (beginpoint = endpoint) also from surface loops and newlyeffectedcurrves
///
private void FilterOutDoublePoints()
{
var doublePoints = new Dictionary();
// Make sure all points (as pointers) in curves are in the point list
foreach (GeometryCurve curve in geometryData.Curves)
{
if (!geometryData.Points.Contains(curve.HeadPoint))
{
geometryData.Points.Add(curve.HeadPoint);
}
if (!geometryData.Points.Contains(curve.EndPoint))
{
geometryData.Points.Add(curve.EndPoint);
}
}
// find double points (by location!) in point list and register them with original
for (var i = 0; i < geometryData.Points.Count; i++)
{
for (var j = 0; j < i; j++)
{
if (i != j && geometryData.Points[i].LocationEquals(geometryData.Points[j]))
{
// register the double point and the original point
doublePoints[geometryData.Points[i]] = geometryData.Points[j];
break;
}
}
}
// replace double points in curves with originals
foreach (Point2D doublePoint in doublePoints.Keys)
{
foreach (GeometryCurve curve in geometryData.Curves)
{
if (curve.HeadPoint == doublePoint)
{
curve.HeadPoint = doublePoints[doublePoint];
}
if (curve.EndPoint == doublePoint)
{
curve.EndPoint = doublePoints[doublePoint];
}
}
}
// remove curves which have the same head as end point
foreach (GeometryCurve curve in geometryData.Curves.ToArray())
{
if (curve.HeadPoint == curve.EndPoint)
{
geometryData.Curves.Remove(curve);
if (geometryData.NewlyEffectedCurves.Contains(curve))
{
geometryData.NewlyEffectedCurves.Remove(curve);
}
foreach (GeometrySurface surface in geometryData.Surfaces)
{
surface.OuterLoop.CurveList.Remove(curve);
foreach (GeometryLoop loop in surface.InnerLoops)
{
loop.CurveList.Remove(curve);
}
}
}
}
// removing curves from loops in surfaces may have created invalid surfaces, remove those here
foreach (GeometrySurface surface in geometryData.Surfaces.ToArray())
{
if (!surface.OuterLoop.HasArea())
{
geometryData.Surfaces.Remove(surface);
}
}
// remove double points from point list
foreach (Point2D point in doublePoints.Keys)
{
geometryData.Points.Remove(point);
if (geometryData.NewlyEffectedPoints.Contains(point))
{
geometryData.NewlyEffectedPoints.Remove(point);
if (!geometryData.NewlyEffectedPoints.Contains(doublePoints[point]))
{
geometryData.NewlyEffectedPoints.Add(doublePoints[point]);
}
}
}
}
private int DetectSurfaces(bool aFirstRegeneration)
{
try
{
// declare some variables
int curvesCount = geometryData.Curves.Count;
var newLoopList = new List();
var attachedCurveList = new List();
// start the first iteration
for (var index = 0; index < curvesCount * 2; index++)
{
int curveIndex = index / 2;
// look for current curve
GeometryCurve currentCurve = geometryData.Curves[curveIndex];
if (currentCurve == null)
{
continue;
}
// check the direction
CurveDirection currentCurveDirection;
if (index % 2 == 0)
{
if (GetIsUsed(currentCurve, CurveDirection.Forward))
{
continue;
}
currentCurveDirection = CurveDirection.Forward; // get the direction of the current curve
}
else
{
if (GetIsUsed(currentCurve, CurveDirection.Reverse))
{
continue;
}
currentCurveDirection = CurveDirection.Reverse; // get the direction of the current curve
}
// create aValue1 new loop
var newLoop = new GeometryLoop();
//this.geometryData.Loops.Add(newLoop);
newLoopList.Add(newLoop);
// initialise LoopBeginCurve
GeometryCurve loopBeginCurve = geometryData.Curves[curveIndex];
CurveDirection loopBeginDirection = currentCurveDirection;
while (true)
{
// set the IsUsed status
SetIsUsed(currentCurve, currentCurveDirection, true);
// add the current curve to new loop
if (!AddCurve(currentCurve, currentCurveDirection, newLoop))
{
// the curve wasn't added bcos the curve-direction pair was already present in loop.
// problem case - break here, else we'd get aValue1 hang!
// Todo: Solve this problem
if (!aFirstRegeneration)
{
//TODO:Show error message box
break;
}
return -1;
}
Point2D curveEndPoint = currentCurve.GetEndPoint(currentCurveDirection);
attachedCurveList.Clear();
var minAngle = 365.0;
var minIndex = 0;
// find all the curves that are connected to the Current Curve at curveEndPoint
for (var index2 = 0; index2 < curvesCount; index2++)
{
GeometryCurve curve = geometryData.Curves[index2];
if (curve.LocationEquals(currentCurve)) // lets not get the reverse direction of the current curve here
{
continue;
}
if (curve.HeadPoint == curveEndPoint)
{
attachedCurveList.Add(new DirectionCurve(curve, CurveDirection.Forward));
}
else if (curve.EndPoint == curveEndPoint)
{
attachedCurveList.Add(new DirectionCurve(curve, CurveDirection.Reverse));
}
}
if (attachedCurveList.Count == 0) // no curves found
{
CurveDirection oppCurrentDirection = currentCurveDirection == CurveDirection.Forward ? CurveDirection.Reverse : CurveDirection.Forward;
// if the current curve is not used in the opposite direction, it is considered in the opposite direction
if (!GetIsUsed(currentCurve, oppCurrentDirection))
{
currentCurveDirection = oppCurrentDirection;
continue;
}
break;
}
// we have aValue1 set of curves, find the one that turns right the most
if (attachedCurveList.Count > 1)
{
minIndex = -1;
Point2D point1 = currentCurve.GetEndPoint(currentCurveDirection);
Point2D point2 = currentCurve.GetHeadPoint(currentCurveDirection);
for (var index2 = 0; index2 < attachedCurveList.Count; index2++)
{
var point3 = new Point2D();
var point4 = new Point2D();
point3.X = attachedCurveList[index2].GetHeadPoint().X;
point3.Z = attachedCurveList[index2].GetHeadPoint().Z;
point4.X = attachedCurveList[index2].GetEndPoint().X;
point4.Z = attachedCurveList[index2].GetEndPoint().Z;
double angle = routines2D.FindAngle(point1, point2, point3, point4);
if (angle < minAngle)
{
minAngle = angle;
minIndex = index2;
}
}
}
DirectionCurve pickedDirectionCurve = attachedCurveList[minIndex];
if (pickedDirectionCurve.Curve == loopBeginCurve && pickedDirectionCurve.Direction == loopBeginDirection)
{
break;
}
// assign the CurrentCurve from the picked one
currentCurve = pickedDirectionCurve.Curve;
currentCurveDirection = pickedDirectionCurve.Direction;
}
}
// create surfaces!
return CreateSurfaces(newLoopList);
}
#if DEBUG
catch //(Exception ex)
{
//Todo:Show error msg box
//var lm = new LogMessage(LogMessageType.FatalError, this, "DetectSurfaces:" + ex.Message);
//LogManager.Add(lm);
return 0;
}
#else
catch
{
return 0;
}
#endif
}
private int CreateSurfaces(List aNewLoopList)
{
var newSurfacesGeoDtaObjectList = new List();
int loopsCount = aNewLoopList.Count;
int curvesCount;
GeometrySurface newSurface;
var newSurfaceList = new List();
for (var index = 0; index < loopsCount; index++)
{
GeometryLoop loop = aNewLoopList[index];
curvesCount = loop.CurveList.Count;
if (curvesCount < 2) // dont create aValue1 surface for loops that have less than 2 curves
{
continue;
}
if (curvesCount == 2) // if only 2 curves in loop, make sure they are distinct (non-repeated)
{
if (loop.CurveList[0] == loop.CurveList[1])
{
continue;
}
}
if (!loop.IsLoop())
{
continue;
}
// if the loop is clockwise, create aValue1 surface
if (loop.IsClockWise() && !CheckIfLoopEnclosesOpenPolyline(loop))
{
// TEMP: aVector1 mechanism to remember surfaces after Geometry Regeneration
// find the surface that is most repeated in the loop's in curves
newSurface = GetReassignmentSurfaceFromCurves(loop);
// an existing surface has been found
if (newSurface != null)
{
newSurface = CreateSurface(loop);
}
else // no existing surface found from its comprising curves... create aValue1 brand new surface!
{
newSurface = CreateSurface(loop);
newlyDetectedSurfaceList.Add(newSurface); // populate the newly detected surface list
newSurfacesGeoDtaObjectList.Add(newSurface);
}
if (newSurface != null)
{
newSurfaceList.Add(newSurface);
AssignSurfaceAtLeftOrRightToCurves(newSurface);
}
}
}
// clear the left and right surfaces for all curves (some curves will have redundant data)
curvesCount = geometryData.Curves.Count;
for (var index = 0; index < curvesCount; index++)
{
geometryData.Curves[index].SurfaceAtRight = null;
geometryData.Curves[index].SurfaceAtLeft = null;
}
// for the new surfaces -- assign the left/right surfaces for comprising curves, and find inner loops
int surfacesCount = newSurfaceList.Count;
for (var index = 0; index < surfacesCount; index++)
{
newSurface = newSurfaceList[index];
AssignSurfaceAtLeftOrRightToCurves(newSurface);
object newSurfaceObject = newSurface /*new object()*/;
CheckAndAddInnerLoops(ref newSurfaceObject);
//newSurface = (GeometrySurface)newSurfaceObject;
}
if (newSurfacesGeoDtaObjectList.Count > 0)
{
var lNewSurfaces = new List();
GetNewlyDetectedSurfaces(ref lNewSurfaces);
}
return surfacesCount;
}
///
/// Checks if loop encloses open polyline.
///
/// a loop.
///
private bool CheckIfLoopEnclosesOpenPolyline(GeometryLoop aLoop)
{
int curvesCount = aLoop.CurveList.Count;
if (curvesCount < 3)
{
return true;
}
for (var index = 0; index < curvesCount; index++)
{
GeometryCurve curve = aLoop.CurveList[index];
CurveDirection direction = geometryLoopDirections[aLoop][index];
var foundOppDirection = false;
for (var index1 = 0; index1 < curvesCount; index1++)
{
if (index == index1)
{
continue;
}
if (aLoop.CurveList[index1] == curve && geometryLoopDirections[aLoop][index] != direction)
{
foundOppDirection = true;
break;
}
}
if (!foundOppDirection)
{
return false;
}
}
return true;
}
private GeometrySurface GetReassignmentSurfaceFromCurves(GeometryLoop aLoop)
{
GeometrySurface surface = null;
GeometrySurface reassignmentSurface = null;
int curvesCount = aLoop.CurveList.Count;
if (!geometryLoopDirections.ContainsKey(aLoop))
{
return null;
}
for (var index = 0; index < curvesCount; index++)
{
GeometryCurve curve = aLoop.CurveList[index];
if (geometryLoopDirections[aLoop][index] == CurveDirection.Forward)
{
if (curve.SurfaceAtRight != null)
{
surface = curve.SurfaceAtRight;
}
}
else
{
if (curve.SurfaceAtLeft != null)
{
surface = curve.SurfaceAtLeft;
}
}
if (surface == null)
{
continue;
}
var maxTimesSurfacesFound = 0;
var noTimesSurfaceFound = 0;
for (var index1 = 0; index1 < curvesCount; index1++)
{
if (geometryLoopDirections[aLoop][index] == CurveDirection.Forward)
{
if (curve.SurfaceAtRight == surface)
{
noTimesSurfaceFound++;
}
}
else
{
if (curve.SurfaceAtLeft == surface)
{
noTimesSurfaceFound++;
}
}
}
if (noTimesSurfaceFound > maxTimesSurfacesFound)
{
maxTimesSurfacesFound = noTimesSurfaceFound;
reassignmentSurface = surface;
}
}
return reassignmentSurface;
}
///
/// Assigns the surface at left or right to its curves on the outerloop. This tells at which side of the curve the surface is present.
/// So after this, for each curve in the outerloop of this surface, it is known at which side the surface is located.
///
/// The surface.
private void AssignSurfaceAtLeftOrRightToCurves(GeometrySurface surface)
{
GeometryLoop loop = surface.OuterLoop;
int curvesCount = loop.CurveList.Count;
var isClockwise = true;
try
{
isClockwise = loop.IsClockWise();
}
catch (GeometryLoop.NotEnoughUniquePointsException e)
{
Debug.WriteLine(e.Message);
}
catch (InvalidOperationException e)
{
Debug.WriteLine(e.Message);
}
for (var index = 0; index < curvesCount; index++)
{
if (isClockwise)
{
if (geometryLoopDirections[loop][index] == CurveDirection.Forward)
{
loop.CurveList[index].SurfaceAtRight = surface;
}
else
{
loop.CurveList[index].SurfaceAtLeft = surface;
}
}
else
{
if (geometryLoopDirections[loop][index] == CurveDirection.Forward)
{
loop.CurveList[index].SurfaceAtLeft = surface;
}
else
{
loop.CurveList[index].SurfaceAtRight = surface;
}
}
}
}
private void SetupCurveSurfaceAssociation()
{
// clear the data
int count = geometryData.Curves.Count;
for (var i = 0; i < count; i++)
{
geometryData.Curves[i].SurfaceAtLeft = null;
geometryData.Curves[i].SurfaceAtRight = null;
}
// reset
count = geometryData.Surfaces.Count;
for (var i = 0; i < count; i++)
{
AssignSurfaceAtLeftOrRightToCurves(geometryData.Surfaces[i]);
}
}
///
/// Setups the curve surface associations.
///
private void SetupCurveSurfaceAssociations()
{
SetUpGeometryLoopDirections();
// only try to connect curves to surfaces when there are loops (i.e. surfaces)
if (geometryLoopDirections.Count > 0)
{
SetupCurveSurfaceAssociation();
}
}
private void SetUpGeometryLoopDirections()
{
geometryLoopDirections.Clear();
foreach (GeometryLoop loop in geometryData.Loops)
{
if (!geometryLoopDirections.ContainsKey(loop))
{
SetUpGeometryLoopDirections(loop);
}
}
}
private void SetUpGeometryLoopDirections(GeometryLoop aLoop)
{
if (aLoop.CurveList.Count > 0)
{
Point2D loopPoint;
geometryLoopDirections.Add(aLoop, new List());
// get the first curve
if (aLoop.CurveList[0].EndPoint == aLoop.CurveList[1].HeadPoint || aLoop.CurveList[0].EndPoint == aLoop.CurveList[1].EndPoint)
{
geometryLoopDirections[aLoop].Add(CurveDirection.Forward);
loopPoint = aLoop.CurveList[0].EndPoint;
}
else
{
geometryLoopDirections[aLoop].Add(CurveDirection.Reverse);
loopPoint = aLoop.CurveList[0].HeadPoint;
}
// the rest of the curves
for (var index1 = 1; index1 < aLoop.CurveList.Count; index1++)
{
if (loopPoint == aLoop.CurveList[index1].HeadPoint)
{
geometryLoopDirections[aLoop].Add(CurveDirection.Forward);
loopPoint = aLoop.CurveList[index1].EndPoint;
}
else
{
geometryLoopDirections[aLoop].Add(CurveDirection.Reverse);
loopPoint = aLoop.CurveList[index1].HeadPoint;
}
}
}
}
///
/// Checks and adds inner loop to the new surface.
///
///
private void CheckAndAddInnerLoops(ref object aNewSurface)
{
var newSurface = (GeometrySurface) aNewSurface;
int surfaceCount = geometryData.Surfaces.Count;
GeometryLoop newLoop = newSurface.OuterLoop;
int newPointCount = newLoop.CurveList.Count;
List newPolygon = newLoop.CalcPoints;
newSurface.InnerLoops.Clear();
for (var index = 0; index < surfaceCount; index++)
{
if (newSurface == geometryData.Surfaces[index])
{
continue;
}
var innerPointCount = 0;
var outerPointCount = 0;
var isOnPointCount = 0;
var hasOnPointCount = 0;
GeometryLoop loop = geometryData.Surfaces[index].OuterLoop;
List polygon = loop.CalcPoints;
int existingLoopPointCount = polygon.Count;
// check if it is an inner loop
for (var innerIndex = 0; innerIndex < newPointCount; innerIndex++)
{
PointInPolygon location = routines2D.CheckIfPointIsInPolygon(loop, newPolygon[innerIndex].X, newPolygon[innerIndex].Z);
if (location == PointInPolygon.InsidePolygon)
{
innerPointCount++;
}
else if (location == PointInPolygon.OnPolygonEdge)
{
isOnPointCount++;
}
}
// check if it has an inner loop
for (var innerIndex1 = 0; innerIndex1 < existingLoopPointCount; innerIndex1++)
{
PointInPolygon location = routines2D.CheckIfPointIsInPolygon(newLoop, polygon[innerIndex1].X, polygon[innerIndex1].Z);
if (location == PointInPolygon.InsidePolygon)
{
outerPointCount++;
}
else if (location == PointInPolygon.OnPolygonEdge)
{
hasOnPointCount++;
}
}
//Add New Loop as inner loop to the existing Surface
if ((innerPointCount == newPointCount) || ((innerPointCount > 0) && (newPointCount == (innerPointCount + isOnPointCount))))
{
geometryData.Surfaces[index].AddInnerLoop(newLoop);
}
//Add Inner Loop to the New Surface
if ((outerPointCount == existingLoopPointCount) || ((outerPointCount > 0) && (existingLoopPointCount == (outerPointCount + hasOnPointCount))))
{
newSurface.AddInnerLoop(loop);
}
}
}
///
/// Gets the newly detected surface from the list.
///
///
private void GetNewlyDetectedSurfaces(ref List aNewSurfaceList)
{
aNewSurfaceList.AddRange(newlyDetectedSurfaceList);
}
#region Nested type: DirectionCurve
internal struct DirectionCurve
{
internal DirectionCurve(GeometryCurve aCurve, CurveDirection aDirection)
{
Curve = aCurve;
Direction = aDirection;
}
internal GeometryCurve Curve { get; }
internal CurveDirection Direction { get; }
internal Point2D GetHeadPoint()
{
return Curve.GetHeadPoint(Direction);
}
internal Point2D GetEndPoint()
{
return Curve.GetEndPoint(Direction);
}
}
#endregion
}