Move geometry primitives to OpenNest.Geometry namespace
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
77
OpenNest.Core/Geometry/BoundingBox.cs
Normal file
77
OpenNest.Core/Geometry/BoundingBox.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public static class BoundingBox
|
||||
{
|
||||
public static Box GetBoundingBox(this IList<Box> boxes)
|
||||
{
|
||||
if (boxes.Count == 0)
|
||||
return Box.Empty;
|
||||
|
||||
double minX = boxes[0].X;
|
||||
double minY = boxes[0].Y;
|
||||
double maxX = boxes[0].X + boxes[0].Width;
|
||||
double maxY = boxes[0].Y + boxes[0].Height;
|
||||
|
||||
foreach (var box in boxes)
|
||||
{
|
||||
if (box.Left < minX) minX = box.Left;
|
||||
if (box.Right > maxX) maxX = box.Right;
|
||||
if (box.Bottom < minY) minY = box.Bottom;
|
||||
if (box.Top > maxY) maxY = box.Top;
|
||||
}
|
||||
|
||||
return new Box(minX, minY, maxX - minX, maxY - minY);
|
||||
}
|
||||
|
||||
public static Box GetBoundingBox(this IList<Vector> pts)
|
||||
{
|
||||
if (pts.Count == 0)
|
||||
return Box.Empty;
|
||||
|
||||
var first = pts[0];
|
||||
var minX = first.X;
|
||||
var maxX = first.X;
|
||||
var minY = first.Y;
|
||||
var maxY = first.Y;
|
||||
|
||||
for (int i = 1; i < pts.Count; ++i)
|
||||
{
|
||||
var vertex = pts[i];
|
||||
|
||||
if (vertex.X < minX) minX = vertex.X;
|
||||
else if (vertex.X > maxX) maxX = vertex.X;
|
||||
|
||||
if (vertex.Y < minY) minY = vertex.Y;
|
||||
else if (vertex.Y > maxY) maxY = vertex.Y;
|
||||
}
|
||||
|
||||
return new Box(minX, minY, maxX - minX, maxY - minY);
|
||||
}
|
||||
|
||||
public static Box GetBoundingBox(this IEnumerable<IBoundable> items)
|
||||
{
|
||||
var first = items.FirstOrDefault();
|
||||
|
||||
if (first == null)
|
||||
return Box.Empty;
|
||||
|
||||
double left = first.Left;
|
||||
double bottom = first.Bottom;
|
||||
double right = first.Right;
|
||||
double top = first.Top;
|
||||
|
||||
foreach (var box in items)
|
||||
{
|
||||
if (box.Left < left) left = box.Left;
|
||||
if (box.Right > right) right = box.Right;
|
||||
if (box.Bottom < bottom) bottom = box.Bottom;
|
||||
if (box.Top > top) top = box.Top;
|
||||
}
|
||||
|
||||
return new Box(left, bottom, right - left, top - bottom);
|
||||
}
|
||||
}
|
||||
}
|
||||
208
OpenNest.Core/Geometry/Box.cs
Normal file
208
OpenNest.Core/Geometry/Box.cs
Normal file
@@ -0,0 +1,208 @@
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public class Box
|
||||
{
|
||||
public static readonly Box Empty = new Box();
|
||||
|
||||
public Box()
|
||||
: this(0, 0, 0, 0)
|
||||
{
|
||||
}
|
||||
|
||||
public Box(double x, double y, double w, double h)
|
||||
{
|
||||
Location = new Vector(x, y);
|
||||
Width = w;
|
||||
Height = h;
|
||||
}
|
||||
|
||||
public Vector Location;
|
||||
|
||||
public Vector Center
|
||||
{
|
||||
get { return new Vector(X + Width * 0.5, Y + Height * 0.5); }
|
||||
}
|
||||
|
||||
public Size Size;
|
||||
|
||||
public double X
|
||||
{
|
||||
get { return Location.X; }
|
||||
set { Location.X = value; }
|
||||
}
|
||||
|
||||
public double Y
|
||||
{
|
||||
get { return Location.Y; }
|
||||
set { Location.Y = value; }
|
||||
}
|
||||
|
||||
public double Width
|
||||
{
|
||||
get { return Size.Width; }
|
||||
set { Size.Width = value; }
|
||||
}
|
||||
|
||||
public double Height
|
||||
{
|
||||
get { return Size.Height; }
|
||||
set { Size.Height = value; }
|
||||
}
|
||||
|
||||
public void MoveTo(double x, double y)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
}
|
||||
|
||||
public void MoveTo(Vector pt)
|
||||
{
|
||||
X = pt.X;
|
||||
Y = pt.Y;
|
||||
}
|
||||
|
||||
public void Offset(double x, double y)
|
||||
{
|
||||
X += x;
|
||||
Y += y;
|
||||
}
|
||||
|
||||
public void Offset(Vector voffset)
|
||||
{
|
||||
Location += voffset;
|
||||
}
|
||||
|
||||
public double Left
|
||||
{
|
||||
get { return X; }
|
||||
}
|
||||
|
||||
public double Right
|
||||
{
|
||||
get { return X + Width; }
|
||||
}
|
||||
|
||||
public double Top
|
||||
{
|
||||
get { return Y + Height; }
|
||||
}
|
||||
|
||||
public double Bottom
|
||||
{
|
||||
get { return Y; }
|
||||
}
|
||||
|
||||
public double Area()
|
||||
{
|
||||
return Width * Height;
|
||||
}
|
||||
|
||||
public double Perimeter()
|
||||
{
|
||||
return Width * 2 + Height * 2;
|
||||
}
|
||||
|
||||
public bool Intersects(Box box)
|
||||
{
|
||||
if (Left >= box.Right) return false;
|
||||
if (Right <= box.Left) return false;
|
||||
if (Top <= box.Bottom) return false;
|
||||
if (Bottom >= box.Top) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Intersects(Box box, out Box intersectingBox)
|
||||
{
|
||||
if (!Intersects(box))
|
||||
{
|
||||
intersectingBox = Box.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
var left = Left < box.Left ? box.Left : Left;
|
||||
var right = Right < box.Right ? Right : box.Right;
|
||||
|
||||
var bottom = Bottom < box.Bottom ? box.Bottom : Bottom;
|
||||
var top = Top < box.Top ? Top : box.Top;
|
||||
|
||||
intersectingBox = new Box(left, bottom, right - left, top - bottom);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Contains(Box box)
|
||||
{
|
||||
if (box.Top > Top) return false;
|
||||
if (box.Left < Left) return false;
|
||||
if (box.Right > Right) return false;
|
||||
if (box.Bottom < Bottom) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Contains(Vector pt)
|
||||
{
|
||||
return pt.X >= Left - Tolerance.Epsilon && pt.X <= Right + Tolerance.Epsilon
|
||||
&& pt.Y >= Bottom - Tolerance.Epsilon && pt.Y <= Top + Tolerance.Epsilon;
|
||||
}
|
||||
|
||||
public bool IsHorizontalTo(Box box)
|
||||
{
|
||||
return Bottom > box.Top || Top < box.Bottom;
|
||||
}
|
||||
|
||||
public bool IsHorizontalTo(Box box, out RelativePosition pos)
|
||||
{
|
||||
if (Bottom >= box.Top || Top <= box.Bottom)
|
||||
{
|
||||
pos = RelativePosition.None;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Left >= box.Right)
|
||||
pos = RelativePosition.Right;
|
||||
else if (Right <= box.Left)
|
||||
pos = RelativePosition.Left;
|
||||
else
|
||||
pos = RelativePosition.Intersecting;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsVerticalTo(Box box)
|
||||
{
|
||||
return Left > box.Right || Right < box.Left;
|
||||
}
|
||||
|
||||
public bool IsVerticalTo(Box box, out RelativePosition pos)
|
||||
{
|
||||
if (Left >= box.Right || Right <= box.Left)
|
||||
{
|
||||
pos = RelativePosition.None;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Bottom >= box.Top)
|
||||
pos = RelativePosition.Top;
|
||||
else if (Top <= box.Bottom)
|
||||
pos = RelativePosition.Bottom;
|
||||
else
|
||||
pos = RelativePosition.Intersecting;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Box Offset(double d)
|
||||
{
|
||||
return new Box(X - d, Y - d, Width + d * 2, Height + d * 2);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[Box: X={0}, Y={1}, Width={2}, Height={3}]", X, Y, Width, Height);
|
||||
}
|
||||
}
|
||||
}
|
||||
57
OpenNest.Core/Geometry/BoxSplitter.cs
Normal file
57
OpenNest.Core/Geometry/BoxSplitter.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public static class BoxSplitter
|
||||
{
|
||||
public static Box SplitTop(Box large, Box small)
|
||||
{
|
||||
if (!large.Intersects(small))
|
||||
return Box.Empty;
|
||||
|
||||
var x = large.Left;
|
||||
var y = small.Top;
|
||||
var w = large.Width;
|
||||
var h = large.Top - y;
|
||||
|
||||
return new Box(x, y, w, h);
|
||||
}
|
||||
|
||||
public static Box SplitLeft(Box large, Box small)
|
||||
{
|
||||
if (!large.Intersects(small))
|
||||
return Box.Empty;
|
||||
|
||||
var x = large.Left;
|
||||
var y = large.Bottom;
|
||||
var w = small.Left - x;
|
||||
var h = large.Height;
|
||||
|
||||
return new Box(x, y, w, h);
|
||||
}
|
||||
|
||||
public static Box SplitBottom(Box large, Box small)
|
||||
{
|
||||
if (!large.Intersects(small))
|
||||
return Box.Empty;
|
||||
|
||||
var x = large.Left;
|
||||
var y = large.Bottom;
|
||||
var w = large.Width;
|
||||
var h = small.Top - y;
|
||||
|
||||
return new Box(x, y, w, h);
|
||||
}
|
||||
|
||||
public static Box SplitRight(Box large, Box small)
|
||||
{
|
||||
if (!large.Intersects(small))
|
||||
return Box.Empty;
|
||||
|
||||
var x = small.Right;
|
||||
var y = large.Bottom;
|
||||
var w = large.Right - x;
|
||||
var h = large.Height;
|
||||
|
||||
return new Box(x, y, w, h);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
OpenNest.Core/Geometry/IBoundable.cs
Normal file
18
OpenNest.Core/Geometry/IBoundable.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public interface IBoundable
|
||||
{
|
||||
Box BoundingBox { get; }
|
||||
|
||||
double Left { get; }
|
||||
|
||||
double Right { get; }
|
||||
|
||||
double Top { get; }
|
||||
|
||||
double Bottom { get; }
|
||||
|
||||
void UpdateBounds();
|
||||
}
|
||||
}
|
||||
55
OpenNest.Core/Geometry/Size.cs
Normal file
55
OpenNest.Core/Geometry/Size.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public struct Size
|
||||
{
|
||||
public Size(double width, double height)
|
||||
{
|
||||
Height = height;
|
||||
Width = width;
|
||||
}
|
||||
|
||||
public double Height;
|
||||
|
||||
public double Width;
|
||||
|
||||
public static Size Parse(string size)
|
||||
{
|
||||
var a = size.ToUpper().Split('X');
|
||||
|
||||
if (a.Length > 2)
|
||||
throw new FormatException("Invalid size format.");
|
||||
|
||||
var height = double.Parse(a[0]);
|
||||
var width = double.Parse(a[1]);
|
||||
|
||||
return new Size(width, height);
|
||||
}
|
||||
|
||||
public static bool TryParse(string s, out Size size)
|
||||
{
|
||||
try
|
||||
{
|
||||
size = Parse(s);
|
||||
}
|
||||
catch
|
||||
{
|
||||
size = new Size(0, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0} x {1}", Height, Width);
|
||||
}
|
||||
|
||||
public string ToString(int decimalPlaces)
|
||||
{
|
||||
return string.Format("{0} x {1}", System.Math.Round(Height, decimalPlaces), System.Math.Round(Width, decimalPlaces));
|
||||
}
|
||||
}
|
||||
}
|
||||
27
OpenNest.Core/Geometry/Spacing.cs
Normal file
27
OpenNest.Core/Geometry/Spacing.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public struct Spacing
|
||||
{
|
||||
public Spacing(double topBottom, double leftRight)
|
||||
{
|
||||
Top = Bottom = topBottom;
|
||||
Left = Right = leftRight;
|
||||
}
|
||||
|
||||
public Spacing(double left, double bottom, double right, double top)
|
||||
{
|
||||
Left = left;
|
||||
Bottom = bottom;
|
||||
Right = right;
|
||||
Top = top;
|
||||
}
|
||||
|
||||
public double Left;
|
||||
|
||||
public double Bottom;
|
||||
|
||||
public double Right;
|
||||
|
||||
public double Top;
|
||||
}
|
||||
}
|
||||
214
OpenNest.Core/Geometry/Vector.cs
Normal file
214
OpenNest.Core/Geometry/Vector.cs
Normal file
@@ -0,0 +1,214 @@
|
||||
using System;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public struct Vector
|
||||
{
|
||||
public static readonly Vector Invalid = new Vector(double.NaN, double.NaN);
|
||||
public static readonly Vector Zero = new Vector(0, 0);
|
||||
|
||||
public double X;
|
||||
public double Y;
|
||||
|
||||
public Vector(double x, double y)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
}
|
||||
|
||||
public double DistanceTo(Vector pt)
|
||||
{
|
||||
var vx = pt.X - X;
|
||||
var vy = pt.Y - Y;
|
||||
|
||||
return System.Math.Sqrt(vx * vx + vy * vy);
|
||||
}
|
||||
|
||||
public double DistanceTo(double x, double y)
|
||||
{
|
||||
var vx = x - X;
|
||||
var vy = y - Y;
|
||||
|
||||
return System.Math.Sqrt(vx * vx + vy * vy);
|
||||
}
|
||||
|
||||
public double DotProduct(Vector pt)
|
||||
{
|
||||
return X * pt.X + Y * pt.Y;
|
||||
}
|
||||
|
||||
public double Angle()
|
||||
{
|
||||
return OpenNest.Math.Angle.NormalizeRad(System.Math.Atan2(Y, X));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the angle to the given point when the origin is this point.
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
/// <returns></returns>
|
||||
public double AngleTo(Vector pt)
|
||||
{
|
||||
return (pt - this).Angle();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the angle when the origin is set at the given point.
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
/// <returns></returns>
|
||||
public double AngleFrom(Vector pt)
|
||||
{
|
||||
return (this - pt).Angle();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the angle between this point and the given point.
|
||||
/// Source: http://math.stackexchange.com/questions/878785/how-to-find-an-angle-in-range0-360-between-2-vectors
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
/// <returns></returns>
|
||||
public double AngleBetween(Vector pt)
|
||||
{
|
||||
var v1 = Normalize();
|
||||
var v2 = pt.Normalize();
|
||||
var dot = v1.X * v2.X + v1.Y + v2.Y;
|
||||
var det = v1.X * v2.X - v1.Y + v2.Y;
|
||||
|
||||
return System.Math.Atan2(det, dot);
|
||||
}
|
||||
|
||||
public static Vector operator +(Vector pt1, Vector pt2)
|
||||
{
|
||||
return new Vector(pt1.X + pt2.X, pt1.Y + pt2.Y);
|
||||
}
|
||||
|
||||
public static Vector operator -(Vector pt1, Vector pt2)
|
||||
{
|
||||
return new Vector(pt1.X - pt2.X, pt1.Y - pt2.Y);
|
||||
}
|
||||
|
||||
public static Vector operator -(Vector pt)
|
||||
{
|
||||
return new Vector(-pt.X, -pt.Y);
|
||||
}
|
||||
|
||||
public static Vector operator *(Vector pt, double factor)
|
||||
{
|
||||
return new Vector(pt.X * factor, pt.Y * factor);
|
||||
}
|
||||
|
||||
public static Vector operator *(double factor, Vector pt)
|
||||
{
|
||||
return new Vector(pt.X * factor, pt.Y * factor);
|
||||
}
|
||||
|
||||
public static Vector operator *(Vector pt, Vector factor)
|
||||
{
|
||||
return new Vector(pt.X * factor.X, pt.Y * factor.Y);
|
||||
}
|
||||
|
||||
public static Vector operator /(Vector pt, double divisor)
|
||||
{
|
||||
return new Vector(pt.X / divisor, pt.Y / divisor);
|
||||
}
|
||||
|
||||
public static bool operator ==(Vector pt1, Vector pt2)
|
||||
{
|
||||
return pt1.X.IsEqualTo(pt2.X) && pt1.Y.IsEqualTo(pt2.Y);
|
||||
}
|
||||
|
||||
public static bool operator !=(Vector pt1, Vector pt2)
|
||||
{
|
||||
return !(pt1 == pt2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the unit vector equivalent to this point.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Vector Normalize()
|
||||
{
|
||||
var d = DistanceTo(Vector.Zero);
|
||||
return new Vector(X / d, Y / d);
|
||||
}
|
||||
|
||||
public Vector Rotate(double angle)
|
||||
{
|
||||
var v = new Vector();
|
||||
|
||||
var cos = System.Math.Cos(angle);
|
||||
var sin = System.Math.Sin(angle);
|
||||
|
||||
v.X = X * cos - Y * sin;
|
||||
v.Y = X * sin + Y * cos;
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
public Vector Rotate(double angle, Vector origin)
|
||||
{
|
||||
var v = new Vector();
|
||||
var pt = this - origin;
|
||||
|
||||
var cos = System.Math.Cos(angle);
|
||||
var sin = System.Math.Sin(angle);
|
||||
|
||||
v.X = pt.X * cos - pt.Y * sin + origin.X;
|
||||
v.Y = pt.X * sin + pt.Y * cos + origin.Y;
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
public Vector Offset(double x, double y)
|
||||
{
|
||||
return new Vector(X + x, Y + y);
|
||||
}
|
||||
|
||||
public Vector Offset(Vector voffset)
|
||||
{
|
||||
return this + voffset;
|
||||
}
|
||||
|
||||
public Vector Scale(double factor)
|
||||
{
|
||||
return new Vector(X * factor, Y * factor);
|
||||
}
|
||||
|
||||
public Vector Scale(double factor, Vector origin)
|
||||
{
|
||||
return (this - origin) * factor + origin;
|
||||
}
|
||||
|
||||
public Vector Clone()
|
||||
{
|
||||
return new Vector(X, Y);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (!(obj is Vector))
|
||||
return false;
|
||||
|
||||
var pt = (Vector)obj;
|
||||
|
||||
return (X.IsEqualTo(pt.X)) && (Y.IsEqualTo(pt.Y));
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return base.GetHashCode();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[Vector: X:{0}, Y:{1}]", X, Y);
|
||||
}
|
||||
|
||||
public bool IsValid()
|
||||
{
|
||||
return !double.IsNaN(X) && !double.IsNaN(Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user