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