using System.Collections.Generic; using System.Linq; using OpenNest.Geometry; using OpenNest.Math; namespace OpenNest { public readonly struct FillScore : System.IComparable { /// /// Minimum short-side dimension for a remnant to be considered usable. /// public const double MinRemnantDimension = 12.0; public int Count { get; } /// /// Area of the largest remnant whose short side >= MinRemnantDimension. /// Zero if no usable remnant exists. /// public double UsableRemnantArea { get; } /// /// Total part area / bounding box area of all placed parts. /// public double Density { get; } public FillScore(int count, double usableRemnantArea, double density) { Count = count; UsableRemnantArea = usableRemnantArea; Density = density; } /// /// Computes a fill score from placed parts and the work area they were placed in. /// public static FillScore Compute(List parts, Box workArea) { if (parts == null || parts.Count == 0) return default; var totalPartArea = 0.0; foreach (var part in parts) totalPartArea += part.BaseDrawing.Area; var bbox = ((IEnumerable)parts).GetBoundingBox(); var bboxArea = bbox.Area(); var density = bboxArea > 0 ? totalPartArea / bboxArea : 0; var usableRemnantArea = ComputeUsableRemnantArea(parts, workArea); return new FillScore(parts.Count, usableRemnantArea, density); } /// /// Finds the largest usable remnant (short side >= MinRemnantDimension) /// by checking right and top edge strips between placed parts and the work area boundary. /// private static double ComputeUsableRemnantArea(List parts, Box workArea) { var maxRight = double.MinValue; var maxTop = double.MinValue; foreach (var part in parts) { var bb = part.BoundingBox; if (bb.Right > maxRight) maxRight = bb.Right; if (bb.Top > maxTop) maxTop = bb.Top; } var largest = 0.0; // Right strip if (maxRight < workArea.Right) { var width = workArea.Right - maxRight; var height = workArea.Length; if (System.Math.Min(width, height) >= MinRemnantDimension) largest = System.Math.Max(largest, width * height); } // Top strip if (maxTop < workArea.Top) { var width = workArea.Width; var height = workArea.Top - maxTop; if (System.Math.Min(width, height) >= MinRemnantDimension) largest = System.Math.Max(largest, width * height); } return largest; } /// /// Lexicographic comparison: count, then usable remnant area, then density. /// public int CompareTo(FillScore other) { var c = Count.CompareTo(other.Count); if (c != 0) return c; c = UsableRemnantArea.CompareTo(other.UsableRemnantArea); if (c != 0) return c; return Density.CompareTo(other.Density); } public static bool operator >(FillScore a, FillScore b) => a.CompareTo(b) > 0; public static bool operator <(FillScore a, FillScore b) => a.CompareTo(b) < 0; public static bool operator >=(FillScore a, FillScore b) => a.CompareTo(b) >= 0; public static bool operator <=(FillScore a, FillScore b) => a.CompareTo(b) <= 0; } }