/* * ****************************************************************************** * * Copyright 2015 See AUTHORS file. * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * ***************************************************************************** */ package com.uwsoft.editor.utils.poly.earclipping.bayazit; // Taken from BayazitDecomposer.cs (FarseerPhysics.Common.Decomposition.BayazitDecomposer) // at http://farseerphysics.codeplex.com import java.security.InvalidParameterException; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.Array; /// <summary> /// Convex decomposition algorithm created by Mark Bayazit (http://mnbayazit.com/) /// For more information about this algorithm, see http://mnbayazit.com/406/bayazit /// </summary> public class BayazitDecomposer { public static final float Epsilon = 1.192092896e-07f; public static int MaxPolygonVertices = 8; public static Vector2 Cross(Vector2 a, float s) { return new Vector2(s * a.y, -s * a.x); } private static Vector2 At(int i, Array<Vector2> vertices) { int s = vertices.size; return vertices.get(i < 0 ? s - (-i % s) : i % s); } private static Array<Vector2> Copy(int i, int j, Array<Vector2> vertices) { Array<Vector2> p = new Array<Vector2>(); while (j < i) j += vertices.size; // p.reserve(j - i + 1); for (; i <= j; ++i) { p.add(At(i, vertices)); } return p; } public static float GetSignedArea(Array<Vector2> vect) { int i; float area = 0; for (i = 0; i < vect.size; i++) { int j = (i + 1) % vect.size; area += vect.get(i).x * vect.get(j).y; area -= vect.get(i).y * vect.get(j).x; } area /= 2.0f; return area; } public static float GetSignedArea(Vector2[] vect) { int i; float area = 0; for (i = 0; i < vect.length; i++) { int j = (i + 1) % vect.length; area += vect[i].x * vect[j].y; area -= vect[i].y * vect[j].x; } area /= 2.0f; return area; } public static Boolean IsCounterClockWise(Array<Vector2> vect) { // We just return true for lines if (vect.size < 3) return true; return (GetSignedArea(vect) > 0.0f); } public static Boolean IsCounterClockWise(Vector2[] vect) { // We just return true for lines if (vect.length < 3) return true; return (GetSignedArea(vect) > 0.0f); } // / <summary> // / Decompose the polygon into several smaller non-concave polygon. // / If the polygon is already convex, it will return the original polygon, // unless it is over Settings.MaxPolygonVertices. // / Precondition: Counter Clockwise polygon // / </summary> // / <param name="vertices"></param> // / <returns></returns> public static Array<Array<Vector2>> ConvexPartition(Array<Vector2> vertices) { // We force it to CCW as it is a precondition in this algorithm. // vertices.ForceCounterClockWise(); if (!IsCounterClockWise(vertices)) { // Collections.reverse(vertices); vertices.reverse(); // Array<Vector2> reversed = new Array<Vector2>(vertices.size); // for (int i = vertices.size - 1; i <= 0; i--) { // reversed.add(vertices.get(i)); // } // vertices = reversed; } Array<Array<Vector2>> list = new Array<Array<Vector2>>(); float d, lowerDist, upperDist; Vector2 p; Vector2 lowerInt = new Vector2(); Vector2 upperInt = new Vector2(); // intersection points int lowerIndex = 0, upperIndex = 0; Array<Vector2> lowerPoly, upperPoly; for (int i = 0; i < vertices.size; ++i) { if (Reflex(i, vertices)) { lowerDist = upperDist = Float.MAX_VALUE; // std::numeric_limits<qreal>::max(); for (int j = 0; j < vertices.size; ++j) { // if line intersects with an edge if (Left(At(i - 1, vertices), At(i, vertices), At(j, vertices)) && RightOn(At(i - 1, vertices), At(i, vertices), At(j - 1, vertices))) { // find the point of intersection p = LineIntersect(At(i - 1, vertices), At(i, vertices), At(j, vertices), At(j - 1, vertices)); if (Right(At(i + 1, vertices), At(i, vertices), p)) { // make sure it's inside the poly d = SquareDist(At(i, vertices), p); if (d < lowerDist) { // keep only the closest intersection lowerDist = d; lowerInt = p; lowerIndex = j; } } } if (Left(At(i + 1, vertices), At(i, vertices), At(j + 1, vertices)) && RightOn(At(i + 1, vertices), At(i, vertices), At(j, vertices))) { p = LineIntersect(At(i + 1, vertices), At(i, vertices), At(j, vertices), At(j + 1, vertices)); if (Left(At(i - 1, vertices), At(i, vertices), p)) { d = SquareDist(At(i, vertices), p); if (d < upperDist) { upperDist = d; upperIndex = j; upperInt = p; } } } } // if there are no vertices to connect to, choose a point in the // middle if (lowerIndex == (upperIndex + 1) % vertices.size) { Vector2 sp = new Vector2((lowerInt.x + upperInt.x) / 2, (lowerInt.y + upperInt.y) / 2); lowerPoly = Copy(i, upperIndex, vertices); lowerPoly.add(sp); upperPoly = Copy(lowerIndex, i, vertices); upperPoly.add(sp); } else { double highestScore = 0, bestIndex = lowerIndex; while (upperIndex < lowerIndex) upperIndex += vertices.size; for (int j = lowerIndex; j <= upperIndex; ++j) { if (CanSee(i, j, vertices)) { double score = 1 / (SquareDist(At(i, vertices), At(j, vertices)) + 1); if (Reflex(j, vertices)) { if (RightOn(At(j - 1, vertices), At(j, vertices), At(i, vertices)) && LeftOn(At(j + 1, vertices), At(j, vertices), At(i, vertices))) { score += 3; } else { score += 2; } } else { score += 1; } if (score > highestScore) { bestIndex = j; highestScore = score; } } } lowerPoly = Copy(i, (int) bestIndex, vertices); upperPoly = Copy((int) bestIndex, i, vertices); } list.addAll(ConvexPartition(lowerPoly)); list.addAll(ConvexPartition(upperPoly)); return list; } } // polygon is already convex if (vertices.size > MaxPolygonVertices) { lowerPoly = Copy(0, vertices.size / 2, vertices); upperPoly = Copy(vertices.size / 2, 0, vertices); list.addAll(ConvexPartition(lowerPoly)); list.addAll(ConvexPartition(upperPoly)); } else list.add(vertices); // The polygons are not guaranteed to be with collinear points. We // remove // them to be sure. for (int i = 0; i < list.size; i++) { list.set(i, SimplifyTools.CollinearSimplify(list.get(i), 0)); } // Remove empty vertice collections for (int i = list.size - 1; i >= 0; i--) { if (list.get(i).size == 0) list.removeIndex(i); } return list; } private static Boolean CanSee(int i, int j, Array<Vector2> vertices) { if (Reflex(i, vertices)) { if (LeftOn(At(i, vertices), At(i - 1, vertices), At(j, vertices)) && RightOn(At(i, vertices), At(i + 1, vertices), At(j, vertices))) return false; } else { if (RightOn(At(i, vertices), At(i + 1, vertices), At(j, vertices)) || LeftOn(At(i, vertices), At(i - 1, vertices), At(j, vertices))) return false; } if (Reflex(j, vertices)) { if (LeftOn(At(j, vertices), At(j - 1, vertices), At(i, vertices)) && RightOn(At(j, vertices), At(j + 1, vertices), At(i, vertices))) return false; } else { if (RightOn(At(j, vertices), At(j + 1, vertices), At(i, vertices)) || LeftOn(At(j, vertices), At(j - 1, vertices), At(i, vertices))) return false; } for (int k = 0; k < vertices.size; ++k) { if ((k + 1) % vertices.size == i || k == i || (k + 1) % vertices.size == j || k == j) { continue; // ignore incident edges } Vector2 intersectionPoint = new Vector2(); if (LineIntersect(At(i, vertices), At(j, vertices), At(k, vertices), At(k + 1, vertices), true, true, intersectionPoint)) { return false; } } return true; } public static Vector2 LineIntersect(Vector2 p1, Vector2 p2, Vector2 q1, Vector2 q2) { Vector2 i = new Vector2(); float a1 = p2.y - p1.y; float b1 = p1.x - p2.x; float c1 = a1 * p1.x + b1 * p1.y; float a2 = q2.y - q1.y; float b2 = q1.x - q2.x; float c2 = a2 * q1.x + b2 * q1.y; float det = a1 * b2 - a2 * b1; if (!FloatEquals(det, 0)) { // lines are not parallel i.x = (b2 * c1 - b1 * c2) / det; i.y = (a1 * c2 - a2 * c1) / det; } return i; } public static Boolean FloatEquals(float value1, float value2) { return Math.abs(value1 - value2) <= Epsilon; } // / <summary> // / This method detects if two line segments (or lines) intersect, // / and, if so, the point of intersection. Use the // <paramname="firstIsSegment"/> and // / <paramname="secondIsSegment"/> parameters to set whether the // intersection point // / must be on the first and second line segments. Setting these // / both to true means you are doing a line-segment to line-segment // / intersection. Setting one of them to true means you are doing a // / line to line-segment intersection test, and so on. // / Note: If two line segments are coincident, then // / no intersection is detected (there are actually // / infinite intersection points). // / Author: Jeremy Bell // / </summary> // / <param name="point1">The first point of the first line segment.</param> // / <param name="point2">The second point of the first line // segment.</param> // / <param name="point3">The first point of the second line // segment.</param> // / <param name="point4">The second point of the second line // segment.</param> // / <param name="point">This is set to the intersection // / point if an intersection is detected.</param> // / <param name="firstIsSegment">Set this to true to require that the // / intersection point be on the first line segment.</param> // / <param name="secondIsSegment">Set this to true to require that the // / intersection point be on the second line segment.</param> // / <returns>True if an intersection is detected, false // otherwise.</returns> public static Boolean LineIntersect(Vector2 point1, Vector2 point2, Vector2 point3, Vector2 point4, Boolean firstIsSegment, Boolean secondIsSegment, Vector2 point) { point = new Vector2(); // these are reused later. // each lettered sub-calculation is used twice, except // for b and d, which are used 3 times float a = point4.y - point3.y; float b = point2.x - point1.x; float c = point4.x - point3.x; float d = point2.y - point1.y; // denominator to solution of linear system float denom = (a * b) - (c * d); // if denominator is 0, then lines are parallel if (!(denom >= -Epsilon && denom <= Epsilon)) { float e = point1.y - point3.y; float f = point1.x - point3.x; float oneOverDenom = 1.0f / denom; // numerator of first equation float ua = (c * e) - (a * f); ua *= oneOverDenom; // check if intersection point of the two lines is on line segment 1 if (!firstIsSegment || ua >= 0.0f && ua <= 1.0f) { // numerator of second equation float ub = (b * e) - (d * f); ub *= oneOverDenom; // check if intersection point of the two lines is on line // segment 2 // means the line segments intersect, since we know it is on // segment 1 as well. if (!secondIsSegment || ub >= 0.0f && ub <= 1.0f) { // check if they are coincident (no collision in this case) if (ua != 0f || ub != 0f) { // There is an intersection point.x = point1.x + ua * b; point.y = point1.y + ua * d; return true; } } } } return false; } // precondition: ccw private static Boolean Reflex(int i, Array<Vector2> vertices) { return Right(i, vertices); } private static Boolean Right(int i, Array<Vector2> vertices) { return Right(At(i - 1, vertices), At(i, vertices), At(i + 1, vertices)); } private static Boolean Left(Vector2 a, Vector2 b, Vector2 c) { return Area(a, b, c) > 0; } private static Boolean LeftOn(Vector2 a, Vector2 b, Vector2 c) { return Area(a, b, c) >= 0; } private static Boolean Right(Vector2 a, Vector2 b, Vector2 c) { return Area(a, b, c) < 0; } private static Boolean RightOn(Vector2 a, Vector2 b, Vector2 c) { return Area(a, b, c) <= 0; } public static float Area(Vector2 a, Vector2 b, Vector2 c) { return a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y); } private static float SquareDist(Vector2 a, Vector2 b) { float dx = b.x - a.x; float dy = b.y - a.y; return dx * dx + dy * dy; } } class SimplifyTools { private static Boolean[] _usePt; private static double _distanceTolerance; // / <summary> // / Removes all collinear points on the polygon. // / </summary> // / <param name="vertices">The polygon that needs simplification.</param> // / <param name="collinearityTolerance">The collinearity tolerance.</param> // / <returns>A simplified polygon.</returns> public static Array<Vector2> CollinearSimplify(Array<Vector2> vertices, float collinearityTolerance) { // We can't simplify polygons under 3 vertices if (vertices.size < 3) return vertices; Array<Vector2> simplified = new Array<Vector2>(); for (int i = 0; i < vertices.size; i++) { int prevId = i - 1; if (prevId < 0) prevId = vertices.size - 1; int nextId = i + 1; if (nextId >= vertices.size) nextId = 0; Vector2 prev = vertices.get(prevId); Vector2 current = vertices.get(i); Vector2 next = vertices.get(nextId); // If they collinear, continue if (Collinear(prev, current, next, collinearityTolerance)) continue; simplified.add(current); } return simplified; } public static Boolean Collinear(Vector2 a, Vector2 b, Vector2 c, float tolerance) { return FloatInRange(BayazitDecomposer.Area(a, b, c), -tolerance, tolerance); } public static Boolean FloatInRange(float value, float min, float max) { return (value >= min && value <= max); } // / <summary> // / Removes all collinear points on the polygon. // / Has a default bias of 0 // / </summary> // / <param name="vertices">The polygon that needs simplification.</param> // / <returns>A simplified polygon.</returns> public static Array<Vector2> CollinearSimplify(Array<Vector2> vertices) { return CollinearSimplify(vertices, 0); } // / <summary> // / Ramer-Douglas-Peucker polygon simplification algorithm. This is the // general recursive version that does not use the // / speed-up technique by using the Melkman convex hull. // / // / If you pass in 0, it will remove all collinear points // / </summary> // / <returns>The simplified polygon</returns> public static Array<Vector2> DouglasPeuckerSimplify( Array<Vector2> vertices, float distanceTolerance) { _distanceTolerance = distanceTolerance; _usePt = new Boolean[vertices.size]; for (int i = 0; i < vertices.size; i++) _usePt[i] = true; SimplifySection(vertices, 0, vertices.size - 1); Array<Vector2> result = new Array<Vector2>(); for (int i = 0; i < vertices.size; i++) if (_usePt[i]) result.add(vertices.get(i)); return result; } private static void SimplifySection(Array<Vector2> vertices, int i, int j) { if ((i + 1) == j) return; Vector2 A = vertices.get(i); Vector2 B = vertices.get(j); double maxDistance = -1.0; int maxIndex = i; for (int k = i + 1; k < j; k++) { double distance = DistancePointLine(vertices.get(k), A, B); if (distance > maxDistance) { maxDistance = distance; maxIndex = k; } } if (maxDistance <= _distanceTolerance) for (int k = i + 1; k < j; k++) _usePt[k] = false; else { SimplifySection(vertices, i, maxIndex); SimplifySection(vertices, maxIndex, j); } } private static double DistancePointPoint(Vector2 p, Vector2 p2) { double dx = p.x - p2.x; double dy = p.y - p2.x; return Math.sqrt(dx * dx + dy * dy); } private static double DistancePointLine(Vector2 p, Vector2 A, Vector2 B) { // if start == end, then use point-to-point distance if (A.x == B.x && A.y == B.y) return DistancePointPoint(p, A); // otherwise use comp.graphics.algorithms Frequently Asked Questions // method /* * (1) AC dot AB r = --------- ||AB||^2 r has the following meaning: r=0 * Point = A r=1 Point = B r<0 Point is on the backward extension of AB * r>1 Point is on the forward extension of AB 0<r<1 Point is interior * to AB */ double r = ((p.x - A.x) * (B.x - A.x) + (p.y - A.y) * (B.y - A.y)) / ((B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y)); if (r <= 0.0) return DistancePointPoint(p, A); if (r >= 1.0) return DistancePointPoint(p, B); /* * (2) (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay) s = ----------------------------- * Curve^2 Then the distance from C to Point = |s|*Curve. */ double s = ((A.y - p.y) * (B.x - A.x) - (A.x - p.x) * (B.y - A.y)) / ((B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y)); return Math.abs(s) * Math.sqrt(((B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y))); } // From physics2d.net public static Array<Vector2> ReduceByArea(Array<Vector2> vertices, float areaTolerance) { if (vertices.size <= 3) return vertices; if (areaTolerance < 0) { throw new InvalidParameterException( "areaTolerance: must be equal to or greater then zero."); } Array<Vector2> result = new Array<Vector2>(); Vector2 v1, v2, v3; float old1, old2, new1; v1 = vertices.get(vertices.size - 2); v2 = vertices.get(vertices.size - 1); areaTolerance *= 2; for (int index = 0; index < vertices.size; ++index, v2 = v3) { if (index == vertices.size - 1) { if (result.size == 0) { throw new InvalidParameterException( "areaTolerance: The tolerance is too high!"); } v3 = result.get(0); } else { v3 = vertices.get(index); } old1 = Cross(v1, v2); old2 = Cross(v2, v3); new1 = Cross(v1, v3); if (Math.abs(new1 - (old1 + old2)) > areaTolerance) { result.add(v2); v1 = v2; } } return result; } public static Float Cross(Vector2 a, Vector2 b) { return a.x * b.y - a.y * b.x; } // From Eric Jordan's convex decomposition library // / <summary> // / Merges all parallel edges in the list of vertices // / </summary> // / <param name="vertices">The vertices.</param> // / <param name="tolerance">The tolerance.</param> public static void MergeParallelEdges(Array<Vector2> vertices, float tolerance) { if (vertices.size <= 3) return; // Can't do anything useful here to a triangle Boolean[] mergeMe = new Boolean[vertices.size]; int newNVertices = vertices.size; // Gather points to process for (int i = 0; i < vertices.size; ++i) { int lower = (i == 0) ? (vertices.size - 1) : (i - 1); int middle = i; int upper = (i == vertices.size - 1) ? (0) : (i + 1); float dx0 = vertices.get(middle).x - vertices.get(lower).x; float dy0 = vertices.get(middle).y - vertices.get(lower).y; float dx1 = vertices.get(upper).y - vertices.get(middle).x; float dy1 = vertices.get(upper).y - vertices.get(middle).y; float norm0 = (float) Math.sqrt(dx0 * dx0 + dy0 * dy0); float norm1 = (float) Math.sqrt(dx1 * dx1 + dy1 * dy1); if (!(norm0 > 0.0f && norm1 > 0.0f) && newNVertices > 3) { // Merge identical points mergeMe[i] = true; --newNVertices; } dx0 /= norm0; dy0 /= norm0; dx1 /= norm1; dy1 /= norm1; float cross = dx0 * dy1 - dx1 * dy0; float dot = dx0 * dx1 + dy0 * dy1; if (Math.abs(cross) < tolerance && dot > 0 && newNVertices > 3) { mergeMe[i] = true; --newNVertices; } else mergeMe[i] = false; } if (newNVertices == vertices.size || newNVertices == 0) return; int currIndex = 0; // Copy the vertices to a new list and clear the old Array<Vector2> oldVertices = new Array<Vector2>(vertices); vertices.clear(); for (int i = 0; i < oldVertices.size; ++i) { if (mergeMe[i] || newNVertices == 0 || currIndex == newNVertices) continue; // Debug.Assert(currIndex < newNVertices); vertices.add(oldVertices.get(i)); ++currIndex; } } // Misc // / <summary> // / Merges the identical points in the polygon. // / </summary> // / <param name="vertices">The vertices.</param> // / <returns></returns> public static Array<Vector2> MergeIdenticalPoints(Array<Vector2> vertices) { Array<Vector2> results = new Array<Vector2>(); for (int i = 0; i < vertices.size; i++) { Vector2 vOriginal = vertices.get(i); boolean alreadyExists = false; for (int j = 0; j < results.size; j++) { Vector2 v = results.get(j); if (vOriginal.equals(v)) { alreadyExists = true; break; } } if (!alreadyExists) results.add(vertices.get(i)); } return results; } // / <summary> // / Reduces the polygon by distance. // / </summary> // / <param name="vertices">The vertices.</param> // / <param name="distance">The distance between points. Points closer than // this will be 'joined'.</param> // / <returns></returns> public static Array<Vector2> ReduceByDistance(Array<Vector2> vertices, float distance) { // We can't simplify polygons under 3 vertices if (vertices.size < 3) return vertices; Array<Vector2> simplified = new Array<Vector2>(); for (int i = 0; i < vertices.size; i++) { Vector2 current = vertices.get(i); int ii = i + 1; if (ii >= vertices.size) ii = 0; Vector2 next = vertices.get(ii); Vector2 diff = new Vector2(next.x - current.x, next.y - current.y); // If they are closer than the distance, continue if (diff.len2() <= distance) continue; simplified.add(current); } return simplified; } // / <summary> // / Reduces the polygon by removing the Nth vertex in the vertices list. // / </summary> // / <param name="vertices">The vertices.</param> // / <param name="nth">The Nth point to remove. Example: 5.</param> // / <returns></returns> public static Array<Vector2> ReduceByNth(Array<Vector2> vertices, int nth) { // We can't simplify polygons under 3 vertices if (vertices.size < 3) return vertices; if (nth == 0) return vertices; Array<Vector2> result = new Array<Vector2>(vertices.size); for (int i = 0; i < vertices.size; i++) { if (i % nth == 0) continue; result.add(vertices.get(i)); } return result; } }