Files
OpenNest/OpenNest.Tests/Splitting/EntitySplitTests.cs
AJ Isaacs df18b72881 feat: add SplitLineIntersect helper for entity-splitline intersection
Add ToLine() to SplitLine and create SplitLineIntersect static class with
FindIntersection, CrossesSplitLine, and SideOf methods for testing entity
intersections against split lines. These helpers support the upcoming
Clipper2-free DrawingSplitter rewrite.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 18:04:21 -04:00

207 lines
6.4 KiB
C#

using OpenNest.Geometry;
using OpenNest.Math;
namespace OpenNest.Tests.Splitting;
public class EntitySplitTests
{
// --- SplitLine.ToLine ---
[Fact]
public void ToLine_Vertical_ReturnsVerticalLine()
{
var sl = new SplitLine(50.0, CutOffAxis.Vertical);
var line = sl.ToLine(0, 100);
Assert.Equal(50.0, line.StartPoint.X, 5);
Assert.Equal(0.0, line.StartPoint.Y, 5);
Assert.Equal(50.0, line.EndPoint.X, 5);
Assert.Equal(100.0, line.EndPoint.Y, 5);
}
[Fact]
public void ToLine_Horizontal_ReturnsHorizontalLine()
{
var sl = new SplitLine(30.0, CutOffAxis.Horizontal);
var line = sl.ToLine(10, 90);
Assert.Equal(10.0, line.StartPoint.X, 5);
Assert.Equal(30.0, line.StartPoint.Y, 5);
Assert.Equal(90.0, line.EndPoint.X, 5);
Assert.Equal(30.0, line.EndPoint.Y, 5);
}
// --- FindIntersection: Line crossing vertical split ---
[Fact]
public void FindIntersection_LineCrossesVerticalSplit_ReturnsPoint()
{
// Diagonal line from (0,0) to (100,100), vertical split at x=50
var line = new Line(0, 0, 100, 100);
var sl = new SplitLine(50.0, CutOffAxis.Vertical);
var result = SplitLineIntersect.FindIntersection(line, sl);
Assert.NotNull(result);
Assert.Equal(50.0, result.Value.X, 5);
Assert.Equal(50.0, result.Value.Y, 5);
}
// --- FindIntersection: Line NOT crossing ---
[Fact]
public void FindIntersection_LineDoesNotCross_ReturnsNull()
{
// Line entirely to the left of split at x=50
var line = new Line(0, 0, 40, 40);
var sl = new SplitLine(50.0, CutOffAxis.Vertical);
var result = SplitLineIntersect.FindIntersection(line, sl);
Assert.Null(result);
}
// --- FindIntersection: Line parallel to split ---
[Fact]
public void FindIntersection_LineParallelToSplit_ReturnsNull()
{
// Vertical line at x=50 — parallel to vertical split at x=50
var line = new Line(50, 0, 50, 100);
var sl = new SplitLine(50.0, CutOffAxis.Vertical);
var result = SplitLineIntersect.FindIntersection(line, sl);
Assert.Null(result);
}
// --- FindIntersection: Arc crossing vertical split ---
[Fact]
public void FindIntersection_ArcCrossesVerticalSplit_ReturnsPoint()
{
// Arc centered at (60,50), radius 20, from PI to 0 (CCW).
// CCW from PI wraps through 3PI/2 (bottom) then 0 (right).
// At x=50: (50-60)^2 + (y-50)^2 = 400 => (y-50)^2 = 300
// y = 50 - sqrt(300) ≈ 32.68 (bottom intersection, on the arc)
// y = 50 + sqrt(300) ≈ 67.32 (top intersection, also on the arc)
var arc = new Arc(60, 50, 20, System.Math.PI, 0, false);
var sl = new SplitLine(50.0, CutOffAxis.Vertical);
var result = SplitLineIntersect.FindIntersection(arc, sl);
Assert.NotNull(result);
Assert.Equal(50.0, result.Value.X, 1);
// The first intersection found by the circle-line algorithm; either ~32.68 or ~67.32
var y = result.Value.Y;
var expectedLow = 50.0 - System.Math.Sqrt(300);
var expectedHigh = 50.0 + System.Math.Sqrt(300);
Assert.True(
System.Math.Abs(y - expectedLow) < 0.1 || System.Math.Abs(y - expectedHigh) < 0.1,
$"Expected Y near {expectedLow:F2} or {expectedHigh:F2}, got {y:F2}");
}
// --- CrossesSplitLine ---
[Fact]
public void CrossesSplitLine_LineStraddles_ReturnsTrue()
{
var line = new Line(40, 0, 60, 100);
var sl = new SplitLine(50.0, CutOffAxis.Vertical);
Assert.True(SplitLineIntersect.CrossesSplitLine(line, sl));
}
[Fact]
public void CrossesSplitLine_LineEntirelyOnOneSide_ReturnsFalse()
{
var line = new Line(10, 0, 40, 100);
var sl = new SplitLine(50.0, CutOffAxis.Vertical);
Assert.False(SplitLineIntersect.CrossesSplitLine(line, sl));
}
[Fact]
public void CrossesSplitLine_HorizontalSplit_Works()
{
var line = new Line(0, 10, 100, 60);
var sl = new SplitLine(30.0, CutOffAxis.Horizontal);
Assert.True(SplitLineIntersect.CrossesSplitLine(line, sl));
}
[Fact]
public void CrossesSplitLine_LineTouchingButNotStraddling_ReturnsFalse()
{
// Line endpoint exactly at the split line — bbox right == 50, left < 50
// But right must be > pos (strictly), so touching exactly returns false
var line = new Line(10, 0, 50, 100);
var sl = new SplitLine(50.0, CutOffAxis.Vertical);
Assert.False(SplitLineIntersect.CrossesSplitLine(line, sl));
}
// --- SideOf ---
[Fact]
public void SideOf_PointLeftOfVerticalSplit_ReturnsNegative()
{
var pt = new Vector(30, 50);
var sl = new SplitLine(50.0, CutOffAxis.Vertical);
Assert.Equal(-1, SplitLineIntersect.SideOf(pt, sl));
}
[Fact]
public void SideOf_PointRightOfVerticalSplit_ReturnsPositive()
{
var pt = new Vector(70, 50);
var sl = new SplitLine(50.0, CutOffAxis.Vertical);
Assert.Equal(1, SplitLineIntersect.SideOf(pt, sl));
}
[Fact]
public void SideOf_PointOnVerticalSplit_ReturnsZero()
{
var pt = new Vector(50, 50);
var sl = new SplitLine(50.0, CutOffAxis.Vertical);
Assert.Equal(0, SplitLineIntersect.SideOf(pt, sl));
}
[Fact]
public void SideOf_PointBelowHorizontalSplit_ReturnsNegative()
{
var pt = new Vector(50, 10);
var sl = new SplitLine(30.0, CutOffAxis.Horizontal);
Assert.Equal(-1, SplitLineIntersect.SideOf(pt, sl));
}
[Fact]
public void SideOf_PointAboveHorizontalSplit_ReturnsPositive()
{
var pt = new Vector(50, 60);
var sl = new SplitLine(30.0, CutOffAxis.Horizontal);
Assert.Equal(1, SplitLineIntersect.SideOf(pt, sl));
}
// --- FindIntersection: horizontal split ---
[Fact]
public void FindIntersection_LineCrossesHorizontalSplit_ReturnsPoint()
{
// Diagonal line from (0,0) to (100,100), horizontal split at y=50
var line = new Line(0, 0, 100, 100);
var sl = new SplitLine(50.0, CutOffAxis.Horizontal);
var result = SplitLineIntersect.FindIntersection(line, sl);
Assert.NotNull(result);
Assert.Equal(50.0, result.Value.X, 5);
Assert.Equal(50.0, result.Value.Y, 5);
}
}