using OpenNest.Geometry;
namespace OpenNest.Desktop.Interaction;
/// How a dragged selection window picks parts and cut-offs.
public enum WindowSelectionMode
{
/// Anything the window touches: dragged from right to left.
Intersect,
/// Only what lies wholly inside the window: dragged from left to right.
Contains,
}
///
/// A selection window between the point a drag started and where it is now, in plate (world)
/// coordinates. As in the WinForms view, dragging to the right selects what the window contains
/// and dragging to the left (or straight up or down) selects what it touches.
///
public readonly record struct SelectionWindow(Vector First, Vector Second)
{
public WindowSelectionMode Mode =>
First.X < Second.X ? WindowSelectionMode.Contains : WindowSelectionMode.Intersect;
public double Left => System.Math.Min(First.X, Second.X);
public double Right => System.Math.Max(First.X, Second.X);
public double Bottom => System.Math.Min(First.Y, Second.Y);
public double Top => System.Math.Max(First.Y, Second.Y);
/// The window as a box (a fresh copy each time).
public Box Box => new(Left, Bottom, Right - Left, Top - Bottom);
/// Whether lies in the window, edges included.
public bool Contains(Vector point) =>
point.X >= Left && point.X <= Right && point.Y >= Bottom && point.Y <= Top;
/// Whether lies in the window, edges included.
public bool Contains(Box box) =>
box.Left >= Left && box.Right <= Right && box.Bottom >= Bottom && box.Top <= Top;
/// Whether and the window overlap, touching edges included.
public bool Overlaps(Box box) =>
box.Left <= Right && box.Right >= Left && box.Bottom <= Top && box.Top >= Bottom;
/// Whether the segment from to enters the window.
public bool Intersects(Vector start, Vector end)
{
if (Contains(start) || Contains(end))
return true;
// Clip the segment against all four edges (Liang-Barsky): a segment can cross the window
// without ending inside it.
var min = 0.0;
var max = 1.0;
var dx = end.X - start.X;
var dy = end.Y - start.Y;
return Clip(-dx, start.X - Left, ref min, ref max)
&& Clip(dx, Right - start.X, ref min, ref max)
&& Clip(-dy, start.Y - Bottom, ref min, ref max)
&& Clip(dy, Top - start.Y, ref min, ref max);
}
/// The window in the frame whose origin is at , such as a placed part's program frame.
public SelectionWindow RelativeTo(Vector origin) => new(First - origin, Second - origin);
private static bool Clip(double p, double q, ref double min, ref double max)
{
if (p == 0)
return q >= 0;
var t = q / p;
if (p < 0)
{
if (t > max)
return false;
min = System.Math.Max(min, t);
}
else
{
if (t < min)
return false;
max = System.Math.Min(max, t);
}
return min <= max;
}
}