mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-26 07:31:08 -04:00
fix(expand): distinguish open-path material entry from bounds
This commit is contained in:
1 parent
27f8c9799b
commit
6424512e8c
6 files changed
+365
-8
No files matched your search
@@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// Strict positive-length entry of an open native line path into polygon material.
|
||||
/// Boundary-only contact is not entry; holes subtract material. This deliberately
|
||||
/// does not infer material from a path's bounding box or close the open path.
|
||||
/// Curved contours are flattened by the caller, which must handle chord uncertainty.
|
||||
/// </summary>
|
||||
internal static class OpenPathMaterial
|
||||
{
|
||||
public static bool Enters(Polygon outer, IReadOnlyList<Polygon> holes, IReadOnlyList<Line> path)
|
||||
{
|
||||
if (outer?.Vertices.Count < 4 || path == null)
|
||||
return true; // Unresolved geometry is not a certificate of clear material.
|
||||
|
||||
foreach (var line in path)
|
||||
{
|
||||
var start = line.StartPoint;
|
||||
var end = line.EndPoint;
|
||||
var travel = end - start;
|
||||
var lengthSquared = travel.DotProduct(travel);
|
||||
if (!double.IsFinite(lengthSquared) || lengthSquared <= 0)
|
||||
return true;
|
||||
|
||||
var cuts = new List<double> { 0, 1 };
|
||||
CollectCuts(outer, start, travel, lengthSquared, cuts);
|
||||
if (holes != null)
|
||||
foreach (var hole in holes)
|
||||
CollectCuts(hole, start, travel, lengthSquared, cuts);
|
||||
cuts.Sort();
|
||||
for (var i = 1; i < cuts.Count; i++)
|
||||
{
|
||||
if (cuts[i] <= cuts[i - 1])
|
||||
continue;
|
||||
var midpoint = start + travel * ((cuts[i - 1] + cuts[i]) / 2);
|
||||
if (!StrictlyInside(outer, midpoint))
|
||||
continue;
|
||||
var inHole = false;
|
||||
if (holes != null)
|
||||
foreach (var hole in holes)
|
||||
if (InsideOrOnBoundary(hole, midpoint))
|
||||
{
|
||||
inHole = true;
|
||||
break;
|
||||
}
|
||||
if (!inHole)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void CollectCuts(Polygon ring, Vector start, Vector travel,
|
||||
double lengthSquared, List<double> cuts)
|
||||
{
|
||||
if (ring?.Vertices.Count < 4)
|
||||
{
|
||||
// A degenerate hole cannot be subtracted reliably. The caller's
|
||||
// unresolved-topology guard handles this case before this helper.
|
||||
return;
|
||||
}
|
||||
var vertices = ring.Vertices;
|
||||
for (var i = 1; i < vertices.Count; i++)
|
||||
{
|
||||
var a = vertices[i - 1];
|
||||
var edge = vertices[i] - a;
|
||||
var difference = a - start;
|
||||
var denominator = Cross(travel, edge);
|
||||
if (denominator == 0)
|
||||
{
|
||||
if (Cross(difference, travel) != 0)
|
||||
continue;
|
||||
AddParameter(difference.DotProduct(travel) / lengthSquared, cuts);
|
||||
AddParameter((vertices[i] - start).DotProduct(travel) / lengthSquared, cuts);
|
||||
continue;
|
||||
}
|
||||
var t = Cross(difference, edge) / denominator;
|
||||
var u = Cross(difference, travel) / denominator;
|
||||
if (u >= -1e-12 && u <= 1 + 1e-12)
|
||||
AddParameter(t, cuts);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddParameter(double t, List<double> cuts)
|
||||
{
|
||||
if (double.IsFinite(t) && t >= -1e-12 && t <= 1 + 1e-12)
|
||||
cuts.Add(System.Math.Clamp(t, 0, 1));
|
||||
}
|
||||
|
||||
private static bool StrictlyInside(Polygon ring, Vector point) =>
|
||||
Contains(ring, point, boundaryIsInside: false);
|
||||
|
||||
private static bool InsideOrOnBoundary(Polygon ring, Vector point) =>
|
||||
Contains(ring, point, boundaryIsInside: true);
|
||||
|
||||
private static bool Contains(Polygon ring, Vector point, bool boundaryIsInside)
|
||||
{
|
||||
var vertices = ring.Vertices;
|
||||
var inside = false;
|
||||
for (var i = 1; i < vertices.Count; i++)
|
||||
{
|
||||
var a = vertices[i - 1];
|
||||
var b = vertices[i];
|
||||
var edge = b - a;
|
||||
var relative = point - a;
|
||||
if (Cross(edge, relative) == 0 &&
|
||||
point.X >= System.Math.Min(a.X, b.X) && point.X <= System.Math.Max(a.X, b.X) &&
|
||||
point.Y >= System.Math.Min(a.Y, b.Y) && point.Y <= System.Math.Max(a.Y, b.Y))
|
||||
return boundaryIsInside;
|
||||
if ((a.Y > point.Y) != (b.Y > point.Y) &&
|
||||
point.X < a.X + (b.X - a.X) * (point.Y - a.Y) / (b.Y - a.Y))
|
||||
inside = !inside;
|
||||
}
|
||||
return inside;
|
||||
}
|
||||
|
||||
private static double Cross(Vector a, Vector b) => a.X * b.Y - a.Y * b.X;
|
||||
}
|
||||
Reference in new issue
Block a user