using OpenNest.Math; using System; using System.Collections.Generic; namespace OpenNest.Geometry { public class Arc : Entity { private double radius; private double startAngle; private double endAngle; private Vector center; private bool reversed; public Arc() { } public Arc(double x, double y, double r, double a1, double a2, bool reversed = false) : this(new Vector(x, y), r, a1, a2, reversed) { } public Arc(Vector center, double radius, double startAngle, double endAngle, bool reversed = false) { this.center = center; this.radius = radius; this.startAngle = startAngle; this.endAngle = endAngle; this.reversed = reversed; UpdateBounds(); } /// /// Center point. /// public Vector Center { get { return center; } set { var offset = value - center; boundingBox.Offset(offset); center = value; } } /// /// Arc radius. /// public double Radius { get { return radius; } set { radius = value; UpdateBounds(); } } /// /// Arc radius * 2. Value NOT stored. /// public double Diameter { get { return Radius * 2.0; } set { Radius = value / 2.0; } } /// /// Start angle in radians. /// public double StartAngle { get { return startAngle; } set { startAngle = Angle.NormalizeRad(value); UpdateBounds(); } } /// /// End angle in radians. /// public double EndAngle { get { return endAngle; } set { endAngle = Angle.NormalizeRad(value); UpdateBounds(); } } /// /// Angle in radians between start and end angles. /// /// public double SweepAngle() { var startAngle = StartAngle; var endAngle = EndAngle; if (IsReversed) Generic.Swap(ref startAngle, ref endAngle); if (startAngle > endAngle) startAngle -= Angle.TwoPI; return endAngle - startAngle; } /// /// Gets or sets if the arc direction is reversed (clockwise). /// public bool IsReversed { get { return reversed; } set { if (reversed != value) Reverse(); } } public RotationType Rotation { get { return IsReversed ? RotationType.CW : RotationType.CCW; } set { IsReversed = (value == RotationType.CW); } } /// /// Start point of the arc. /// /// public Vector StartPoint() { return new Vector( Center.X + Radius * System.Math.Cos(StartAngle), Center.Y + Radius * System.Math.Sin(StartAngle)); } /// /// End point of the arc. /// /// public Vector EndPoint() { return new Vector( Center.X + Radius * System.Math.Cos(EndAngle), Center.Y + Radius * System.Math.Sin(EndAngle)); } /// /// Splits the arc at the given point, returning two sub-arcs. /// Either half may be null if the split point coincides with an endpoint. /// /// The point at which to split the arc. /// A tuple of (first, second) sub-arcs. public (Arc first, Arc second) SplitAt(Vector point) { if (point.DistanceTo(StartPoint()) < Tolerance.Epsilon) return (null, new Arc(Center, Radius, StartAngle, EndAngle, IsReversed)); if (point.DistanceTo(EndPoint()) < Tolerance.Epsilon) return (new Arc(Center, Radius, StartAngle, EndAngle, IsReversed), null); var splitAngle = Angle.NormalizeRad(Center.AngleTo(point)); var firstArc = new Arc(Center, Radius, StartAngle, splitAngle, IsReversed); var secondArc = new Arc(Center, Radius, splitAngle, EndAngle, IsReversed); return (firstArc, secondArc); } /// /// Returns true if the given arc has the same center point and radius as this. /// /// /// public bool IsCoradialTo(Arc arc) { return center == arc.Center && Radius.IsEqualTo(arc.Radius); } /// /// Returns true if the given arc has the same radius as this. /// /// /// public bool IsConcentricTo(Arc arc) { return center == arc.center; } /// /// Returns true if the given circle has the same radius as this. /// /// /// public bool IsConcentricTo(Circle circle) { return center == circle.Center; } /// /// Returns the minimum number of segments needed so that the chord-to-arc /// deviation (sagitta) does not exceed the given tolerance. /// public int SegmentsForTolerance(double tolerance) { if (tolerance >= Radius) return 1; var maxAngle = 2.0 * System.Math.Acos(1.0 - tolerance / Radius); return System.Math.Max(1, (int)System.Math.Ceiling(System.Math.Abs(SweepAngle()) / maxAngle)); } /// /// Converts the arc to a group of points. /// /// Number of parts to divide the arc into. /// public List ToPoints(int segments = 1000, bool circumscribe = false) { var points = new List(); var stepAngle = reversed ? -SweepAngle() / segments : SweepAngle() / segments; var r = circumscribe && segments > 0 ? Radius / System.Math.Cos(System.Math.Abs(stepAngle) / 2.0) : Radius; for (int i = 0; i <= segments; ++i) { var angle = stepAngle * i + StartAngle; points.Add(new Vector( System.Math.Cos(angle) * r + Center.X, System.Math.Sin(angle) * r + Center.Y)); } return points; } /// /// Linear distance of the arc. /// public override double Length { get { return Diameter * System.Math.PI * SweepAngle() / Angle.TwoPI; } } /// /// Reverses the rotation direction. /// public override void Reverse() { reversed = !reversed; Generic.Swap(ref startAngle, ref endAngle); } /// /// Moves the center point to the given coordinates. /// /// The x-coordinate /// The y-coordinate public override void MoveTo(double x, double y) { Center = new Vector(x, y); } /// /// Moves the center point to the given point. /// /// The new center point location. public override void MoveTo(Vector pt) { Center = pt; } /// /// Offsets the center point by the given distances. /// /// The x-axis offset distance. /// The y-axis offset distance. public override void Offset(double x, double y) { Center = new Vector(Center.X + x, Center.Y + y); } /// /// Offsets the center point by the given distances. /// /// public override void Offset(Vector voffset) { Center += voffset; } /// /// Scales the arc from the zero point. /// /// public override void Scale(double factor) { center *= factor; radius *= factor; UpdateBounds(); } /// /// Scales the arc from the origin. /// /// /// public override void Scale(double factor, Vector origin) { center = center.Scale(factor, origin); radius *= factor; UpdateBounds(); } /// /// Rotates the arc from the zero point. /// /// public override void Rotate(double angle) { startAngle += angle; endAngle += angle; center = center.Rotate(angle); UpdateBounds(); } /// /// Rotates the arc from the origin. /// /// /// public override void Rotate(double angle, Vector origin) { startAngle += angle; endAngle += angle; center = center.Rotate(angle, origin); UpdateBounds(); } /// /// Updates the bounding box. /// public override void UpdateBounds() { var startpt = StartPoint(); var endpt = EndPoint(); double minX; double minY; double maxX; double maxY; if (startpt.X < endpt.X) { minX = startpt.X; maxX = endpt.X; } else { minX = endpt.X; maxX = startpt.X; } if (startpt.Y < endpt.Y) { minY = startpt.Y; maxY = endpt.Y; } else { minY = endpt.Y; maxY = startpt.Y; } var angle1 = StartAngle; var angle2 = EndAngle; // switch the angle to counter clockwise. if (IsReversed) Generic.Swap(ref angle1, ref angle2); if (Angle.IsBetweenRad(Angle.HalfPI, angle1, angle2)) maxY = Center.Y + Radius; if (Angle.IsBetweenRad(System.Math.PI, angle1, angle2)) minX = Center.X - Radius; const double oneHalfPI = System.Math.PI * 1.5; if (Angle.IsBetweenRad(oneHalfPI, angle1, angle2)) minY = Center.Y - Radius; if (Angle.IsBetweenRad(Angle.TwoPI, angle1, angle2)) maxX = Center.X + Radius; boundingBox.X = minX; boundingBox.Y = minY; boundingBox.Width = maxX - minX; boundingBox.Length = maxY - minY; } public override Entity OffsetEntity(double distance, OffsetSide side) { if (side == OffsetSide.Left && reversed) { return new Arc(center, radius + distance, startAngle, endAngle, reversed); } else { if (distance >= radius) return null; return new Arc(center, radius - distance, startAngle, endAngle, reversed); } } public override Entity OffsetEntity(double distance, Vector pt) { throw new NotImplementedException(); } /// /// Gets the closest point on the arc to the given point. /// /// /// public override Vector ClosestPointTo(Vector pt) { var angle = Center.AngleTo(pt); if (Angle.IsBetweenRad(angle, StartAngle, EndAngle, IsReversed)) { return new Vector( System.Math.Cos(angle) * Radius + Center.X, System.Math.Sin(angle) * Radius + Center.Y); } else { var sp = StartPoint(); var ep = EndPoint(); return pt.DistanceTo(sp) <= pt.DistanceTo(ep) ? sp : ep; } } /// /// Returns true if the given arc is intersecting this. /// /// /// public override bool Intersects(Arc arc) { List pts; return Intersect.Intersects(this, arc, out pts); } /// /// Returns true if the given arc is intersecting this. /// /// /// Points of intersection. /// public override bool Intersects(Arc arc, out List pts) { return Intersect.Intersects(this, arc, out pts); ; } /// /// Returns true if the given circle is intersecting this. /// /// /// public override bool Intersects(Circle circle) { List pts; return Intersect.Intersects(this, circle, out pts); } /// /// Returns true if the given circle is intersecting this. /// /// /// Points of intersection. /// public override bool Intersects(Circle circle, out List pts) { return Intersect.Intersects(this, circle, out pts); } /// /// Returns true if the given line is intersecting this. /// /// /// public override bool Intersects(Line line) { List pts; return Intersect.Intersects(this, line, out pts); } /// /// Returns true if the given line is intersecting this. /// /// /// Points of intersection. /// public override bool Intersects(Line line, out List pts) { return Intersect.Intersects(this, line, out pts); } /// /// Returns true if the given polygon is intersecting this. /// /// /// public override bool Intersects(Polygon polygon) { List pts; return Intersect.Intersects(this, polygon, out pts); } /// /// Returns true if the given polygon is intersecting this. /// /// /// Points of intersection. /// public override bool Intersects(Polygon polygon, out List pts) { return Intersect.Intersects(this, polygon, out pts); } /// /// Returns true if the given shape is intersecting this. /// /// /// public override bool Intersects(Shape shape) { List pts; return Intersect.Intersects(this, shape, out pts); } /// /// Returns true if the given shape is intersecting this. /// /// /// Points of intersection. /// public override bool Intersects(Shape shape, out List pts) { return Intersect.Intersects(this, shape, out pts); } /// /// Type of entity. /// public override EntityType Type { get { return EntityType.Arc; } } } }