using System; using System.Collections.Generic; using System.Linq; 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; } } 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 double FindBestRotation(double stepAngle) { var entities = new List(ToLines()); return entities.FindBestRotation(stepAngle); } public double FindBestRotation(double stepAngle, double startAngle, double endAngle) { var entities = new List(ToLines()); return entities.FindBestRotation(stepAngle, startAngle, endAngle); } } }