using System;
using System.Collections.Generic;
using OpenNest.Geometry;
namespace OpenNest.Engine.Qwen38FlashNext.Engine;
using Math = System.Math;
///
/// Flat-array polygon with a uniform edge grid, used as the engine's fast outer-shell
/// clearance test. Two closed polygons share positive area only when an edge pair
/// crosses/touches or one polygon's vertex lies strictly inside the other; neither
/// happening certifies the two closed regions (hence any materials inside them) are
/// clear. returns true only in that certified case and false
/// whenever anything touches, so it can only ever skip the exact
/// gate when the exact gate would also find no overlap - the exact gate triangulates
/// both polygons per call and dominates runtime on finely flattened arc geometry.
///
/// A holds the shared geometry;
/// produces a placement in world coordinates in O(1) - translation leaves the grid and
/// all cell indices unchanged, only the predicate coordinates shift.
///
///
internal sealed class FastPoly
{
/// Vertex-on-segment / collinearity tolerance for conservative touches.
private const double TouchEps = 1e-9;
private readonly FastPolyTemplate _template;
/// Translation applied to the shared template geometry.
public readonly double Dx;
public readonly double Dy;
private FastPoly(FastPolyTemplate template, double dx, double dy)
{
_template = template;
Dx = dx;
Dy = dy;
}
public double MinX => _template.MinX + Dx;
public double MinY => _template.MinY + Dy;
public double MaxX => _template.MaxX + Dx;
public double MaxY => _template.MaxY + Dy;
///
/// Builds from a closed (last vertex may repeat the first).
/// Returns null when the polygon has no usable ring - callers treat that as
/// "no information" and fall through to the exact gate.
///
public static FastPoly? From(Polygon polygon)
{
var template = FastPolyTemplate.Build(polygon);
return template == null ? null : new FastPoly(template, 0, 0);
}
public FastPoly Translated(double dx, double dy) => new(_template, Dx + dx, Dy + dy);
private double X(int i) => _template.X[i] + Dx;
private double Y(int i) => _template.Y[i] + Dy;
///
/// True when this and are CERTIFIED clear: their
/// boundaries neither cross nor touch (within ) and neither
/// contains a vertex of the other, so the closed regions share no area. Any touch,
/// crossing, or containment reports false and defers to the exact gate.
///
public static bool Clears(FastPoly a, FastPoly b) => Relate(a, b) == FastRelation.Clear;
///
/// Outer-shell relation between two closed polygons: crossing or containment means
/// the shells share positive area; a boundary touch alone or disjoint shells means
/// they do not. The Overlap verdict is about SHELLS only - callers with holes must
/// still consult the exact gate, because holes can cancel shell overlap.
///
public static FastRelation Relate(FastPoly a, FastPoly b)
{
if (
a.MaxX <= b.MinX
|| b.MaxX <= a.MinX
|| a.MaxY <= b.MinY
|| b.MaxY <= a.MinY
)
return FastRelation.Clear; // disjoint bounding boxes
// One walk per direction reports the strongest edge relation: a transversal
// crossing shares a positive-area wedge (overlap); a mere touch shares zero
// area but may hide a crossing in near-degenerate coordinates (unknown).
var edge = EdgeRelation(a, b);
if (edge < 2)
{
var back = EdgeRelation(b, a);
if (back > edge)
edge = back;
}
if (edge == 2)
return FastRelation.Overlap;
// No transversal crossing. Cases:
// 0 = boundaries fully disjoint: containment (hence positive overlap) is
// decided by one vertex test per direction.
// 1 = point touches only (zero shared area by themselves): positive overlap
// requires a vertex strictly inside the other polygon; a tangency - the
// spacing-exact contact a bottom-left packer lives on - has none.
// 3 = collinear/near-degenerate contact: a shared boundary strip can hide a
// same-side positive overlap with no strict-interior vertex anywhere, so
// it defers to the exact gate.
switch (edge)
{
case 0:
if (ContainsPointStrictly(a, b.X(0), b.Y(0)))
return FastRelation.Overlap;
if (ContainsPointStrictly(b, a.X(0), a.Y(0)))
return FastRelation.Overlap;
return FastRelation.Clear;
case 1:
if (AnyVertexStrictlyInside(b, a) || AnyVertexStrictlyInside(a, b))
return FastRelation.Overlap;
return FastRelation.Clear;
default:
return FastRelation.Unknown;
}
}
///
/// True when any vertex of lies strictly inside
/// , or any edge interior sample point does. The samples
/// close the inscribed-polygon hole: positive shared area with boundaries meeting
/// only at clean points, no strict-interior vertex, and no collinear contact
/// requires an edge to run through the interior - its quarter points catch that.
///
private static bool AnyVertexStrictlyInside(FastPoly poly, FastPoly vertexSource)
{
var n = vertexSource._template.Count;
for (var i = 0; i < n; i++)
{
var vx = vertexSource.X(i);
var vy = vertexSource.Y(i);
if (ContainsPointStrictly(poly, vx, vy))
return true;
var i2 = (i + 1) % n;
var wx = vertexSource.X(i2);
var wy = vertexSource.Y(i2);
if (wx == vx && wy == vy)
continue;
for (var k = 1; k <= 3; k++)
{
var t = k * 0.25;
if (ContainsPointStrictly(poly, vx + (wx - vx) * t, vy + (wy - vy) * t))
return true;
}
}
return false;
}
/// Three-state outcome of .
public enum FastRelation
{
/// Shells certified disjoint: any materials inside them are clear.
Clear,
/// Shells share positive area (crossing or containment).
Overlap,
/// Boundary touch too close to classify: consult the exact gate.
Unknown,
}
///
/// True when any edge of crosses or touches the boundary of
/// . Walks p's grid using each query edge's own bbox cells.
/// p's grid lives in p's LOCAL frame (the template's own coordinates), so the
/// query edge is converted by subtracting p's translation first.
///
private static int EdgeRelation(FastPoly p, FastPoly q)
{
var t = p._template;
var n = t.Count;
var seen = t.Seen;
var head = t.Head;
var nodeEdge = t.NodeEdge;
var nodeNext = t.NodeNext;
var no = q._template.Count;
var strongest = 0;
for (var e = 0; e < no; e++)
{
// Stamp per QUERY edge: a grid edge may need testing against every query
// edge; the dedupe only collapses cells an individual query edge crosses
// more than once.
var stamp = ++t.Stamp;
var i2 = (e + 1) % no;
var p0x = q.X(e) - p.Dx;
var p0y = q.Y(e) - p.Dy;
var p1x = q.X(i2) - p.Dx;
var p1y = q.Y(i2) - p.Dy;
var c0 = ColLow(t, p0x, p1x);
if (c0 > ColHigh(t, p0x, p1x))
continue;
var c1 = ColHigh(t, p0x, p1x);
var r0 = RowLow(t, p0y, p1y);
if (r0 > RowHigh(t, p0y, p1y))
continue;
var r1 = RowHigh(t, p0y, p1y);
for (var r = r0; r <= r1; r++)
for (var c = c0; c <= c1; c++)
for (var nIdx = head[r * t.Cols + c]; nIdx >= 0; nIdx = nodeNext[nIdx])
{
var ea = nodeEdge[nIdx];
if (seen[ea] == stamp)
continue;
seen[ea] = stamp;
var a2 = (ea + 1) % n;
var relation = SegmentRelation(
t.X[ea], t.Y[ea], t.X[a2], t.Y[a2], p0x, p0y, p1x, p1y
);
if (relation == 2)
return 2; // transversal crossing
if (relation > strongest)
strongest = relation;
}
}
return strongest;
}
///
/// Segment-pair relation: 2 = transversal crossing (strict sign flips on both
/// orientations - the regions share a positive-area wedge); 1 = a clean endpoint
/// touch (zero shared area by itself; callers decide via interior-vertex tests);
/// 3 = collinear or near-degenerate contact (a shared boundary segment can hide
/// either a same-side positive overlap or an opposite-side tangency, so it must
/// defer to the exact gate); 0 = disjoint.
///
private static int SegmentRelation(
double ax, double ay, double bx, double by, double cx, double cy, double dx, double dy
)
{
var rx = bx - ax;
var ry = by - ay;
var sx = dx - cx;
var sy = dy - cy;
var d1 = rx * (cy - ay) - ry * (cx - ax);
var d2 = rx * (dy - ay) - ry * (dx - ax);
var d3 = sx * (ay - cy) - sy * (ax - cx);
var d4 = sx * (by - cy) - sy * (bx - cx);
if (((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) && ((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0)))
return 2; // proper crossing
// A near-zero orientation means the configuration is collinear or too close to
// classify; only exact-zero orientations get the clean point-touch verdict.
var scale = Math.Max(
1e-30,
Math.Max(Math.Abs(rx) + Math.Abs(ry), Math.Abs(sx) + Math.Abs(sy))
);
var eps = TouchEps * scale;
var nearDegenerate =
(Math.Abs(d1) <= eps && d1 != 0)
|| (Math.Abs(d2) <= eps && d2 != 0)
|| (Math.Abs(d3) <= eps && d3 != 0)
|| (Math.Abs(d4) <= eps && d4 != 0);
var exactDegenerate = d1 == 0 || d2 == 0 || d3 == 0 || d4 == 0;
var touch =
(d1 == 0 && PointOnSegment(cx, cy, ax, ay, bx, by))
|| (d2 == 0 && PointOnSegment(dx, dy, ax, ay, bx, by))
|| (d3 == 0 && PointOnSegment(ax, ay, cx, cy, dx, dy))
|| (d4 == 0 && PointOnSegment(bx, by, cx, cy, dx, dy));
if (nearDegenerate)
return 3;
if (exactDegenerate)
// Collinear: contact along a segment (or too close to tell) must defer to
// the exact gate; collinear but disjoint edges simply do not touch.
return touch ? 3 : 0;
if (touch)
return 1;
return 0;
}
private static bool PointOnSegment(
double px, double py, double ax, double ay, double bx, double by
) =>
Math.Min(ax, bx) - TouchEps <= px
&& px <= Math.Max(ax, bx) + TouchEps
&& Math.Min(ay, by) - TouchEps <= py
&& py <= Math.Max(ay, by) + TouchEps;
/// Strict ray-cast containment (boundary touches are excluded upstream).
private static bool ContainsPointStrictly(FastPoly poly, double px, double py)
{
var t = poly._template;
var inside = false;
var n = t.Count;
for (var i = 0; i < n; i++)
{
var j = (i + 1) % n;
var yi = poly.Y(i);
var yj = poly.Y(j);
if ((yi > py) != (yj > py))
{
var xAt = poly.X(i) + (py - yi) / (yj - yi) * (poly.X(j) - poly.X(i));
if (px < xAt)
inside = !inside;
}
}
return inside;
}
private static int ColLow(FastPolyTemplate t, double a, double b) =>
Math.Clamp((int)Math.Floor((Math.Min(a, b) - t.MinX) / t.CellSize), 0, t.Cols);
private static int ColHigh(FastPolyTemplate t, double a, double b) =>
Math.Clamp((int)Math.Floor((Math.Max(a, b) - t.MinX) / t.CellSize), -1, t.Cols - 1);
private static int RowLow(FastPolyTemplate t, double a, double b) =>
Math.Clamp((int)Math.Floor((Math.Min(a, b) - t.MinY) / t.CellSize), 0, t.Rows);
private static int RowHigh(FastPolyTemplate t, double a, double b) =>
Math.Clamp((int)Math.Floor((Math.Max(a, b) - t.MinY) / t.CellSize), -1, t.Rows - 1);
}
///
/// Shared, immutable grid geometry for ; the grid is defined
/// relative to the shape's own local coordinates, so translated instances reuse it.
/// Stamp/Seen are mutable single-threaded scratch for the edge-walk dedupe.
///
internal sealed class FastPolyTemplate
{
public readonly double[] X;
public readonly double[] Y;
public readonly int Count;
public readonly double MinX;
public readonly double MinY;
public readonly double MaxX;
public readonly double MaxY;
public readonly double CellSize;
public readonly int Cols;
public readonly int Rows;
public readonly int[] Head;
///
/// Grid nodes as parallel (edge, next) arrays: an edge spanning several cells gets
/// one node PER cell - a single next-per-edge chain would corrupt the other cells'
/// chains and silently drop edges from the walk.
///
public readonly int[] NodeEdge;
public readonly int[] NodeNext;
public readonly int NodeCount;
public int Stamp;
public readonly int[] Seen;
private FastPolyTemplate(
double[] x,
double[] y,
int count,
double minX,
double minY,
double maxX,
double maxY
)
{
X = x;
Y = y;
Count = count;
MinX = minX;
MinY = minY;
MaxX = maxX;
MaxY = maxY;
Seen = new int[count];
var extentX = Math.Max(maxX - minX, 1e-9);
var extentY = Math.Max(maxY - minY, 1e-9);
CellSize = Math.Max(Math.Max(extentX, extentY) / 16.0, 1e-9);
Cols = Math.Clamp((int)Math.Ceiling(extentX / CellSize) + 1, 1, 48);
Rows = Math.Clamp((int)Math.Ceiling(extentY / CellSize) + 1, 1, 48);
Head = new int[Cols * Rows];
Array.Fill(Head, -1);
// Pass 1: count nodes; pass 2: fill (edge, next) node arrays.
var cellsPerEdge = new int[count];
var total = 0;
for (var e = 0; e < count; e++)
{
var i2 = (e + 1) % count;
var c0 = ClampCol(Math.Min(x[e], x[i2]) - minX);
var c1 = ClampCol(Math.Max(x[e], x[i2]) - minX);
var r0 = ClampRow(Math.Min(y[e], y[i2]) - minY);
var r1 = ClampRow(Math.Max(y[e], y[i2]) - minY);
cellsPerEdge[e] = (c1 - c0 + 1) * (r1 - r0 + 1);
total += cellsPerEdge[e];
}
NodeEdge = new int[total];
NodeNext = new int[total];
var node = 0;
for (var e = 0; e < count; e++)
{
var i2 = (e + 1) % count;
var c0 = ClampCol(Math.Min(x[e], x[i2]) - minX);
var c1 = ClampCol(Math.Max(x[e], x[i2]) - minX);
var r0 = ClampRow(Math.Min(y[e], y[i2]) - minY);
var r1 = ClampRow(Math.Max(y[e], y[i2]) - minY);
for (var r = r0; r <= r1; r++)
for (var c = c0; c <= c1; c++)
{
var cell = r * Cols + c;
NodeEdge[node] = e;
NodeNext[node] = Head[cell];
Head[cell] = node;
node++;
}
}
NodeCount = node;
}
private int ClampCol(double dx) =>
Math.Clamp((int)Math.Floor(dx / CellSize), 0, Cols - 1);
private int ClampRow(double dy) =>
Math.Clamp((int)Math.Floor(dy / CellSize), 0, Rows - 1);
public static FastPolyTemplate? Build(Polygon polygon)
{
var vertices = polygon.Vertices;
var n = vertices.Count;
if (n >= 2 && vertices[0].Equals(vertices[n - 1]))
n--;
if (n < 3)
return null;
var xs = new double[n];
var ys = new double[n];
var minX = double.MaxValue;
var minY = double.MaxValue;
var maxX = double.MinValue;
var maxY = double.MinValue;
for (var i = 0; i < n; i++)
{
var vx = vertices[i].X;
var vy = vertices[i].Y;
xs[i] = vx;
ys[i] = vy;
if (vx < minX)
minX = vx;
if (vx > maxX)
maxX = vx;
if (vy < minY)
minY = vy;
if (vy > maxY)
maxY = vy;
}
return new FastPolyTemplate(xs, ys, n, minX, minY, maxX, maxY);
}
}