Files
OpenNest/OpenNest.Core/Geometry/Line.cs
T
aj 49603eddf9 fix(geometry): nest the same on Windows and Linux
Geometry, nesting and import code now call PortableMath instead of
System.Math for Sin, Cos, Tan, Atan, Atan2, Asin and Acos (Core, Engine,
IO, Data; 269 calls in 64 files). A test fails if those projects call the
runtime's trig again.

With the runtime's trig, Windows and Linux disagreed in the last bit at
non-quarter angles and the same job could nest differently: the
learned-angle nests (a cross fixture) placed 23 parts in Strip on Windows
and 28 on Linux. Now every Engine.Tests pin gives the same layout on both
(3600 passing on Linux and on DesktopPC), Linux layouts are unchanged, and
the learned-angle rows are re-pinned to the shared layouts, which pass the
overlap and work-area checks, and run on every platform again.

Fixes Project Memory 518f3b58.
2026-11-09 19:18:00 -05:00

572 lines
17 KiB
C#

using System;
using System.Collections.Generic;
using OpenNest.Math;
namespace OpenNest.Geometry
{
public class Line : Entity
{
internal Vector pt1;
internal Vector pt2;
public Line() { }
public Line(double x1, double y1, double x2, double y2)
: this(new Vector(x1, y1), new Vector(x2, y2)) { }
public Line(Vector startPoint, Vector endPoint)
{
pt1 = startPoint;
pt2 = endPoint;
UpdateBounds();
}
/// <summary>
/// Start point of the line.
/// </summary>
public Vector StartPoint
{
get { return pt1; }
set
{
pt1 = value;
UpdateBounds();
}
}
/// <summary>
/// Mid-point of the line.
/// </summary>
public Vector MidPoint
{
get
{
var x = (pt1.X + pt2.X) * 0.5;
var y = (pt1.Y + pt2.Y) * 0.5;
return new Vector(x, y);
}
}
/// <summary>
/// End point of the line.
/// </summary>
public Vector EndPoint
{
get { return pt2; }
set
{
pt2 = value;
UpdateBounds();
}
}
/// <summary>
/// Gets the point on the line that is perpendicular from the given point.
/// </summary>
/// <param name="pt"></param>
/// <returns></returns>
public Vector PointPerpendicularFrom(Vector pt)
{
var diff1 = pt - StartPoint;
var diff2 = EndPoint - StartPoint;
var dotProduct = diff1.X * diff2.X + diff1.Y * diff2.Y;
var lengthSquared = diff2.X * diff2.X + diff2.Y * diff2.Y;
var param = dotProduct / lengthSquared;
if (param < 0)
return StartPoint;
else if (param > 1)
return EndPoint;
else
{
return new Vector(StartPoint.X + param * diff2.X, StartPoint.Y + param * diff2.Y);
}
}
/// <summary>
/// Returns true if the given line is parallel to this.
/// </summary>
/// <param name="line"></param>
/// <returns></returns>
public bool IsParallelTo(Line line)
{
bool line1Vertical = IsVertical();
bool line2Vertical = line.IsVertical();
if (line1Vertical)
return line2Vertical;
else if (line2Vertical)
return false;
return Slope().IsEqualTo(line.Slope());
}
/// <summary>
/// Returns true if the given line is perpendicular to this.
/// </summary>
/// <param name="line"></param>
/// <returns></returns>
public bool IsPerpendicularTo(Line line)
{
bool line1Vertical = IsVertical();
bool line2Vertical = line.IsVertical();
if (line1Vertical)
return line.IsHorizontal();
else if (line.IsVertical())
return IsHorizontal();
return Slope().IsEqualTo(-1 / line.Slope());
}
/// <summary>
/// Returns true if the given line is intersecting this.
/// </summary>
/// <param name="line"></param>
/// <param name="pt">Point of intersection.</param>
/// <returns></returns>
public bool Intersects(Line line, out Vector pt)
{
var a1 = EndPoint.Y - StartPoint.Y;
var b1 = StartPoint.X - EndPoint.X;
var c1 = a1 * StartPoint.X + b1 * StartPoint.Y;
var a2 = line.EndPoint.Y - line.StartPoint.Y;
var b2 = line.StartPoint.X - line.EndPoint.X;
var c2 = a2 * line.StartPoint.X + b2 * line.StartPoint.Y;
var d = a1 * b2 - a2 * b1;
if (d.IsEqualTo(0.0))
{
pt = new Vector();
return false;
}
else
{
var x = (b2 * c1 - b1 * c2) / d;
var y = (a1 * c2 - a2 * c1) / d;
pt = new Vector(x, y);
return boundingBox.Contains(pt) && line.boundingBox.Contains(pt);
}
}
/// <summary>
/// Returns true if this is vertical.
/// </summary>
/// <returns></returns>
public bool IsVertical()
{
return pt1.X.IsEqualTo(pt2.X);
}
/// <summary>
/// Returns true if this is horizontal.
/// </summary>
/// <returns></returns>
public bool IsHorizontal()
{
return pt1.Y.IsEqualTo(pt2.Y);
}
/// <summary>
/// Returns true if the given line is collinear to this.
/// </summary>
/// <param name="line"></param>
/// <returns></returns>
public bool IsCollinearTo(Line line)
{
if (IsVertical())
{
if (!line.IsVertical())
return false;
return StartPoint.X.IsEqualTo(line.StartPoint.X);
}
else if (line.IsVertical())
return false;
if (!YIntercept().IsEqualTo(line.YIntercept()))
return false;
if (!Slope().IsEqualTo(line.Slope()))
return false;
return true;
}
/// <summary>
/// Angle of the line from start point to end point.
/// </summary>
/// <returns></returns>
public double Angle()
{
return StartPoint.AngleTo(EndPoint);
}
/// <summary>
/// Returns the angle between the two lines in radians.
/// </summary>
/// <param name="line"></param>
/// <returns></returns>
public double AngleBetween(Line line)
{
var m1 = Slope();
var m2 = line.Slope();
return PortableMath.Atan(System.Math.Abs((m2 - m1) / (1 + m2 * m1)));
}
/// <summary>
/// Slope of the line.
/// </summary>
/// <returns></returns>
public double Slope()
{
if (IsVertical())
throw new DivideByZeroException();
return (EndPoint.Y - StartPoint.Y) / (EndPoint.X - StartPoint.X);
}
/// <summary>
/// Gets the y-axis intersection coordinate.
/// </summary>
/// <returns></returns>
public double YIntercept()
{
return StartPoint.Y - Slope() * StartPoint.X;
}
/// <summary>
/// Length of the line from start point to end point.
/// </summary>
public override double Length
{
get
{
var x = EndPoint.X - StartPoint.X;
var y = EndPoint.Y - StartPoint.Y;
return System.Math.Sqrt(x * x + y * y);
}
}
public override Entity Clone()
{
var copy = new Line(pt1, pt2);
CopyBaseTo(copy);
return copy;
}
/// <summary>
/// Reversed the line.
/// </summary>
public override void Reverse()
{
Generic.Swap<Vector>(ref pt1, ref pt2);
}
/// <summary>
/// Moves the start point to the given coordinates.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
public override void MoveTo(double x, double y)
{
var xoffset = pt1.X - x;
var yoffset = pt1.Y - y;
pt2.X += xoffset;
pt2.Y += yoffset;
pt1.X = x;
pt1.Y = y;
boundingBox.Offset(xoffset, yoffset);
}
/// <summary>
/// Moves the start point to the given point.
/// </summary>
/// <param name="pt"></param>
public override void MoveTo(Vector pt)
{
var offset = pt1 - pt;
pt2 += offset;
pt1 = pt;
boundingBox.Offset(offset);
}
/// <summary>
/// Offsets the line location by the given distances.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
public override void Offset(double x, double y)
{
pt2.X += x;
pt2.Y += y;
pt1.X += x;
pt1.Y += y;
boundingBox.Offset(x, y);
}
/// <summary>
/// Offsets the line location by the given distances.
/// </summary>
/// <param name="voffset"></param>
public override void Offset(Vector voffset)
{
pt1 += voffset;
pt2 += voffset;
boundingBox.Offset(voffset);
}
/// <summary>
/// Scales the line from the zero point.
/// </summary>
/// <param name="factor"></param>
public override void Scale(double factor)
{
pt1 *= factor;
pt2 *= factor;
}
/// <summary>
/// Scales the line from the origin.
/// </summary>
/// <param name="factor"></param>
/// <param name="origin"></param>
public override void Scale(double factor, Vector origin)
{
pt1 = (pt1 - origin) * factor + origin;
pt2 = (pt2 - origin) * factor + origin;
}
/// <summary>
/// Rotates the line from the zero point.
/// </summary>
/// <param name="angle"></param>
public override void Rotate(double angle)
{
StartPoint = StartPoint.Rotate(angle);
EndPoint = EndPoint.Rotate(angle);
}
/// <summary>
/// Rotates the line from the origin.
/// </summary>
/// <param name="angle"></param>
/// <param name="origin"></param>
public override void Rotate(double angle, Vector origin)
{
StartPoint = StartPoint.Rotate(angle, origin);
EndPoint = EndPoint.Rotate(angle, origin);
}
/// <summary>
/// Updates the bounding box.
/// </summary>
public sealed override void UpdateBounds()
{
if (StartPoint.X < EndPoint.X)
{
boundingBox.X = StartPoint.X;
boundingBox.Length = EndPoint.X - StartPoint.X;
}
else
{
boundingBox.X = EndPoint.X;
boundingBox.Length = StartPoint.X - EndPoint.X;
}
if (StartPoint.Y < EndPoint.Y)
{
boundingBox.Y = StartPoint.Y;
boundingBox.Width = EndPoint.Y - StartPoint.Y;
}
else
{
boundingBox.Y = EndPoint.Y;
boundingBox.Width = StartPoint.Y - EndPoint.Y;
}
}
public override Entity OffsetEntity(double distance, OffsetSide side)
{
var angle = OpenNest.Math.Angle.NormalizeRad(Angle() + OpenNest.Math.Angle.HalfPI);
var x = PortableMath.Cos(angle) * distance;
var y = PortableMath.Sin(angle) * distance;
var pt = side == OffsetSide.Left ? new Vector(x, y) : new Vector(-x, -y);
return new Line(StartPoint + pt, EndPoint + pt);
}
public override Entity OffsetEntity(double distance, Vector pt)
{
var a = pt - StartPoint;
var b = EndPoint - StartPoint;
var c = a.DotProduct(b);
var side = c < 0 ? OffsetSide.Left : OffsetSide.Right;
return OffsetEntity(distance, side);
}
/// <summary>
/// Splits the line at the given point, returning two sub-lines.
/// Either half may be null if the split point coincides with an endpoint.
/// </summary>
/// <param name="point">The point at which to split the line.</param>
/// <returns>A tuple of (first, second) sub-lines.</returns>
public (Line first, Line second) SplitAt(Vector point)
{
var first =
point.DistanceTo(StartPoint) < Tolerance.Epsilon
? null
: new Line(StartPoint, point);
var second =
point.DistanceTo(EndPoint) < Tolerance.Epsilon ? null : new Line(point, EndPoint);
return (first, second);
}
/// <summary>
/// Gets the closest point on the line to the given point.
/// </summary>
/// <param name="pt"></param>
/// <returns></returns>
public override Vector ClosestPointTo(Vector pt)
{
var perpendicularPt = PointPerpendicularFrom(pt);
if (BoundingBox.Contains(perpendicularPt))
return perpendicularPt;
else
return pt.DistanceTo(StartPoint) <= pt.DistanceTo(EndPoint) ? StartPoint : EndPoint;
}
/// <summary>
/// Returns true if the given arc is intersecting this.
/// </summary>
/// <param name="arc"></param>
/// <returns></returns>
public override bool Intersects(Arc arc)
{
List<Vector> pts;
return Intersect.Intersects(arc, this, out pts);
}
/// <summary>
/// Returns true if the given arc is intersecting this.
/// </summary>
/// <param name="arc"></param>
/// <param name="pts"></param>
/// <returns></returns>
public override bool Intersects(Arc arc, out List<Vector> pts)
{
return Intersect.Intersects(arc, this, out pts);
}
/// <summary>
/// Returns true if the given circle is intersecting this.
/// </summary>
/// <param name="circle"></param>
/// <returns></returns>
public override bool Intersects(Circle circle)
{
List<Vector> pts;
return Intersect.Intersects(circle, this, out pts);
}
/// <summary>
/// Returns true if the given circle is intersecting this.
/// </summary>
/// <param name="circle"></param>
/// <param name="pts"></param>
/// <returns></returns>
public override bool Intersects(Circle circle, out List<Vector> pts)
{
return Intersect.Intersects(circle, this, out pts);
}
/// <summary>
/// Returns true if the given line is intersecting this.
/// </summary>
/// <param name="line"></param>
/// <returns></returns>
public override bool Intersects(Line line)
{
Vector pt;
return Intersects(line, out pt);
}
/// <summary>
/// Returns true if the given line is intersecting this.
/// </summary>
/// <param name="line"></param>
/// <param name="pts"></param>
/// <returns></returns>
public override bool Intersects(Line line, out List<Vector> pts)
{
Vector pt;
var success = Intersect.Intersects(this, line, out pt);
pts = new List<Vector>(new[] { pt });
return success;
}
/// <summary>
/// Returns true if the given polygon is intersecting this.
/// </summary>
/// <param name="polygon"></param>
/// <returns></returns>
public override bool Intersects(Polygon polygon)
{
List<Vector> pts;
return Intersect.Intersects(this, polygon, out pts);
}
/// <summary>
/// Returns true if the given polygon is intersecting this.
/// </summary>
/// <param name="polygon"></param>
/// <param name="pts"></param>
/// <returns></returns>
public override bool Intersects(Polygon polygon, out List<Vector> pts)
{
return Intersect.Intersects(this, polygon, out pts);
}
/// <summary>
/// Returns true if the given shape is intersecting this.
/// </summary>
/// <param name="shape"></param>
/// <returns></returns>
public override bool Intersects(Shape shape)
{
List<Vector> pts;
return Intersect.Intersects(this, shape, out pts);
}
/// <summary>
/// Returns true if the given shape is intersecting this.
/// </summary>
/// <param name="shape"></param>
/// <param name="pts"></param>
/// <returns></returns>
public override bool Intersects(Shape shape, out List<Vector> pts)
{
return Intersect.Intersects(this, shape, out pts);
}
/// <summary>
/// Type of entity.
/// </summary>
public override EntityType Type
{
get { return EntityType.Line; }
}
}
}