using System; using System.Collections.Generic; using OpenNest.Geometry; namespace OpenNest { /// /// Caches computed No-Fit Polygons keyed by (DrawingA.Id, RotationA, DrawingB.Id, RotationB). /// NFPs are computed on first access and stored for reuse during optimization. /// Thread-safe for concurrent reads after pre-computation. /// public class NfpCache { private readonly Dictionary cache = new Dictionary(); private readonly Dictionary> polygonCache = new Dictionary>(); /// /// Registers a pre-computed polygon for a drawing at a specific rotation. /// Call this during initialization before computing NFPs. /// public void RegisterPolygon(int drawingId, double rotation, Polygon polygon) { if (!polygonCache.TryGetValue(drawingId, out var rotations)) { rotations = new Dictionary(); polygonCache[drawingId] = rotations; } rotations[rotation] = polygon; } /// /// Gets the polygon for a drawing at a specific rotation. /// public Polygon GetPolygon(int drawingId, double rotation) { if (polygonCache.TryGetValue(drawingId, out var rotations)) { if (rotations.TryGetValue(rotation, out var polygon)) return polygon; } return null; } /// /// Gets or computes the NFP between two drawings at their respective rotations. /// The NFP is computed from the stationary polygon (drawingA at rotationA) and /// the orbiting polygon (drawingB at rotationB). /// public Polygon Get(int drawingIdA, double rotationA, int drawingIdB, double rotationB) { var key = new NfpKey(drawingIdA, rotationA, drawingIdB, rotationB); if (cache.TryGetValue(key, out var nfp)) return nfp; var polyA = GetPolygon(drawingIdA, rotationA); var polyB = GetPolygon(drawingIdB, rotationB); if (polyA == null || polyB == null) return new Polygon(); nfp = NoFitPolygon.Compute(polyA, polyB); cache[key] = nfp; return nfp; } /// /// Pre-computes all NFPs for every combination of registered polygons. /// Call after all polygons are registered to front-load computation. /// public void PreComputeAll() { var entries = new List<(int drawingId, double rotation)>(); foreach (var kvp in polygonCache) { foreach (var rot in kvp.Value) entries.Add((kvp.Key, rot.Key)); } for (var i = 0; i < entries.Count; i++) { for (var j = 0; j < entries.Count; j++) { Get(entries[i].drawingId, entries[i].rotation, entries[j].drawingId, entries[j].rotation); } } } /// /// Number of cached NFP entries. /// public int Count => cache.Count; private readonly struct NfpKey : IEquatable { public readonly int DrawingIdA; public readonly double RotationA; public readonly int DrawingIdB; public readonly double RotationB; public NfpKey(int drawingIdA, double rotationA, int drawingIdB, double rotationB) { DrawingIdA = drawingIdA; RotationA = rotationA; DrawingIdB = drawingIdB; RotationB = rotationB; } public bool Equals(NfpKey other) { return DrawingIdA == other.DrawingIdA && RotationA == other.RotationA && DrawingIdB == other.DrawingIdB && RotationB == other.RotationB; } public override bool Equals(object obj) => obj is NfpKey key && Equals(key); public override int GetHashCode() { unchecked { var hash = 17; hash = hash * 31 + DrawingIdA; hash = hash * 31 + RotationA.GetHashCode(); hash = hash * 31 + DrawingIdB; hash = hash * 31 + RotationB.GetHashCode(); return hash; } } } } }