Index: DamEngine/trunk/src/Deltares.DamEngine.IntegrationTests/IntegrationTests/CheckLargeResultsSets.cs =================================================================== diff -u -r6404 -r7045 --- DamEngine/trunk/src/Deltares.DamEngine.IntegrationTests/IntegrationTests/CheckLargeResultsSets.cs (.../CheckLargeResultsSets.cs) (revision 6404) +++ DamEngine/trunk/src/Deltares.DamEngine.IntegrationTests/IntegrationTests/CheckLargeResultsSets.cs (.../CheckLargeResultsSets.cs) (revision 7045) @@ -121,7 +121,7 @@ { Assert.Multiple(() => { - Assert.That(designResult.StabilityDesignResults.SafetyFactor, Is.EqualTo(1.204).Within(tolerance)); + Assert.That(designResult.StabilityDesignResults.SafetyFactor, Is.EqualTo(1.203).Within(tolerance)); Assert.That(designResult.ScenarioName, Is.EqualTo("4")); Assert.That(designResult.StabilityDesignResults.UpliftSituation.Pl3HeadAdjusted, Is.EqualTo(-0.52).Within(tol2Digits)); }); Index: DamEngine/trunk/src/Deltares.DamEngine.IntegrationTests/IntegrationTests/IssuesTests.cs =================================================================== diff -u -r6677 -r7045 --- DamEngine/trunk/src/Deltares.DamEngine.IntegrationTests/IntegrationTests/IssuesTests.cs (.../IssuesTests.cs) (revision 6677) +++ DamEngine/trunk/src/Deltares.DamEngine.IntegrationTests/IntegrationTests/IssuesTests.cs (.../IssuesTests.cs) (revision 7045) @@ -42,8 +42,8 @@ [TestCase("DWP_7", "SegDWP_7",9, 65, 57, 37, 1.264)] [TestCase("DWP_8", "SegDWP_8",27, 117, 91, 46, 1.020)] [TestCase("DWP_16", "SegDWP_16",26, 99, 74, 39, 0.713)] - [TestCase("DWP_17", "SegDWP_17",19, 82, 64, 32, 1.256)] - [TestCase("DWP_20", "SegDWP_20",24, 102, 79, 46, 1.523)] + [TestCase("DWP_17", "SegDWP_17",18, 81, 64, 33, 1.256)] + [TestCase("DWP_20", "SegDWP_20",25, 104, 80, 46, 1.523)] public void TestGeometryAndResultForIssueWithDwpsFromTutorial(string location, string segment, int surfaceCount, int curveCount, int pointCount, int surfaceLinePointCount, double safetyFactor) { const string calcDir = "TestGeometryAndResultForIssueWithDwpsFromTutorial"; Index: DamEngine/trunk/src/Deltares.DamEngine.Data/Geometry/GeometrySurface.cs =================================================================== diff -u -r7044 -r7045 --- DamEngine/trunk/src/Deltares.DamEngine.Data/Geometry/GeometrySurface.cs (.../GeometrySurface.cs) (revision 7044) +++ DamEngine/trunk/src/Deltares.DamEngine.Data/Geometry/GeometrySurface.cs (.../GeometrySurface.cs) (revision 7045) @@ -178,14 +178,20 @@ const double stopCriteriumOffset = 1e-8; double offset = initialOffset; Point2D point = null; - var geometryPointString = DetermineTopGeometrySurface(); + GeometryPointString geometryPointString = DetermineTopGeometrySurface(); geometryPointString.SortPointsByXAscending(); while (point == null && offset > stopCriteriumOffset) { // Just keep looking by halving the offset until found. point = FindValidTestPoint(geometryPointString, offset); offset /= 2.0; } + + // if no valid point is found (this can happen in case of very thin layer), try using the centroid of the outer loop + if (point == null && InnerLoops.Count == 0) + { + point = Routines2D.ComputeCentroid(geometryPointString.Points); + } return point; } Index: DamEngine/trunk/src/Deltares.DamEngine.Data.Tests/Geometry/Routines2DTests.cs =================================================================== diff -u --- DamEngine/trunk/src/Deltares.DamEngine.Data.Tests/Geometry/Routines2DTests.cs (revision 0) +++ DamEngine/trunk/src/Deltares.DamEngine.Data.Tests/Geometry/Routines2DTests.cs (revision 7045) @@ -0,0 +1,69 @@ +// 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.Collections.Generic; +using Deltares.DamEngine.Data.Geometry; +using NUnit.Framework; + +namespace Deltares.DamEngine.Data.Tests.Geometry; + +[TestFixture] +public class Routines2DTests +{ + [Test] + public void GivenTriangle_WhenDeterminingTheCentroid_ThenReturnMediansIntersection() + { + var polygon = new List + { + new Point2D(-5, -1), + new Point2D(3, -1), + new Point2D(-1, 5) + }; + + Point2D centroid = Routines2D.ComputeCentroid(polygon); + + Assert.Multiple(() => + { + Assert.That(centroid.X, Is.EqualTo(-1)); + Assert.That(centroid.Z, Is.EqualTo(1)); + }); + } + + [Test] + public void GivenRectangle_WhenDeterminingTheCentroid_ThenReturnCenter() + { + var polygon = new List + { + new Point2D(-5, -1), + new Point2D(3, -1), + new Point2D(3, 6), + new Point2D(-5, 6) + }; + + Point2D centroid = Routines2D.ComputeCentroid(polygon); + + Assert.Multiple(() => + { + Assert.That(centroid.X, Is.EqualTo(-1)); + Assert.That(centroid.Z, Is.EqualTo(2.5)); + }); + } +} \ No newline at end of file Index: DamEngine/trunk/src/Deltares.DamEngine.Data/Geometry/Routines2D.cs =================================================================== diff -u -r6404 -r7045 --- DamEngine/trunk/src/Deltares.DamEngine.Data/Geometry/Routines2D.cs (.../Routines2D.cs) (revision 6404) +++ DamEngine/trunk/src/Deltares.DamEngine.Data/Geometry/Routines2D.cs (.../Routines2D.cs) (revision 7045) @@ -294,7 +294,7 @@ al3 = al3 - (2.0 * Math.PI); } - if (((Math.PI - al3) < epsilon) || ((Math.PI + al3) < epsilon)) + if ((Math.PI - al3 < epsilon) || (Math.PI + al3 < epsilon)) { UndoAddIfNeeded(polygon, pointAdded); return PointInPolygon.OnPolygonEdge; @@ -307,7 +307,7 @@ index++; } - if ((som > (1.9 * Math.PI)) || (som < (-1.9 * Math.PI))) + if ((som > 1.9 * Math.PI) || (som < -1.9 * Math.PI)) { result = PointInPolygon.InsidePolygon; } @@ -359,7 +359,7 @@ if (lD > lAbcEps) { double lU = (-lB + Math.Sqrt(lD)) / (2 * lA); - if ((lU >= -lAbcEps) && (lU <= (1.0 + lAbcEps))) + if ((lU >= -lAbcEps) && (lU <= 1.0 + lAbcEps)) { result.Add(new Point2D { @@ -370,7 +370,7 @@ lU = (-lB - Math.Sqrt(lD)) / (2 * lA); - if ((lU >= -lAbcEps) && (lU <= (1.0 + lAbcEps))) + if ((lU >= -lAbcEps) && (lU <= 1.0 + lAbcEps)) { result.Add(new Point2D { @@ -381,7 +381,7 @@ } else if (Math.Abs(lD) <= lAbcEps) { - double lU = (-lB) / (2 * lA); + double lU = -lB / (2 * lA); if ((lU >= -lAbcEps) && (lU <= 1.0 + lAbcEps)) { result.Add(new Point2D @@ -444,7 +444,7 @@ /// public static bool AreEqual(double x1, double x2, double tolerance) { - return (Math.Abs(x1 - x2) < tolerance); + return Math.Abs(x1 - x2) < tolerance; } /// @@ -512,7 +512,7 @@ /// true when points coincide public static bool DetermineIfPointsCoincide(double point1X, double point1Z, double point2X, double point2Z, double tolerance) { - if ((Math.Abs(point1X - point2X)) < tolerance && Math.Abs(point1Z - point2Z) < tolerance) + if (Math.Abs(point1X - point2X) < tolerance && Math.Abs(point1Z - point2Z) < tolerance) { return true; } @@ -558,6 +558,32 @@ new Point2D(lineEndX, lineEndY))); } + public static Point2D ComputeCentroid(List polygon) + { + double accumulatedArea = 0; + double centerX = 0; + double centerY = 0; + + int count = polygon.Count; + + for (var i = 0; i < count; i++) + { + Point2D current = polygon[i]; + Point2D next = polygon[(i + 1) % count]; + + double cross = current.X * next.Z - next.X * current.Z; + accumulatedArea += cross; + centerX += (current.X + next.X) * cross; + centerY += (current.Z + next.Z) * cross; + } + + accumulatedArea *= 0.5f; + centerX /= 6 * accumulatedArea; + centerY /= 6 * accumulatedArea; + + return new Point2D(centerX, centerY); + } + private static void UndoAddIfNeeded(GeometryLoop polygon, bool needed) { if (needed)