using System.Collections.Generic;
using OpenNest.Geometry;
namespace OpenNest
{
///
/// Overlap-only form of for one pass over a fixed set of parts.
/// Verdicts match Intersects(other, out _). Each distinct
/// (by reference) is prepared once, each part's world polygon is built once, and crossing
/// points are not computed. Tiled copies from share one
/// Program, so a fill grid prepares its pattern's programs only once.
/// Parts must not move, rotate or change Program while an instance is in use. Instances are
/// not thread-safe: create one per check.
///
public sealed class PartOverlapChecker
{
private readonly Dictionary programs = new(
ReferenceEqualityComparer.Instance
);
private readonly Dictionary worldPolygons = new(
ReferenceEqualityComparer.Instance
);
///
/// Same verdict as part1.Intersects(part2, out _). Preparation stages run in the
/// same order, with the same early exits, the first time each Program is needed.
///
public bool Overlaps(Part part1, Part part2)
{
PerfCounters.CountPartIntersects();
var prepared1 = Prepare(part1.Program);
var prepared2 = Prepare(part2.Program);
if (prepared1.Material.Count == 0 || prepared2.Material.Count == 0)
return false;
var perimeter1 = prepared1.GetPerimeter();
var perimeter2 = prepared2.GetPerimeter();
if (perimeter1 == null || perimeter2 == null)
return false;
var polygon1 = WorldPolygon(part1, prepared1);
var polygon2 = WorldPolygon(part2, prepared2);
if (polygon1 == null || polygon2 == null)
return false;
return Collision.HasOverlap(polygon1, polygon2);
}
private PreparedProgram Prepare(CNC.Program program)
{
if (!programs.TryGetValue(program, out var prepared))
{
prepared = new PreparedProgram(Part.MaterialEntities(program));
programs.Add(program, prepared);
}
return prepared;
}
private Polygon WorldPolygon(Part part, PreparedProgram prepared)
{
if (worldPolygons.TryGetValue(part, out var polygon))
return polygon;
var local = prepared.GetLocalPolygon();
if (local != null)
{
// Clone copies the vertices but not the bounds. Recomputing the bounds from the
// same vertices and then offsetting reproduces Part.Intersects' polygon bit for bit.
polygon = (Polygon)local.Clone();
polygon.UpdateBounds();
polygon.Offset(part.Location);
}
worldPolygons.Add(part, polygon);
return polygon;
}
private sealed class PreparedProgram
{
private bool perimeterReady;
private Shape perimeter;
private bool polygonReady;
private Polygon localPolygon;
public PreparedProgram(List material) => Material = material;
public List Material { get; }
public Shape GetPerimeter()
{
if (!perimeterReady)
{
perimeter = new ShapeProfile(Material).Perimeter;
perimeterReady = true;
}
return perimeter;
}
/// Only called after returned non-null.
public Polygon GetLocalPolygon()
{
if (!polygonReady)
{
localPolygon = Part.BuildOverlapPolygon(perimeter);
polygonReady = true;
}
return localPolygon;
}
}
}
}