From 1c8763a201f4ab1f0f4fc58a04504ddda1e793e8 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Fri, 6 Mar 2026 22:49:09 -0500 Subject: [PATCH] feat: update FindBestRotation to use rotating calipers algorithm Co-Authored-By: Claude Opus 4.6 --- OpenNest.Core/Geometry/Entity.cs | 73 ++++++++++++++++++++----------- OpenNest.Core/Geometry/Polygon.cs | 11 +++-- OpenNest.Core/Geometry/Shape.cs | 8 ++-- 3 files changed, 56 insertions(+), 36 deletions(-) diff --git a/OpenNest.Core/Geometry/Entity.cs b/OpenNest.Core/Geometry/Entity.cs index f3dc781..95c94e7 100644 --- a/OpenNest.Core/Geometry/Entity.cs +++ b/OpenNest.Core/Geometry/Entity.cs @@ -231,39 +231,60 @@ namespace OpenNest.Geometry public static class EntityExtensions { - public static double FindBestRotation(this List entities, double stepAngle, double startAngle = 0, double endAngle = Angle.TwoPI) + public static BoundingRectangleResult FindBestRotation(this List entities, double startAngle = 0, double endAngle = Angle.TwoPI) { - startAngle = Angle.NormalizeRad(startAngle); + var points = new List(); - if (!endAngle.IsEqualTo(Angle.TwoPI)) - endAngle = Angle.NormalizeRad(endAngle); - - if (stepAngle.IsEqualTo(0.0)) - return startAngle; - - entities.ForEach(e => e.Rotate(startAngle)); - - var bestAngle = startAngle; - var bestArea = entities.GetBoundingBox().Area(); - - var steps = startAngle < endAngle - ? (endAngle - startAngle) / stepAngle - : (endAngle + Angle.TwoPI) - startAngle / stepAngle; - - for (int i = 1; i <= steps; ++i) + foreach (var entity in entities) { - entities.ForEach(e => e.Rotate(stepAngle)); - - var area = entities.GetBoundingBox().Area(); - - if (area < bestArea) + switch (entity.Type) { - bestArea = area; - bestAngle = startAngle + stepAngle * i; + case EntityType.Line: + var line = (Line)entity; + points.Add(line.StartPoint); + points.Add(line.EndPoint); + break; + + case EntityType.Arc: + var arc = (Arc)entity; + points.Add(arc.StartPoint()); + points.Add(arc.EndPoint()); + points.Add(arc.Center.Offset(arc.Radius, 0)); + points.Add(arc.Center.Offset(-arc.Radius, 0)); + points.Add(arc.Center.Offset(0, arc.Radius)); + points.Add(arc.Center.Offset(0, -arc.Radius)); + break; + + case EntityType.Circle: + var circle = (Circle)entity; + points.Add(circle.Center.Offset(circle.Radius, 0)); + points.Add(circle.Center.Offset(-circle.Radius, 0)); + points.Add(circle.Center.Offset(0, circle.Radius)); + points.Add(circle.Center.Offset(0, -circle.Radius)); + break; + + case EntityType.Polygon: + var polygon = (Polygon)entity; + points.AddRange(polygon.Vertices); + break; + + case EntityType.Shape: + var shape = (Shape)entity; + var subResult = shape.Entities.FindBestRotation(startAngle, endAngle); + return subResult; } } - return bestAngle; + if (points.Count == 0) + return new BoundingRectangleResult(startAngle, 0, 0); + + var hull = ConvexHull.Compute(points); + + bool constrained = !startAngle.IsEqualTo(0) || !endAngle.IsEqualTo(Angle.TwoPI); + + return constrained + ? RotatingCalipers.MinimumBoundingRectangle(hull, startAngle, endAngle) + : RotatingCalipers.MinimumBoundingRectangle(hull); } } } diff --git a/OpenNest.Core/Geometry/Polygon.cs b/OpenNest.Core/Geometry/Polygon.cs index 4c5d3f2..c6b8d4b 100644 --- a/OpenNest.Core/Geometry/Polygon.cs +++ b/OpenNest.Core/Geometry/Polygon.cs @@ -598,16 +598,15 @@ namespace OpenNest.Geometry } } - public double FindBestRotation(double stepAngle) + public BoundingRectangleResult FindBestRotation() { - var entities = new List(ToLines()); - return entities.FindBestRotation(stepAngle); + return RotatingCalipers.MinimumBoundingRectangle(Vertices); } - public double FindBestRotation(double stepAngle, double startAngle, double endAngle) + public BoundingRectangleResult FindBestRotation(double startAngle, double endAngle) { - var entities = new List(ToLines()); - return entities.FindBestRotation(stepAngle, startAngle, endAngle); + var hull = ConvexHull.Compute(Vertices); + return RotatingCalipers.MinimumBoundingRectangle(hull, startAngle, endAngle); } } } diff --git a/OpenNest.Core/Geometry/Shape.cs b/OpenNest.Core/Geometry/Shape.cs index 843485d..7f55f10 100644 --- a/OpenNest.Core/Geometry/Shape.cs +++ b/OpenNest.Core/Geometry/Shape.cs @@ -599,14 +599,14 @@ namespace OpenNest.Geometry get { return EntityType.Shape; } } - public double FindBestRotation(double stepAngle) + public BoundingRectangleResult FindBestRotation() { - return Entities.FindBestRotation(stepAngle); + return Entities.FindBestRotation(); } - public double FindBestRotation(double stepAngle, double startAngle, double endAngle) + public BoundingRectangleResult FindBestRotation(double startAngle, double endAngle) { - return Entities.FindBestRotation(stepAngle, startAngle, endAngle); + return Entities.FindBestRotation(startAngle, endAngle); } } }