From 00ede7c5d075d0b706cc1251bd20d8767fa9d06c Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Tue, 29 Sep 2026 19:27:23 -0400 Subject: [PATCH] fix(math): normalize degree interval checks in degrees --- OpenNest.Core/Math/Angle.cs | 8 +-- OpenNest.Tests/Math/AngleTests.cs | 82 +++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 OpenNest.Tests/Math/AngleTests.cs diff --git a/OpenNest.Core/Math/Angle.cs b/OpenNest.Core/Math/Angle.cs index 82708f2..6023e42 100644 --- a/OpenNest.Core/Math/Angle.cs +++ b/OpenNest.Core/Math/Angle.cs @@ -1,4 +1,4 @@ -namespace OpenNest.Math +namespace OpenNest.Math { public static class Angle { @@ -106,14 +106,14 @@ if (reversed) Generic.Swap(ref a1, ref a2); - var diff = Angle.NormalizeRad(a2 - a1); + var diff = Angle.NormalizeDeg(a2 - a1); // full circle if (a2.IsEqualTo(a1)) return true; - a1 = Angle.NormalizeRad(angle - a1); - a2 = Angle.NormalizeRad(a2 - angle); + a1 = Angle.NormalizeDeg(angle - a1); + a2 = Angle.NormalizeDeg(a2 - angle); return diff >= a1 - Tolerance.Epsilon || diff >= a2 - Tolerance.Epsilon; } diff --git a/OpenNest.Tests/Math/AngleTests.cs b/OpenNest.Tests/Math/AngleTests.cs new file mode 100644 index 0000000..d7dfee6 --- /dev/null +++ b/OpenNest.Tests/Math/AngleTests.cs @@ -0,0 +1,82 @@ +using Xunit; + +namespace OpenNest.Tests.Math; + +public class AngleTests +{ + [Theory] + [InlineData(10, 0, 90, true)] + [InlineData(180, 0, 90, false)] + [InlineData(0, 0, 90, true)] + [InlineData(90, 0, 90, true)] + [InlineData(350, 340, 10, true)] + [InlineData(20, 340, 10, false)] + [InlineData(-10, 340, 10, true)] + [InlineData(370, 0, 90, true)] + [InlineData(123, 45, 45, true)] + public void IsBetweenDeg_ReturnsExpected(double angle, double a1, double a2, bool expected) + { + Assert.Equal(expected, OpenNest.Math.Angle.IsBetweenDeg(angle, a1, a2)); + } + + [Theory] + [InlineData(45, 90, 0, true)] + [InlineData(45, 0, 90, false)] + public void IsBetweenDeg_Reversed_SwapsEndpoints(double angle, double a1, double a2, bool expected) + { + Assert.Equal(expected, OpenNest.Math.Angle.IsBetweenDeg(angle, a1, a2, reversed: true)); + } + + [Theory] + [InlineData(10, 0, 90, false)] + [InlineData(45, 0, 90, false)] + [InlineData(180, 0, 90, false)] + [InlineData(0, 0, 90, false)] + [InlineData(90, 0, 90, false)] + [InlineData(350, 340, 10, false)] + [InlineData(20, 340, 10, false)] + [InlineData(-10, 340, 10, false)] + [InlineData(370, 0, 90, false)] + [InlineData(123, 45, 45, false)] + [InlineData(45, 90, 0, true)] + [InlineData(45, 0, 90, true)] + public void IsBetweenDeg_OrdinaryCases_MatchesRadians( + double angle, double a1, double a2, bool reversed) + { + var radianResult = OpenNest.Math.Angle.IsBetweenRad( + angle * System.Math.PI / 180, + a1 * System.Math.PI / 180, + a2 * System.Math.PI / 180, + reversed); + + Assert.Equal(radianResult, OpenNest.Math.Angle.IsBetweenDeg(angle, a1, a2, reversed)); + } + + [Theory] + [InlineData(-0.000005, 0, 90, true)] + [InlineData(-0.00002, 0, 90, false)] + [InlineData(90.000005, 0, 90, true)] + [InlineData(90.00002, 0, 90, false)] + [InlineData(123, 45, 45.000005, true)] + [InlineData(123, 45, 45.00002, false)] + [InlineData(123, 45, 405, false)] + public void IsBetweenDeg_UsesDegreeToleranceBeforeNormalizingEndpoints( + double angle, double a1, double a2, bool expected) + { + Assert.Equal(expected, OpenNest.Math.Angle.IsBetweenDeg(angle, a1, a2)); + } + + [Theory] + [InlineData(-0.000005, 0, 1, true)] + [InlineData(-0.00002, 0, 1, false)] + [InlineData(1.000005, 0, 1, true)] + [InlineData(1.00002, 0, 1, false)] + [InlineData(2, 0.5, 0.500005, true)] + [InlineData(2, 0.5, 0.50002, false)] + [InlineData(2, 0.5, 0.5 + 2 * System.Math.PI, false)] + public void IsBetweenRad_UsesRadianToleranceBeforeNormalizingEndpoints( + double angle, double a1, double a2, bool expected) + { + Assert.Equal(expected, OpenNest.Math.Angle.IsBetweenRad(angle, a1, a2)); + } +}