using System.Collections.Generic; namespace OpenNest.Geometry; /// /// Tolerance-free test of material against an axis-aligned keep-out rectangle, for /// rectangles too small for the tolerance-based overlap machinery (validation offsets, /// Clipper grids, triangulation and vector equality work at 1e-3 to 1e-5 scale and collapse /// such a rectangle, PM 6859db53). Positive-area overlap only: flush contact is clear, as /// everywhere else. /// public static class KeepOutOverlap { /// The scale below which tolerance-based overlap tests are not trusted for a /// keep-out rectangle (the validation outline chord tolerance). public const double ToleranceFreeBelow = 0.001; /// A keep-out narrower than on either axis. public static bool NeedsToleranceFreeTest(Box keepOut) => keepOut.Length < ToleranceFreeBelow || keepOut.Width < ToleranceFreeBelow; /// /// True when the material (perimeter minus holes) shares positive area with the open /// rectangle: some boundary edge runs through its interior, or its interior lies wholly /// inside the material (tested at its centre). /// public static bool MaterialEnters(Polygon perimeter, IReadOnlyList holes, Box box) { if (RingEnters(perimeter, box)) return true; if (holes != null) foreach (var hole in holes) if (RingEnters(hole, box)) return true; var centre = new Vector((box.Left + box.Right) / 2, (box.Bottom + box.Top) / 2); if (!Contains(perimeter, centre)) return false; if (holes != null) foreach (var hole in holes) if (Contains(hole, centre)) return false; return true; } private static bool RingEnters(Polygon ring, Box box) { var vertices = ring.Vertices; var count = vertices.Count; for (var i = 0; i < count; i++) { var next = vertices[(i + 1) % count]; if (SegmentEntersOpenBox(vertices[i], next, box)) return true; } return false; } /// Liang-Barsky clip against the open rectangle; a positive-length part of the /// segment must lie strictly inside it. private static bool SegmentEntersOpenBox(Vector p, Vector q, Box box) { var dx = q.X - p.X; var dy = q.Y - p.Y; var t0 = 0.0; var t1 = 1.0; return Clip(-dx, p.X - box.Left, ref t0, ref t1) && Clip(dx, box.Right - p.X, ref t0, ref t1) && Clip(-dy, p.Y - box.Bottom, ref t0, ref t1) && Clip(dy, box.Top - p.Y, ref t0, ref t1) && t0 < t1; } /// Restricts [t0, t1] to the parameters where denominator * t < numerator. private static bool Clip(double denominator, double numerator, ref double t0, ref double t1) { if (denominator == 0) return numerator > 0; var r = numerator / denominator; if (denominator < 0) { if (r >= t1) return false; if (r > t0) t0 = r; } else { if (r <= t0) return false; if (r < t1) t1 = r; } return true; } /// Even-odd ray cast; exact for a point that is not on the ring (callers only /// ask after ruling out any edge through the rectangle containing the point). private static bool Contains(Polygon ring, Vector point) { var inside = false; var vertices = ring.Vertices; var count = vertices.Count; for (int i = 0, j = count - 1; i < count; j = i++) { var a = vertices[i]; var b = vertices[j]; if ((a.Y > point.Y) != (b.Y > point.Y) && point.X < (b.X - a.X) * (point.Y - a.Y) / (b.Y - a.Y) + a.X) inside = !inside; } return inside; } }