All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.dermetfan.gdx.math.BayazitDecomposer Maven / Gradle / Ivy

The newest version!
package net.dermetfan.gdx.math;

import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;

import static net.dermetfan.gdx.math.GeometryUtils.areVerticesClockwise;

/** Taken from Aurelien Ribon's Physics Body Editor
*
* Convex decomposition algorithm created by Mark Bayazit * more information about this algorithm */ public abstract 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 vertices) { return vertices.get(i < 0 ? vertices.size - -i % vertices.size : i % vertices.size); } private static Array copy(int i, int j, Array vertices) { Array p = new Array<>(); while(j < i) j += vertices.size; // p.reserve(j - i + 1); for(; i <= j; ++i) p.add(at(i, vertices)); return p; } /** 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 */ public static Array> convexPartition(Array vertices) { // We force it to CCW as it is a precondition in this algorithm. // vertices.ForceCounterClockWise(); if(areVerticesClockwise(vertices)) vertices.reverse(); Array> list = new Array<>(); float d, lowerDist, upperDist; Vector2 p; Vector2 lowerInt = new Vector2(); Vector2 upperInt = new Vector2(); // intersection points int lowerIndex = 0, upperIndex = 0; Array lowerPoly, upperPoly; for(int i = 0; i < vertices.size; ++i) if(reflex(i, vertices)) { lowerDist = upperDist = Float.MAX_VALUE; // std::numeric_limits::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 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; } /** This method detects if two line segments (or lines) intersect, * and, if so, the point of intersection. Use the firstIsSegment and * 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). * * @param point1 The first point of the first line segment. * @param point2 The second point of the first line segment. * @param point3 The first point of the second line segment. * @param point4 The second point of the second line segment. * @param firstIsSegment Set this to true to require that the intersection point be on the first line segment. * @param secondIsSegment Set this to true to require that the intersection point be on the second line segment. * @param point This is set to the intersection point if an intersection is detected. * @return True if an intersection is detected, false otherwise. * * Author: Jeremy Bell */ 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 vertices) { return right(i, vertices); } private static boolean right(int i, Array 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; } public static class SimplifyTools { private static boolean[] usePt; private static double distanceTolerance; /** Removes all collinear points on the polygon. * @param vertices The polygon that needs simplification. * @param collinearityTolerance The collinearity tolerance. * @return A simplified polygon. */ public static Array collinearSimplify(Array vertices, float collinearityTolerance) { // We can't simplify polygons under 3 vertices if(vertices.size < 3) return vertices; Array simplified = new Array<>(); 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; } /** Removes all collinear points on the polygon. * Has a default bias of 0 * @param vertices The polygon that needs simplification * @return A simplified polygon. */ public static Array collinearSimplify(Array vertices) { return collinearSimplify(vertices, 0); } /** 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 * @param vertices * @param distanceTolerance * @return The simplified polygon */ public static Array douglasPeuckerSimplify(Array vertices, float distanceTolerance) { SimplifyTools.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 result = new Array<>(); for(int i = 0; i < vertices.size; i++) if(usePt[i]) result.add(vertices.get(i)); return result; } private static void simplifySection(Array 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= 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 reduceByArea(Array vertices, float areaTolerance) { if(vertices.size <= 3) return vertices; if(areaTolerance < 0) throw new IllegalArgumentException("areaTolerance: must be equal to or greater then zero."); Array result = new Array<>(); 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 IllegalArgumentException("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 * Merges all parallel edges in the list of vertices */ public static void mergeParallelEdges(Array 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 oldVertices = new Array<>(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; } } /** Merges the identical points in the polygon. */ public static Array mergeIdenticalPoints(Array vertices) { Array results = new Array<>(); 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; } /** Reduces the polygon by distance. * @param vertices * @param distance The distance between points. Points closer than this will be 'joined' */ public static Array reduceByDistance(Array vertices, float distance) { // We can't simplify polygons under 3 vertices if(vertices.size < 3) return vertices; Array simplified = new Array<>(); 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; } /** Reduces the polygon by removing the Nth vertex in the vertices list. * @param vertices * @param nth The Nth point to remove. Example: 5. */ public static Array reduceByNth(Array vertices, int nth) { // We can't simplify polygons under 3 vertices if(vertices.size < 3) return vertices; if(nth == 0) return vertices; Array result = new Array<>(vertices.size); for(int i = 0; i < vertices.size; i++) { if(i % nth == 0) continue; result.add(vertices.get(i)); } return result; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy