mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-28 14:51:06 -04:00
MinimumBoundingRectangle(Polygon) evaluated only the first (n + 1) / 2
edges, assuming opposite edges give the same rectangle. That holds only
for centrally symmetric hulls: for the triangle (0,0), (10,0), (12,1) it
returned area 12 instead of 10, depending on vertex order.
Every edge is now a candidate. A second-half edge replaces the best only
when smaller beyond rounding, so symmetric hulls (rectangles, best-fit
pairs, whose MBR angle PairEvaluator uses directly) keep their previous
orientation instead of flipping 180 degrees on floating-point noise.
On the Desktop problem nests, 8 of 160 drawing entries now get a smaller
MBR (0.004% to 3.2%) and a turned canonical frame; none changed without a
real area gain. The problem-nest corpus is unchanged against f51e5aef.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
58 lines
2.0 KiB
C#
58 lines
2.0 KiB
C#
using OpenNest.Geometry;
|
|
|
|
namespace OpenNest.Tests.Geometry;
|
|
|
|
public class RotatingCalipersTests
|
|
{
|
|
private static readonly Vector[] Triangle = { new(0, 0), new(10, 0), new(12, 1) };
|
|
|
|
private static Polygon HullStartingAt(Vector[] vertices, int start)
|
|
{
|
|
var hull = new Polygon();
|
|
for (var i = 0; i < vertices.Length; i++)
|
|
hull.Vertices.Add(vertices[(start + i) % vertices.Length]);
|
|
return hull;
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(0)]
|
|
[InlineData(1)]
|
|
[InlineData(2)]
|
|
public void AsymmetricHull_FindsTheBestEdgeInAnyVertexOrder(int start)
|
|
{
|
|
// The edge (12,1)-(0,0) gives area 10; the others give 12 and 50. Starting at
|
|
// (0,0), that edge is last, in the half the opposite-edge shortcut skipped.
|
|
var result = RotatingCalipers.MinimumBoundingRectangle(HullStartingAt(Triangle, start));
|
|
|
|
Assert.Equal(10, result.Area, 9);
|
|
Assert.Equal(System.Math.Sqrt(145), result.Width, 9);
|
|
Assert.Equal(System.Math.Atan2(-1, -12), result.Angle, 9);
|
|
}
|
|
|
|
[Fact]
|
|
public void PointList_FindsTheBestEdgeOfItsHull()
|
|
{
|
|
var result = RotatingCalipers.MinimumBoundingRectangle(Triangle);
|
|
|
|
Assert.Equal(10, result.Area, 9);
|
|
}
|
|
|
|
[Fact]
|
|
public void SymmetricHull_KeepsAFirstHalfEdgeOrientation()
|
|
{
|
|
// A rotated rectangle's opposite edges tie up to rounding; the result must stay
|
|
// on the first two edges rather than flip 180° onto the last two.
|
|
var angle = 0.4;
|
|
var corners = new[] { new Vector(0, 0), new Vector(30, 0), new Vector(30, 7), new Vector(0, 7) };
|
|
var hull = new Polygon();
|
|
foreach (var corner in corners)
|
|
hull.Vertices.Add(corner.Rotate(angle));
|
|
|
|
var result = RotatingCalipers.MinimumBoundingRectangle(hull);
|
|
|
|
Assert.Equal(210, result.Area, 6);
|
|
var firstHalfAngles = new[] { angle, angle + System.Math.PI / 2 }.Select(a => System.Math.Round(a, 9));
|
|
Assert.Contains(System.Math.Round(result.Angle, 9), firstHalfAngles);
|
|
}
|
|
}
|