/// <summary> /// Returns: /// 1 if the Polygon created by poly has no self-intersections /// 0 if one point of the polygon lies close to a line (absoluteEpsilon) /// -1 if the Polygon created by poly has a real self-intersection /// </summary> public static int HasSelfIntersections( this Polygon2d poly, double absoluteEpsilon) { var pointCount = poly.PointCount; if (absoluteEpsilon < 0.0) { throw new ArgumentOutOfRangeException(); } int i = 0; int u1 = 0; int worst = 1; V2d n0; //Triangles cannot have self-intersections if (pointCount == 3) { return(1); } Line2d line; for (i = 0; i < pointCount - 1; i++) { //line between i and i+1 line = new Line2d(poly[i], poly[i + 1]); n0 = line.Direction.Normalized; n0 = new V2d(-n0.Y, n0.X); for (int u = i + 2; u < pointCount && (u + 1) % pointCount != i; u++) { //Polygon not degenerated -> Line cannot intersect with line directly before and directly after //All lines prior to (u,u1) have already been tested with (i,i+1) u1 = (u + 1) % pointCount; if (line.IntersectsLine(poly[u], poly[u1])) { //One Point of (u,u1) lies on line (within absoluteEpsilon) if (n0.Dot(poly[u1] - line.P0).Abs() < absoluteEpsilon || n0.Dot(poly[u] - line.P0).Abs() < absoluteEpsilon) { worst = 0; } else { return(-1); } } } } return(worst); }
/// <summary> /// Returns the minimal distance between the polygon and the non- /// overlapping other supplied polygon. The minimal distance is /// always computed as the distance between a line segment and a /// point. The indices of the minimal distance configuration are /// returned in the out parameter, as the indices of points on the /// two polygons, and wether the line segement was on this or the /// other polygon. O(n). The returned index of the line segment is /// the lower point index (except in case of wraparound). /// </summary> public static double MinDistanceTo( this Polygon2d polygon, Polygon2d polygon1, out int pi0, out int pi1, out bool lineOnThis) { var p0a = polygon.m_pointArray; var p1a = polygon1.m_pointArray; var p0c = polygon.m_pointCount; var p1c = polygon1.m_pointCount; var e0a = polygon.GetEdgeArray(); var e1a = polygon1.GetEdgeArray(); int i0 = p0a.IndexOfMinY(p0c), i1 = p1a.IndexOfMaxY(p1c); V2d p0 = p0a[i0], e0 = e0a[i0], p1 = p1a[i1], e1 = e1a[i1]; int start0 = i0, start1 = i1; var dir = V2d.XAxis; var d = V2d.Distance(p0, p1); var bestValue = double.MaxValue; int bpi0 = -1, bpi1 = -1; var bLineOnThis = true; do { var s0 = Fun.Atan2(e0.Dot90(dir), e0.Dot(dir)); var s1 = Fun.Atan2(e1.Dot270(dir), e1.Dot180(dir)); if (s0 <= s1) { dir = e0a[i0]; int i0n = (i0 + 1) % p0c; var p0n = p0a[i0]; var dn = V2d.Distance(p0n, p1); var dist = DistanceToLine(p1, p0, p0n, d, dn); if (dist < bestValue) { bestValue = dist; bLineOnThis = true; bpi0 = i0; bpi1 = i1; } i0 = i0n; p0 = p0n; e0 = e0a[i0]; d = dn; } else { dir = e0a[i1].Rot180; int i1n = (i1 + 1) % p1c; var p1n = p1a[i1]; var dn = V2d.Distance(p0, p1n); var dist = DistanceToLine(p0, p1, p1n, d, dn); if (dist < bestValue) { bestValue = dist; bLineOnThis = false; bpi0 = i0; bpi1 = i1; } i1 = i1n; p1 = p1n; e1 = e1a[i1]; d = dn; } }while (i0 != start0 || i1 != start1); lineOnThis = bLineOnThis; pi0 = bpi0; pi1 = bpi1; return(bestValue); }
private static double DistanceToLine(V2d query, V2d p0, V2d p1, double d0, double d1) { var p0p1 = p1 - p0; var p0q = query - p0; var t = V2d.Dot(p0q, p0p1); if (t <= 0) { return(d0); } var denom = p0p1.LengthSquared; if (t >= denom) { return(d1); } t /= denom; return(V2d.Distance(query, p0 + t * p0p1)); }
/// <summary> /// Returns true if the Polygon contains a degenerated part /// NonDegenerated holds the Non-Degenerated part of the polygon /// </summary> public static bool PolygonHasDegeneratedPart(this Polygon2d poly, double absoluteEpsilon, out int[] NonDegenerated) { if (absoluteEpsilon < 0.0) { throw new ArgumentOutOfRangeException(); } int i = 0; int u = 1; int start = 0; V2d e0; V2d e1; bool found = false; int count = poly.PointCount; List <int> newPoints = new List <int>(); newPoints.Add(0); double l0 = 0.0; while (i < count - 1) { e0 = (poly[i + 1] - poly[i]); l0 = e0.Length; e0 = e0 / l0; u = i + 1; V2d e = poly[(u + 1) % count] - poly[u]; while ( (((e0.X * e.Y - e0.Y * e.X).Abs() < absoluteEpsilon && e.Dot(e0) > 0.0) || (poly[(u + 1) % count] - poly[i + 1]).Length < absoluteEpsilon) && u < count - 1) { if ((poly[(u + 1) % count] - poly[i + 1]).Length < absoluteEpsilon) { found = true; } u++; e0 = (poly[u] - poly[i]).Normalized; e = poly[(u + 1) % count] - poly[u]; } u--; start = u; do { u++; e1 = (poly[(u + 1) % count] - poly[u]); }while (u < count && ((e0.X * e1.Y - e0.Y * e1.X).Abs() < absoluteEpsilon && e.Dot(e0) < 0.0)); if (u != start + 1) { found = true; } newPoints.Add(u); i = u; } NonDegenerated = newPoints.ToArray(); return(found); }
// 2-Dimensional #region V2d - V2d public static bool IsOrthogonalTo(this V2d u, V2d v) => Fun.IsTiny(u.Dot(v));
/// <summary> /// Creates plane from point and normal vector. IMPORTANT: The /// supplied vector has to be normalized in order for all methods /// to work correctly, however if only relative height computations /// using the <see cref="Height"/> method are necessary, the normal /// vector need not be normalized. /// </summary> public Plane2d(V2d normalizedNormal, V2d point) { Normal = normalizedNormal; Distance = V2d.Dot(normalizedNormal, point); }
/// <summary> /// Projets the given point x perpendicular on the plane /// and returns the nearest point on the plane. /// </summary> public V2d NearestPoint(V2d x) { var p = Point; return(x - Normal.Dot(x - p) * Normal); }
/// <summary> /// The signed height of the supplied point over the plane. /// </summary> public double Height(V2d p) { return(V2d.Dot(Normal, p) - Distance); }
/// <summary> /// Gets the point on the ray that is closest to the given point. /// Ray direction must be normalized (length 1). /// </summary> public V2d GetClosestPointOnRay(V2d p) => Origin + Direction * Direction.Dot(p - Origin);
/// <summary> /// Returns the rotation of the supplied counter clockwise enumerated /// convex polygon that results in the minimum area enclosing box. /// If multiple rotations are within epsilon in their area, the one /// that is closest to an axis-aligned rotation (0, 90, 180, 270) is /// returned. O(n). /// </summary> public static M22d ComputeMinAreaEnclosingBoxRotation( this Polygon2d polygon, double epsilon = 1e-6) { polygon = polygon.WithoutMultiplePoints(epsilon); var pc = polygon.PointCount; if (pc < 2) { return(M22d.Identity); } var ea = polygon.GetEdgeArray(); ea.Apply(v => v.Normalized); int i0 = 0, i1 = 0; int i2 = 0, i3 = 0; var min = polygon[0]; var max = polygon[0]; for (int pi = 1; pi < pc; pi++) { var p = polygon[pi]; if (p.Y < min.Y) { i0 = pi; min.Y = p.Y; } else if (p.Y > max.Y) { i2 = pi; max.Y = p.Y; } if (p.X > max.X) { i1 = pi; max.X = p.X; } else if (p.X < min.X) { i3 = pi; min.X = p.X; } } V2d p0 = polygon[i0], e0 = ea[i0], p1 = polygon[i1], e1 = ea[i1]; V2d p2 = polygon[i2], e2 = ea[i2], p3 = polygon[i3], e3 = ea[i3]; int end0 = (i0 + 1) % pc, end1 = (i1 + 1) % pc; int end2 = (i2 + 1) % pc, end3 = (i3 + 1) % pc; var dir = V2d.XAxis; var best = dir; var bestArea = double.MaxValue; var bestValue = double.MaxValue; while (true) { var s0 = Fun.FastAtan2(e0.Dot90(dir), e0.Dot(dir)); var s1 = Fun.FastAtan2(e1.Dot180(dir), e1.Dot90(dir)); var s2 = Fun.FastAtan2(e2.Dot270(dir), e2.Dot180(dir)); var s3 = Fun.FastAtan2(e3.Dot(dir), e3.Dot270(dir)); int si, si01, si23; double s01, s23; if (s0 < s1) { s01 = s0; si01 = 0; } else { s01 = s1; si01 = 1; } if (s2 < s3) { s23 = s2; si23 = 2; } else { s23 = s3; si23 = 3; } if (s01 < s23) { si = si01; } else { si = si23; } if (si == 0) { dir = ea[i0]; } else if (si == 1) { dir = ea[i1].Rot270; } else if (si == 2) { dir = ea[i2].Rot180; } else { dir = ea[i3].Rot90; } double sx = (p2 - p0).Dot90(dir), sy = (p1 - p3).Dot(dir); double area = sx * sy; double value = Fun.Min(Fun.Abs(dir.X), Fun.Abs(dir.Y)); if (area < bestArea - epsilon || (area < bestArea + epsilon && value < bestValue)) { bestArea = area; bestValue = value; best = dir; } if (si == 0) { if (++i0 >= pc) { i0 -= pc; } if (i0 == end1) { break; } p0 = polygon[i0]; e0 = ea[i0]; } else if (si == 1) { if (++i1 >= pc) { i1 -= pc; } if (i1 == end2) { break; } p1 = polygon[i1]; e1 = ea[i1]; } else if (si == 2) { if (++i2 >= pc) { i2 -= pc; } if (i2 == end3) { break; } p2 = polygon[i2]; e2 = ea[i2]; } else { if (++i3 >= pc) { i3 -= pc; } if (i3 == end0) { break; } p3 = polygon[i3]; e3 = ea[i3]; } } return(new M22d(best.X, best.Y, -best.Y, best.X)); }
/// <summary> /// Returns the Line-Segments of line inside the Polygon (CCW ordered). /// Works only with Convex-Polygons /// </summary> public static Line2d ClipWithConvex(this Line2d line, Polygon2d poly) { V2d p = V2d.NaN; bool i0, i1; i0 = poly.Contains(line.P0); i1 = poly.Contains(line.P1); if (i0 && i1) { return(line); } else if ((!i0 && i1) || (i0 && !i1)) { foreach (var l in poly.EdgeLines) { if (line.Intersects(l, out p)) { break; } } if (i0) { return(new Line2d(line.P0, p)); } else { return(new Line2d(p, line.P1)); } } else { V2d p0 = V2d.NaN; V2d p1 = V2d.NaN; int c = 0; foreach (var l in poly.EdgeLines) { if (line.Intersects(l, out p)) { if (c == 0) { p0 = p; } else { p1 = p; } c++; } } if (c == 2) { V2d u = p1 - p0; if (u.Dot(line.Direction) > 0) { return(new Line2d(p0, p1)); } else { return(new Line2d(p1, p0)); } } else { return(new Line2d(V2d.NaN, V2d.NaN)); } } }
/// <summary> /// Returns the Line-Segments of line inside the Polygon (CCW ordered). /// Works with all (convex and non-convex) Polygons /// </summary> public static IEnumerable <Line2d> ClipWith(this Line2d line, Polygon2d poly) { bool i0, i1; i0 = poly.Contains(line.P0); i1 = poly.Contains(line.P1); List <V2d> resulting = new List <V2d>(); List <bool> enter = new List <bool>(); if (i0) { resulting.Add(line.P0); enter.Add(true); } if (i1) { resulting.Add(line.P1); enter.Add(false); } V2d p = V2d.NaN; V2d direction = line.Direction; foreach (var l in poly.EdgeLines) { if (line.Intersects(l, out p)) { V2d d = l.Direction; V2d n = new V2d(-d.Y, d.X); if (!p.IsNaN) { bool addflag = true; bool flag = direction.Dot(n) > 0; for (int i = 0; i < resulting.Count; i++) { if (Fun.IsTiny((resulting[i] - p).Length)) { if (flag != enter[i]) { resulting.RemoveAt(i); enter.RemoveAt(i); } addflag = false; break; } } if (addflag) { resulting.Add(p); enter.Add(flag); } } } } V2d dir = line.P1 - line.P0; resulting = (from r in resulting select r).OrderBy(x => x.Dot(dir)).ToList(); int counter = resulting.Count; List <Line2d> lines = new List <Line2d>(); for (int i = 0; i < counter - 1; i += 2) { lines.Add(new Line2d(resulting[i], resulting[i + 1])); } return(lines); }
// 2-Dimensional #region V2d - V2d public static bool IsOrthogonalTo(this V2d u, V2d v) { return(Fun.IsTiny(u.Dot(v))); }
/// <summary> /// The signed height of the supplied point over the plane. /// </summary> public double Height(V2d p) => V2d.Dot(Normal, p) - Distance;
/// <summary> /// Gets the point on the ray that is closest to the given point. /// Ray direction must be normalized (length 1). /// </summary> public V2d GetClosestPointOnRay(V2d p) { return(Origin + Direction * Direction.Dot(p - Origin)); }