using System; using System.Collections.Generic; using OpenNest.Geometry; namespace OpenNest.Support { /// /// A bed of straight slats, as on a plasma table: each slat a rectangle /// wide running the full length of the table along , one every /// across it, the first centered from the plate origin /// into the sheet. A sheet in another quadrant sees the same slats mirrored. A part rests on /// the region where its material crosses a slat; the corners of that region (where the /// outline crosses a slat's edges, and outline vertices on the slat) are its contacts. /// Lengths are in the nest's units. /// public sealed class SupportSlats : SupportBed { public SupportSlats(SlatAxis axis, double pitch, double thickness, double offset = 0, double registrationTolerance = 0, double stabilityMargin = 0) : base(registrationTolerance, stabilityMargin) { if (axis is not (SlatAxis.X or SlatAxis.Y)) throw new ArgumentOutOfRangeException(nameof(axis), "Slats run along X or Y."); if (!double.IsFinite(pitch) || pitch <= 0) throw new ArgumentOutOfRangeException(nameof(pitch), "Slat pitch must be finite and positive."); if (!double.IsFinite(thickness) || thickness < 0) throw new ArgumentOutOfRangeException(nameof(thickness), "Slat thickness must be finite and zero or more."); if (!double.IsFinite(offset)) throw new ArgumentOutOfRangeException(nameof(offset), "Slat offset must be finite."); Axis = axis; Pitch = pitch; Thickness = thickness; Offset = offset; } public SlatAxis Axis { get; } /// The distance between neighboring slats' center lines. public double Pitch { get; } /// How wide each slat's top edge is; zero treats a slat as a line. public double Thickness { get; } /// The first slat's center line, measured from the plate origin into the sheet. public double Offset { get; } public override IReadOnlyList Contacts(SupportMaterial material, int quadrant, Vector shift, bool extremesOnly = false) { ArgumentNullException.ThrowIfNull(material); var contacts = new List(); if (!material.IsComplete) return contacts; // Work across the slats: Y for slats along X, X for slats along Y. var alongX = Axis == SlatAxis.X; var baseline = alongX ? SignY(quadrant) * Offset + shift.Y : SignX(quadrant) * Offset + shift.X; var bounds = material.Bounds; var low = alongX ? bounds.Bottom : bounds.Left; var high = alongX ? bounds.Top : bounds.Right; var half = Thickness / 2; var first = (long)System.Math.Ceiling((low - half - baseline) / Pitch); var last = (long)System.Math.Floor((high + half - baseline) / Pitch); if (first > last) return contacts; // Where the outline crosses each slat's edges. for (var slat = first; slat <= last; slat++) { var center = baseline + slat * Pitch; AddEdge(material, alongX, center - half, contacts); if (half > 0) AddEdge(material, alongX, center + half, contacts); } // Outline and hole vertices that sit on a slat: every vertex on one slat's top. AddVertices(material.Outline, alongX, baseline, half, contacts); foreach (var hole in material.Holes) AddVertices(hole, alongX, baseline, half, contacts); return contacts; } /// /// The slats within (world coordinates) on a sheet in /// , each cut off to the region, for display; empty when there /// would be more than . /// public IReadOnlyList SlatsIn(Box region, int quadrant, int limit = 5000) { ArgumentNullException.ThrowIfNull(region); var alongX = Axis == SlatAxis.X; var baseline = alongX ? SignY(quadrant) * Offset : SignX(quadrant) * Offset; var low = alongX ? region.Bottom : region.Left; var high = alongX ? region.Top : region.Right; var half = Thickness / 2; var first = System.Math.Ceiling((low - half - baseline) / Pitch); var last = System.Math.Floor((high + half - baseline) / Pitch); var count = last - first + 1; if (!double.IsFinite(count) || count <= 0 || count > limit) return Array.Empty(); var slats = new List((int)count); for (var slat = first; slat <= last; slat++) { var center = baseline + slat * Pitch; var start = System.Math.Max(low, center - half); var end = System.Math.Min(high, center + half); slats.Add(alongX ? new Box(region.Left, start, region.Length, end - start) : new Box(start, region.Bottom, end - start, region.Width)); } return slats; } public override SupportBed Scaled(double factor) => new SupportSlats(Axis, Pitch * factor, Thickness * factor, Offset * factor, RegistrationTolerance * factor, StabilityMargin * factor); public override bool Equals(SupportBed other) => other is SupportSlats slats && slats.Axis == Axis && slats.Pitch == Pitch && slats.Thickness == Thickness && slats.Offset == Offset && slats.RegistrationTolerance == RegistrationTolerance && slats.StabilityMargin == StabilityMargin; public override int GetHashCode() => HashCode.Combine(Axis, Pitch, Thickness, Offset, RegistrationTolerance, StabilityMargin); private static void AddEdge(SupportMaterial material, bool alongX, double across, List contacts) { var spans = alongX ? material.SpansAtY(across) : material.SpansAtX(across); foreach (var (start, end) in spans) { contacts.Add(alongX ? new Vector(start, across) : new Vector(across, start)); contacts.Add(alongX ? new Vector(end, across) : new Vector(across, end)); } } private void AddVertices(IReadOnlyList ring, bool alongX, double baseline, double half, List contacts) { foreach (var vertex in ring) { var across = alongX ? vertex.Y : vertex.X; // The nearest slat center; the vertex is on that slat when within half its thickness. var center = baseline + System.Math.Round((across - baseline) / Pitch) * Pitch; if (System.Math.Abs(across - center) <= half) contacts.Add(vertex); } } } }