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(); } /// /// Start point of the line. /// public Vector StartPoint { get { return pt1; } set { pt1 = value; UpdateBounds(); } } /// /// Mid-point of the line. /// 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); } } /// /// End point of the line. /// public Vector EndPoint { get { return pt2; } set { pt2 = value; UpdateBounds(); } } /// /// Gets the point on the line that is perpendicular from the given point. /// /// /// 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); } } /// /// Returns true if the given line is parallel to this. /// /// /// 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()); } /// /// Returns true if the given line is perpendicular to this. /// /// /// 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()); } /// /// Returns true if the given line is intersecting this. /// /// /// Point of intersection. /// 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); } } /// /// Returns true if this is vertical. /// /// public bool IsVertical() { return pt1.X.IsEqualTo(pt2.X); } /// /// Returns true if this is horizontal. /// /// public bool IsHorizontal() { return pt1.Y.IsEqualTo(pt2.Y); } /// /// Returns true if the given line is collinear to this. /// /// /// 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; } /// /// Angle of the line from start point to end point. /// /// public double Angle() { return StartPoint.AngleTo(EndPoint); } /// /// Returns the angle between the two lines in radians. /// /// /// public double AngleBetween(Line line) { var m1 = Slope(); var m2 = line.Slope(); return System.Math.Atan(System.Math.Abs((m2 - m1) / (1 + m2 * m1))); } /// /// Slope of the line. /// /// public double Slope() { if (IsVertical()) throw new DivideByZeroException(); return (EndPoint.Y - StartPoint.Y) / (EndPoint.X - StartPoint.X); } /// /// Gets the y-axis intersection coordinate. /// /// public double YIntercept() { return StartPoint.Y - Slope() * StartPoint.X; } /// /// Length of the line from start point to end point. /// 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); } } /// /// Reversed the line. /// public override void Reverse() { Generic.Swap(ref pt1, ref pt2); } /// /// Moves the start point to the given coordinates. /// /// /// 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); } /// /// Moves the start point to the given point. /// /// public override void MoveTo(Vector pt) { var offset = pt1 - pt; pt2 += offset; pt1 = pt; boundingBox.Offset(offset); } /// /// Offsets the line location by the given distances. /// /// /// public override void Offset(double x, double y) { pt2.X += x; pt2.Y += y; pt1.X += x; pt1.Y += y; boundingBox.Offset(x, y); } /// /// Offsets the line location by the given distances. /// /// public override void Offset(Vector voffset) { pt1 += voffset; pt2 += voffset; boundingBox.Offset(voffset); } /// /// Scales the line from the zero point. /// /// public override void Scale(double factor) { pt1 *= factor; pt2 *= factor; } /// /// Scales the line from the origin. /// /// /// public override void Scale(double factor, Vector origin) { pt1 = (pt1 - origin) * factor + origin; pt2 = (pt2 - origin) * factor + origin; } /// /// Rotates the line from the zero point. /// /// public override void Rotate(double angle) { StartPoint = StartPoint.Rotate(angle); EndPoint = EndPoint.Rotate(angle); } /// /// Rotates the line from the origin. /// /// /// public override void Rotate(double angle, Vector origin) { StartPoint = StartPoint.Rotate(angle, origin); EndPoint = EndPoint.Rotate(angle, origin); } /// /// Updates the bounding box. /// public override sealed void UpdateBounds() { if (StartPoint.X < EndPoint.X) { boundingBox.X = StartPoint.X; boundingBox.Width = EndPoint.X - StartPoint.X; } else { boundingBox.X = EndPoint.X; boundingBox.Width = StartPoint.X - EndPoint.X; } if (StartPoint.Y < EndPoint.Y) { boundingBox.Y = StartPoint.Y; boundingBox.Length = EndPoint.Y - StartPoint.Y; } else { boundingBox.Y = EndPoint.Y; boundingBox.Length = 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 = System.Math.Cos(angle) * distance; var y = System.Math.Sin(angle) * distance; var pt = new Vector(x, y); return side == OffsetSide.Left ? new Line(StartPoint + pt, EndPoint + pt) : new Line(EndPoint + pt, StartPoint + 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); } /// /// Splits the line at the given point, returning two sub-lines. /// Either half may be null if the split point coincides with an endpoint. /// /// The point at which to split the line. /// A tuple of (first, second) sub-lines. 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); } /// /// Gets the closest point on the line to the given point. /// /// /// 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; } /// /// Returns true if the given arc is intersecting this. /// /// /// public override bool Intersects(Arc arc) { List pts; return Helper.Intersects(arc, this, out pts); } /// /// Returns true if the given arc is intersecting this. /// /// /// /// public override bool Intersects(Arc arc, out List pts) { return Helper.Intersects(arc, this, out pts); } /// /// Returns true if the given circle is intersecting this. /// /// /// public override bool Intersects(Circle circle) { List pts; return Helper.Intersects(circle, this, out pts); } /// /// Returns true if the given circle is intersecting this. /// /// /// /// public override bool Intersects(Circle circle, out List pts) { return Helper.Intersects(circle, this, out pts); } /// /// Returns true if the given line is intersecting this. /// /// /// public override bool Intersects(Line line) { Vector pt; return Intersects(line, out pt); } /// /// Returns true if the given line is intersecting this. /// /// /// /// public override bool Intersects(Line line, out List pts) { Vector pt; var success = Helper.Intersects(this, line, out pt); pts = new List(new[] { pt }); return success; } /// /// Returns true if the given polygon is intersecting this. /// /// /// public override bool Intersects(Polygon polygon) { List pts; return Helper.Intersects(this, polygon, out pts); } /// /// Returns true if the given polygon is intersecting this. /// /// /// /// public override bool Intersects(Polygon polygon, out List pts) { return Helper.Intersects(this, polygon, out pts); } /// /// Returns true if the given shape is intersecting this. /// /// /// public override bool Intersects(Shape shape) { List pts; return Helper.Intersects(this, shape, out pts); } /// /// Returns true if the given shape is intersecting this. /// /// /// /// public override bool Intersects(Shape shape, out List pts) { return Helper.Intersects(this, shape, out pts); } /// /// Type of entity. /// public override EntityType Type { get { return EntityType.Line; } } } }