using System; using System.Collections.Generic; using System.Linq; using OpenNest.Math; namespace OpenNest.Geometry { public class Polygon : Entity { public List Vertices; public Polygon() { Vertices = new List(); } /// /// Closes the polygon if it's not already. /// public void Close() { if (Vertices.Count < 3) return; var first = Vertices.First(); var last = Vertices.Last(); if (first != last) Vertices.Add(first); } /// /// Returns true if the polygon is closed. /// /// public bool IsClosed() { if (Vertices.Count < 3) return false; return (Vertices.First() == Vertices.Last()); } /// /// Returns true if the polygon is self intersecting. /// /// public bool IsComplex() { var lines = ToLines(); for (int i = 0; i < lines.Count; ++i) { var line1 = lines[i]; for (int j = i; j < lines.Count; ++j) { var line2 = lines[j]; if (line1.Intersects(line2)) return true; } } return false; } /// /// Area of the polygon. /// /// Returns the area or 0 if the polygon is NOT closed. public double Area() { if (Vertices.Count < 3) return 0.0; return System.Math.Abs(CalculateArea()); } /// /// Distance around the polygon. /// /// public double Perimeter() { if (Vertices.Count < 3) return 0.0; double sum = 0.0; var last = Vertices[0]; for (int i = 1; i < Vertices.Count; ++i) { var current = Vertices[i]; sum += last.DistanceTo(current); last = current; } return sum; } /// /// Gets the rotation direction of the polygon. /// /// public RotationType RotationDirection() { if (Vertices.Count < 3) throw new Exception("Not enough points to determine direction. Must have at least 3 points."); return CalculateArea() > 0 ? RotationType.CCW : RotationType.CW; } /// /// Converts the polygon to a group of lines. /// /// public List ToLines() { var list = new List(); if (Vertices.Count < 2) return list; var last = Vertices[0]; for (int i = 1; i < Vertices.Count; ++i) { var current = Vertices[i]; list.Add(new Line(last, current)); last = current; } return list; } /// /// Gets the area of the polygon. /// /// /// Returns the area of the polygon. /// * Positive number = counter-clockwise rotation /// * Negative number = clockwise rotation /// private double CalculateArea() { double xsum = 0; double ysum = 0; for (int i = 0; i < Vertices.Count - 1; ++i) { var current = Vertices[i]; var next = Vertices[i + 1]; xsum += current.X * next.Y; ysum += current.Y * next.X; } return (xsum - ysum) * 0.5; } /// /// Distance around the polygon. /// public override double Length { get { return Perimeter(); } } /// /// Reverses the rotation direction of the polygon. /// public override void Reverse() { Vertices.Reverse(); } /// /// Moves the start point to the given coordinates. /// /// /// public override void MoveTo(double x, double y) { if (Vertices.Count == 0) return; var first = Vertices[0]; var offset = new Vector(x - first.X, y - first.Y); Vertices.ForEach(vertex => vertex += offset); boundingBox.Offset(offset); } /// /// Moves the start point to the given point. /// /// public override void MoveTo(Vector pt) { if (Vertices.Count == 0) return; var first = Vertices[0]; var offset = pt - first; Vertices.ForEach(vertex => vertex += offset); boundingBox.Offset(offset); } /// /// Offsets the location by the given distances. /// /// /// public override void Offset(double x, double y) { for (int i = 0; i < Vertices.Count; i++) Vertices[i] = Vertices[i].Offset(x, y); boundingBox.Offset(x, y); } /// /// Offsets the location by the given distances. /// /// public override void Offset(Vector voffset) { for (int i = 0; i < Vertices.Count; i++) Vertices[i] = Vertices[i].Offset(voffset); boundingBox.Offset(voffset); } /// /// Scales the polygon from the zero point. /// /// public override void Scale(double factor) { for (int i = 0; i < Vertices.Count; i++) Vertices[i] *= factor; UpdateBounds(); } /// /// Scales the polygon from the zero point. /// /// /// public override void Scale(double factor, Vector origin) { for (int i = 0; i < Vertices.Count; i++) Vertices[i] = (Vertices[i] - origin) * factor + origin; UpdateBounds(); } /// /// Rotates the polygon from the zero point. /// /// public override void Rotate(double angle) { for (int i = 0; i < Vertices.Count; i++) Vertices[i] = Vertices[i].Rotate(angle); UpdateBounds(); } /// /// Rotates the polygon from the origin. /// /// /// public override void Rotate(double angle, Vector origin) { for (int i = 0; i < Vertices.Count; i++) Vertices[i] = Vertices[i].Rotate(angle, origin); UpdateBounds(); } /// /// Updates the bounding box. /// public override void UpdateBounds() { if (Vertices.Count == 0) return; var first = Vertices[0]; var minX = first.X; var maxX = first.X; var minY = first.Y; var maxY = first.Y; for (int i = 1; i < Vertices.Count; ++i) { var vertex = Vertices[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; } boundingBox.X = minX; boundingBox.Y = minY; boundingBox.Width = maxX - minX; boundingBox.Height = maxY - minY; } public override Entity OffsetEntity(double distance, OffsetSide side) { throw new NotImplementedException(); } public override Entity OffsetEntity(double distance, Vector pt) { throw new NotImplementedException(); } /// /// Gets the closest point on the polygon to the given point. /// /// /// public override Vector ClosestPointTo(Vector pt) { var lines = ToLines(); if (lines.Count == 0) return Vector.Invalid; Vector closestPt = lines[0].ClosestPointTo(pt); double distance = closestPt.DistanceTo(pt); for (int i = 1; i < lines.Count; i++) { var line = lines[i]; var closestPt2 = line.ClosestPointTo(pt); var distance2 = closestPt2.DistanceTo(pt); if (distance2 < distance) { closestPt = closestPt2; distance = distance2; } } return closestPt; } /// /// 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) { List pts; return Helper.Intersects(line, this, out pts); } /// /// Returns true if the given line is intersecting this. /// /// /// /// public override bool Intersects(Line line, out List pts) { return Helper.Intersects(line, this, out pts); } /// /// 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(shape, this, out pts); } /// /// Returns true if the given shape is intersecting this. /// /// /// /// public override bool Intersects(Shape shape, out List pts) { return Helper.Intersects(shape, this, out pts); } /// /// Type of entity. /// public override EntityType Type { get { return EntityType.Polygon; } } /// /// Removes self-intersecting loops from the polygon by finding non-adjacent /// edge crossings and keeping the larger contour at each crossing. /// public void RemoveSelfIntersections() { if (!IsClosed() || Vertices.Count < 5) return; bool found = true; while (found) { found = false; int n = Vertices.Count - 1; // exclude closing vertex for (int i = 0; i < n && !found; i++) { var a1 = Vertices[i]; var a2 = Vertices[i + 1]; for (int j = i + 2; j < n && !found; j++) { // Skip edges that share a vertex (first and last edge) if (i == 0 && j == n - 1) continue; var b1 = Vertices[j]; var b2 = Vertices[j + 1]; Vector pt; if (SegmentsIntersect(a1, a2, b1, b2, out pt)) { // Two loops formed by the crossing: // Loop A: vertices[0..i], pt, vertices[j+1..n-1], close // Loop B: pt, vertices[i+1..j], close var loopA = new List(); for (int k = 0; k <= i; k++) loopA.Add(Vertices[k]); loopA.Add(pt); for (int k = j + 1; k < n; k++) loopA.Add(Vertices[k]); loopA.Add(loopA[0]); var loopB = new List(); loopB.Add(pt); for (int k = i + 1; k <= j; k++) loopB.Add(Vertices[k]); loopB.Add(pt); var areaA = System.Math.Abs(CalculateArea(loopA)); var areaB = System.Math.Abs(CalculateArea(loopB)); Vertices = areaA >= areaB ? loopA : loopB; found = true; } } } } } private static bool SegmentsIntersect(Vector a1, Vector a2, Vector b1, Vector b2, out Vector pt) { var da = a2 - a1; var db = b2 - b1; var cross = da.X * db.Y - da.Y * db.X; if (cross.IsEqualTo(0.0)) { pt = Vector.Zero; return false; } var dc = b1 - a1; var t = (dc.X * db.Y - dc.Y * db.X) / cross; var u = (dc.X * da.Y - dc.Y * da.X) / cross; if (t > Tolerance.Epsilon && t < 1.0 - Tolerance.Epsilon && u > Tolerance.Epsilon && u < 1.0 - Tolerance.Epsilon) { pt = new Vector(a1.X + t * da.X, a1.Y + t * da.Y); return true; } pt = Vector.Zero; return false; } private static double CalculateArea(List vertices) { double xsum = 0; double ysum = 0; for (int i = 0; i < vertices.Count - 1; i++) { var current = vertices[i]; var next = vertices[i + 1]; xsum += current.X * next.Y; ysum += current.Y * next.X; } return (xsum - ysum) * 0.5; } internal void Cleanup() { for (int i = Vertices.Count - 1; i > 0; i--) { var vertex = Vertices[i]; var nextVertex = Vertices[i - 1]; if (vertex == nextVertex) Vertices.RemoveAt(i); } } public BoundingRectangleResult FindBestRotation() { return RotatingCalipers.MinimumBoundingRectangle(Vertices); } public BoundingRectangleResult FindBestRotation(double startAngle, double endAngle) { var hull = ConvexHull.Compute(Vertices); return RotatingCalipers.MinimumBoundingRectangle(hull, startAngle, endAngle); } public bool ContainsPoint(Vector pt) { var n = IsClosed() ? Vertices.Count - 1 : Vertices.Count; if (n < 3) return false; var inside = false; for (int i = 0, j = n - 1; i < n; j = i++) { var vi = Vertices[i]; var vj = Vertices[j]; if ((vi.Y > pt.Y) != (vj.Y > pt.Y) && pt.X < (vj.X - vi.X) * (pt.Y - vi.Y) / (vj.Y - vi.Y) + vi.X) { inside = !inside; } } return inside; } } }