First integration of Spine's BoundingBoxAttachment with Box2D

This commit is contained in:
fgnm
2023-10-29 19:50:21 +01:00
parent d22c55f1a2
commit c7a2b735fe
15 changed files with 21 additions and 1283 deletions
+1
View File
@@ -13,6 +13,7 @@
**Runtime**
- [BREAK CHANGE] `TextureArrayCpuPolygonSpriteBatch` is now the default batch implementation
- [BREAK CHANGE] New `MainItemComponent#uniqueId` management, change from integer to a random string
- First integration of Spine's BoundingBoxAttachment with Box2D for skeletal collision detection
- Chained Entities and Spine' SkeletonRenderSeparator
- Improved PhysicsActions. Add `PhysicsActions#transformTo` and `PhysicsActions#transformBy`
- Fixed physic's step
@@ -6,6 +6,7 @@ import com.badlogic.gdx.utils.IntSet;
import games.rednblack.editor.controller.SandboxCommand;
import games.rednblack.editor.controller.commands.component.UpdatePolygonVerticesCommand;
import games.rednblack.editor.renderer.components.shape.PolygonShapeComponent;
import games.rednblack.editor.renderer.utils.poly.PolygonRuntimeUtils;
import games.rednblack.editor.utils.poly.PolygonUtils;
import games.rednblack.editor.utils.runtime.SandboxComponentRetriever;
import games.rednblack.editor.view.ui.followers.PolygonFollower;
@@ -50,10 +51,10 @@ public class ChangePolygonVertexPositionCommand extends SandboxCommand {
Vector2[] pointsArray = points.toArray();
IntSet intersections = PolygonUtils.checkForIntersection(anchor, points, intersectionProblems);
if(intersections == null) {
if(PolygonUtils.isPolygonCCW(pointsArray)){
if(PolygonRuntimeUtils.isPolygonCCW(pointsArray)){
points.reverse();
}
polygonShapeComponent.polygonizedVertices = PolygonUtils.polygonize(polygonShapeComponent.vertices.toArray());
polygonShapeComponent.polygonizedVertices = PolygonRuntimeUtils.polygonize(polygonShapeComponent.vertices.toArray());
UpdatePolygonVerticesCommand.payload(currentCommandPayload, polygonShapeComponent.vertices, polygonShapeComponent.polygonizedVertices);
Facade.getInstance().sendNotification(MsgAPI.ACTION_UPDATE_MESH_DATA, currentCommandPayload);
@@ -6,7 +6,7 @@ import com.kotcrab.vis.ui.util.dialog.Dialogs;
import games.rednblack.editor.controller.SandboxCommand;
import games.rednblack.editor.controller.commands.component.UpdatePolygonVerticesCommand;
import games.rednblack.editor.renderer.components.shape.PolygonShapeComponent;
import games.rednblack.editor.utils.poly.PolygonUtils;
import games.rednblack.editor.renderer.utils.poly.PolygonRuntimeUtils;
import games.rednblack.editor.utils.runtime.SandboxComponentRetriever;
import games.rednblack.editor.view.ui.followers.PolygonFollower;
import games.rednblack.h2d.common.MsgAPI;
@@ -47,7 +47,7 @@ public class DeletePolygonVertexCommand extends SandboxCommand {
polygonShapeComponent.vertices.removeIndex(anchor);
follower.setSelectedAnchor(0);
polygonShapeComponent.polygonizedVertices = PolygonUtils.polygonize(polygonShapeComponent.vertices.toArray());
polygonShapeComponent.polygonizedVertices = PolygonRuntimeUtils.polygonize(polygonShapeComponent.vertices.toArray());
if(polygonShapeComponent.polygonizedVertices == null) {
// restore from backup
@@ -3,7 +3,7 @@ package games.rednblack.editor.controller.commands;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
import games.rednblack.editor.renderer.components.shape.PolygonShapeComponent;
import games.rednblack.editor.utils.poly.PolygonUtils;
import games.rednblack.editor.renderer.utils.poly.PolygonRuntimeUtils;
import games.rednblack.editor.utils.runtime.EntityUtils;
import games.rednblack.editor.utils.runtime.SandboxComponentRetriever;
import games.rednblack.h2d.common.MsgAPI;
@@ -49,10 +49,10 @@ public class UpdatePolygonDataCommand extends EntityModifyRevertibleCommand {
private void checkPolygon(PolygonShapeComponent polygonShapeComponent) {
if (!polygonShapeComponent.openEnded) {
Array<Vector2> points = polygonShapeComponent.vertices;
if(PolygonUtils.isPolygonCCW(points.toArray())){
if(PolygonRuntimeUtils.isPolygonCCW(points.toArray())){
points.reverse();
}
polygonShapeComponent.polygonizedVertices = PolygonUtils.polygonize(points.toArray());
polygonShapeComponent.polygonizedVertices = PolygonRuntimeUtils.polygonize(points.toArray());
}
}
}
@@ -5,6 +5,7 @@ import com.badlogic.gdx.utils.Array;
import games.rednblack.editor.controller.commands.EntityModifyRevertibleCommand;
import games.rednblack.editor.renderer.components.TextureRegionComponent;
import games.rednblack.editor.renderer.components.shape.PolygonShapeComponent;
import games.rednblack.editor.renderer.utils.poly.PolygonRuntimeUtils;
import games.rednblack.editor.utils.runtime.EntityUtils;
import games.rednblack.editor.utils.runtime.SandboxComponentRetriever;
import games.rednblack.h2d.common.MsgAPI;
@@ -88,11 +89,7 @@ public class UpdatePolygonVerticesCommand extends EntityModifyRevertibleCommand
}
public static Array<Vector2> cloneData(Array<Vector2> data) {
Array<Vector2> clone = new Array<>(true, data.size, Vector2.class);
for (Vector2 vector2 : data) {
clone.add(vector2.cpy());
}
return clone;
return PolygonRuntimeUtils.cloneData(data);
}
public static Vector2[][] cloneData(Vector2[][] data) {
Vector2[][] newData = new Vector2[data.length][];
@@ -1,105 +0,0 @@
/*
* ******************************************************************************
* * 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 games.rednblack.editor.utils.poly;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
import games.rednblack.editor.utils.poly.earclipping.bayazit.BayazitDecomposer;
import games.rednblack.editor.utils.poly.earclipping.ewjordan.EwjordanDecomposer;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
*
* @author Aurelien Ribon | http://www.aurelienribon.com/
*/
public class Clipper {
public enum Polygonizer {EWJORDAN, BAYAZIT}
public static Vector2[][] polygonize(Polygonizer polygonizer, Vector2[] points) {
Vector2[][] polygons = null;
if (PolygonUtils.isPolygonCCW(points)) {
List<Vector2> vertices = Arrays.asList(points);
Collections.reverse(vertices);
points = vertices.toArray(new Vector2[0]);
}
switch (polygonizer) {
case EWJORDAN:
polygons = EwjordanDecomposer.decompose(points);
break;
case BAYAZIT:
Array<Vector2> tmpPoints = new Array<Vector2>(points.length);
tmpPoints.addAll(points);
Array<Array<Vector2>> tmpPolygons;
try {
tmpPolygons = BayazitDecomposer.ConvexPartition(tmpPoints);
} catch (Exception ex) {
tmpPolygons = null;
}
if (tmpPolygons != null) {
polygons = new Vector2[tmpPolygons.size][];
for (int i = 0; i < tmpPolygons.size; i++) {
polygons[i] = new Vector2[tmpPolygons.get(i).size];
for (int ii = 0; ii < tmpPolygons.get(i).size; ii++)
polygons[i][ii] = new Vector2(tmpPolygons.get(i).get(ii));
}
}
break;
}
if (polygons != null) polygons = sliceForMax8Vertices(polygons);
return polygons;
}
private static Vector2[][] sliceForMax8Vertices(Vector2[][] polygons) {
for (int i = 0; i < polygons.length; i++) {
Vector2[] poly = polygons[i];
if (poly.length > 8) {
int limit = poly.length < 15 ? poly.length / 2 + 1 : 8;
Vector2[] newPoly1 = new Vector2[limit];
Vector2[] newPoly2 = new Vector2[poly.length - limit + 2];
System.arraycopy(poly, 0, newPoly1, 0, limit);
System.arraycopy(poly, limit - 1, newPoly2, 0, poly.length - limit + 1);
newPoly2[newPoly2.length - 1] = poly[0].cpy();
Vector2[][] newPolys = new Vector2[polygons.length + 1][];
if (i > 0) {
System.arraycopy(polygons, 0, newPolys, 0, i);
}
if (i < polygons.length - 1) {
System.arraycopy(polygons, i + 1, newPolys, i + 2, polygons.length - i - 1);
}
newPolys[i] = newPoly1;
newPolys[i + 1] = newPoly2;
polygons = newPolys;
i -= 1;
}
}
return polygons;
}
}
@@ -24,37 +24,11 @@ import com.badlogic.gdx.utils.*;
import games.rednblack.editor.utils.Vector2Pool;
import games.rednblack.editor.view.stage.Sandbox;
import java.util.HashSet;
/**
*
* @author Aurelien Ribon | http://www.aurelienribon.com/
*/
public class PolygonUtils {
public static float getPolygonSignedArea(Vector2[] points) {
if (points.length < 3)
return 0;
float sum = 0;
for (int i = 0; i < points.length; i++) {
Vector2 p1 = points[i];
Vector2 p2 = i != points.length-1 ? points[i+1] : points[0];
sum += (p1.x * p2.y) - (p1.y * p2.x);
}
return 0.5f * sum;
}
public static float getPolygonArea(Vector2[] points) {
return Math.abs(getPolygonSignedArea(points));
}
public static boolean isPolygonCCW(Vector2[] points) {
return getPolygonSignedArea(points) > 0;
}
public static Vector2[][] polygonize(Vector2[] vertices) {
return Clipper.polygonize(Clipper.Polygonizer.EWJORDAN, vertices);
}
public static boolean intersectSegments(Array<Vector2> points, int index1, int index2, int index3, int index4) {
Vector2 intersectionPoint = Pools.obtain(Vector2.class).set(points.get(index1));
@@ -1,701 +0,0 @@
/*
* ******************************************************************************
* * 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 games.rednblack.editor.utils.poly.earclipping.bayazit;
// Taken from BayazitDecomposer.cs (FarseerPhysics.Common.Decomposition.BayazitDecomposer)
// at http://farseerphysics.codeplex.com
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
import games.rednblack.editor.controller.commands.component.UpdatePolygonVerticesCommand;
import java.security.InvalidParameterException;
/// <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 = UpdatePolygonVerticesCommand.cloneData(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;
}
}
@@ -1,206 +0,0 @@
/*
* ******************************************************************************
* * 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 games.rednblack.editor.utils.poly.earclipping.ewjordan;
import com.badlogic.gdx.math.Vector2;
/**
* Original code from EwJordan (http://www.ewjordan.com/earClip/)
*/
public class EwjordanDecomposer {
public static Vector2[][] decompose(Vector2[] points) {
int vNum = points.length;
float[] xv = new float[vNum];
float[] yv = new float[vNum];
for (int i = 0; i < vNum; i++) {
xv[i] = points[i].x;
yv[i] = points[i].y;
}
Triangle[] tempTriangles = triangulatePolygon(xv, yv, vNum);
Polygon[] tempPolygons = polygonizeTriangles(tempTriangles);
if (tempPolygons == null)
return null;
Vector2[][] polygons = new Vector2[tempPolygons.length][];
for (int i = 0; i < tempPolygons.length; i++) {
polygons[i] = new Vector2[tempPolygons[i].nVertices];
for (int ii = 0; ii < tempPolygons[i].nVertices; ii++)
polygons[i][ii] = new Vector2(tempPolygons[i].x[ii], tempPolygons[i].y[ii]);
}
return polygons;
}
// -------------------------------------------------------------------------
private static Triangle[] triangulatePolygon(float[] xv, float[] yv, int vNum) {
if (vNum < 3)
return null;
Triangle[] buffer = new Triangle[vNum];
int bufferSize = 0;
float[] xrem = new float[vNum];
float[] yrem = new float[vNum];
for (int i = 0; i < vNum; ++i) {
xrem[i] = xv[i];
yrem[i] = yv[i];
}
while (vNum > 3) {
int earIndex = -1;
for (int i = 0; i < vNum; ++i) {
if (isEar(i, xrem, yrem)) {
earIndex = i;
break;
}
}
if (earIndex == -1)
return null;
--vNum;
float[] newx = new float[vNum];
float[] newy = new float[vNum];
int currDest = 0;
for (int i = 0; i < vNum; ++i) {
if (currDest == earIndex) {
++currDest;
}
newx[i] = xrem[currDest];
newy[i] = yrem[currDest];
++currDest;
}
int under = (earIndex == 0) ? (xrem.length - 1) : (earIndex - 1);
int over = (earIndex == xrem.length - 1) ? 0 : (earIndex + 1);
Triangle toAdd = new Triangle(xrem[earIndex], yrem[earIndex], xrem[over], yrem[over], xrem[under], yrem[under]);
buffer[bufferSize] = toAdd;
++bufferSize;
xrem = newx;
yrem = newy;
}
Triangle toAdd = new Triangle(xrem[1], yrem[1], xrem[2], yrem[2], xrem[0], yrem[0]);
buffer[bufferSize] = toAdd;
++bufferSize;
Triangle[] res = new Triangle[bufferSize];
System.arraycopy(buffer, 0, res, 0, bufferSize);
return res;
}
private static Polygon[] polygonizeTriangles(Triangle[] triangulated) {
Polygon[] polys;
int polyIndex = 0;
if (triangulated == null)
return null;
polys = new Polygon[triangulated.length];
boolean[] covered = new boolean[triangulated.length];
for (int i = 0; i < triangulated.length; i++)
covered[i] = false;
boolean notDone = true;
while (notDone) {
int currTri = -1;
for (int i = 0; i < triangulated.length; i++) {
if (!covered[i]) {
currTri = i;
break;
}
}
if (currTri == -1) {
notDone = false;
} else {
Polygon poly = new Polygon(triangulated[currTri]);
covered[currTri] = true;
for (int i = 0; i < triangulated.length; i++) {
if (covered[i])
continue;
Polygon newP = poly.add(triangulated[i]);
if (newP == null)
continue;
if (newP.isConvex()) {
poly = newP;
covered[i] = true;
}
}
polys[polyIndex] = poly;
polyIndex++;
}
}
Polygon[] ret = new Polygon[polyIndex];
System.arraycopy(polys, 0, ret, 0, polyIndex);
return ret;
}
private static boolean isEar(int i, float[] xv, float[] yv) {
float dx0, dy0, dx1, dy1;
dx0 = dy0 = dx1 = dy1 = 0;
if (i >= xv.length || i < 0 || xv.length < 3)
return false;
int upper = i + 1;
int lower = i - 1;
if (i == 0) {
dx0 = xv[0] - xv[xv.length - 1];
dy0 = yv[0] - yv[yv.length - 1];
dx1 = xv[1] - xv[0];
dy1 = yv[1] - yv[0];
lower = xv.length - 1;
} else if (i == xv.length - 1) {
dx0 = xv[i] - xv[i - 1];
dy0 = yv[i] - yv[i - 1];
dx1 = xv[0] - xv[i];
dy1 = yv[0] - yv[i];
upper = 0;
} else {
dx0 = xv[i] - xv[i - 1];
dy0 = yv[i] - yv[i - 1];
dx1 = xv[i + 1] - xv[i];
dy1 = yv[i + 1] - yv[i];
}
float cross = dx0 * dy1 - dx1 * dy0;
if (cross > 0)
return false;
Triangle myTri = new Triangle(xv[i], yv[i], xv[upper], yv[upper], xv[lower], yv[lower]);
for (int j = 0; j < xv.length; ++j) {
if (j == i || j == lower || j == upper)
continue;
if (myTri.isInside(xv[j], yv[j]))
return false;
}
return true;
}
}
@@ -1,150 +0,0 @@
/*
* ******************************************************************************
* * 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 games.rednblack.editor.utils.poly.earclipping.ewjordan;
/**
*
* @author EwJordan (http://www.ewjordan.com/earClip/)
*/
public class Polygon {
public float[] x;
public float[] y;
public int nVertices;
public Polygon(float[] _x, float[] _y) {
nVertices = _x.length;
x = new float[nVertices];
y = new float[nVertices];
for (int i = 0; i < nVertices; ++i) {
x[i] = _x[i];
y[i] = _y[i];
}
}
public Polygon(Triangle t) {
this(t.x, t.y);
}
public void set(Polygon p) {
nVertices = p.nVertices;
x = new float[nVertices];
y = new float[nVertices];
for (int i = 0; i < nVertices; ++i) {
x[i] = p.x[i];
y[i] = p.y[i];
}
}
public boolean isConvex() {
boolean isPositive = false;
for (int i = 0; i < nVertices; ++i) {
int lower = (i == 0) ? (nVertices - 1) : (i - 1);
int middle = i;
int upper = (i == nVertices - 1) ? (0) : (i + 1);
float dx0 = x[middle] - x[lower];
float dy0 = y[middle] - y[lower];
float dx1 = x[upper] - x[middle];
float dy1 = y[upper] - y[middle];
float cross = dx0 * dy1 - dx1 * dy0;
//Cross product should have same sign
//for each vertex if poly is convex.
boolean newIsP = (cross > 0) ? true : false;
if (i == 0) {
isPositive = newIsP;
} else if (isPositive != newIsP) {
return false;
}
}
return true;
}
/*
* Tries to add a triangle to the polygon.
* Returns null if it can't connect properly.
* Assumes bitwise equality of join vertices.
*/
public Polygon add(Triangle t) {
//First, find vertices that connect
int firstP = -1;
int firstT = -1;
int secondP = -1;
int secondT = -1;
for (int i = 0; i < nVertices; i++) {
if (t.x[0] == x[i] && t.y[0] == y[i]) {
if (firstP == -1) {
firstP = i;
firstT = 0;
} else {
secondP = i;
secondT = 0;
}
} else if (t.x[1] == x[i] && t.y[1] == y[i]) {
if (firstP == -1) {
firstP = i;
firstT = 1;
} else {
secondP = i;
secondT = 1;
}
} else if (t.x[2] == x[i] && t.y[2] == y[i]) {
if (firstP == -1) {
firstP = i;
firstT = 2;
} else {
secondP = i;
secondT = 2;
}
}
}
//Fix ordering if first should be last vertex of poly
if (firstP == 0 && secondP == nVertices - 1) {
firstP = nVertices - 1;
secondP = 0;
}
//Didn't find it
if (secondP == -1) {
return null;
}
//Find tip index on triangle
int tipT = 0;
if (tipT == firstT || tipT == secondT) {
tipT = 1;
}
if (tipT == firstT || tipT == secondT) {
tipT = 2;
}
float[] newx = new float[nVertices + 1];
float[] newy = new float[nVertices + 1];
int currOut = 0;
for (int i = 0; i < nVertices; i++) {
newx[currOut] = x[i];
newy[currOut] = y[i];
if (i == firstP) {
++currOut;
newx[currOut] = t.x[tipT];
newy[currOut] = t.y[tipT];
}
++currOut;
}
return new Polygon(newx, newy);
}
}
@@ -1,74 +0,0 @@
/*
* ******************************************************************************
* * 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 games.rednblack.editor.utils.poly.earclipping.ewjordan;
/**
*
* @author EwJordan (http://www.ewjordan.com/earClip/)
*/
public class Triangle {
public float[] x;
public float[] y;
public Triangle(float x1, float y1, float x2, float y2, float x3, float y3) {
x = new float[3];
y = new float[3];
float dx1 = x2 - x1;
float dx2 = x3 - x1;
float dy1 = y2 - y1;
float dy2 = y3 - y1;
float cross = dx1 * dy2 - dx2 * dy1;
boolean ccw = (cross > 0);
if (ccw) {
x[0] = x1;
x[1] = x2;
x[2] = x3;
y[0] = y1;
y[1] = y2;
y[2] = y3;
} else {
x[0] = x1;
x[1] = x3;
x[2] = x2;
y[0] = y1;
y[1] = y3;
y[2] = y2;
}
}
public boolean isInside(float _x, float _y) {
float vx2 = _x - x[0];
float vy2 = _y - y[0];
float vx1 = x[1] - x[0];
float vy1 = y[1] - y[0];
float vx0 = x[2] - x[0];
float vy0 = y[2] - y[0];
float dot00 = vx0 * vx0 + vy0 * vy0;
float dot01 = vx0 * vx1 + vy0 * vy1;
float dot02 = vx0 * vx2 + vy0 * vy2;
float dot11 = vx1 * vx1 + vy1 * vy1;
float dot12 = vx1 * vx2 + vy1 * vy2;
float invDenom = 1f / (dot00 * dot11 - dot01 * dot01);
float u = (dot11 * dot02 - dot01 * dot12) * invDenom;
float v = (dot00 * dot12 - dot01 * dot02) * invDenom;
return ((u > 0) && (v > 0) && (u + v < 1));
}
}
@@ -26,6 +26,7 @@ import games.rednblack.editor.controller.commands.AddComponentToItemCommand;
import games.rednblack.editor.controller.commands.RemoveComponentFromItemCommand;
import games.rednblack.editor.controller.commands.component.UpdatePolygonVerticesCommand;
import games.rednblack.editor.renderer.components.shape.PolygonShapeComponent;
import games.rednblack.editor.renderer.utils.poly.PolygonRuntimeUtils;
import games.rednblack.editor.utils.KeyBindingsLayout;
import games.rednblack.editor.utils.poly.PolygonUtils;
import games.rednblack.editor.utils.runtime.SandboxComponentRetriever;
@@ -132,10 +133,10 @@ public class PolygonTool extends SelectionTool implements PolygonTransformationL
if (!polygonShapeComponent.openEnded) {
IntSet intersections = PolygonUtils.checkForIntersection(vertexIndex, points, intersectionProblems);
if(intersections == null) {
if(PolygonUtils.isPolygonCCW(points.toArray())){
if(PolygonRuntimeUtils.isPolygonCCW(points.toArray())){
points.reverse();
}
polygonShapeComponent.polygonizedVertices = PolygonUtils.polygonize(points.toArray());
polygonShapeComponent.polygonizedVertices = PolygonRuntimeUtils.polygonize(points.toArray());
} else {
// restore from backup
polygonShapeComponent.vertices = UpdatePolygonVerticesCommand.cloneData(verticesBackup);
@@ -156,7 +157,7 @@ public class PolygonTool extends SelectionTool implements PolygonTransformationL
polygonShapeComponent.vertices.insert(vertexIndex, new Vector2(x, y));
if (!polygonShapeComponent.openEnded)
polygonShapeComponent.polygonizedVertices = PolygonUtils.polygonize(polygonShapeComponent.vertices.toArray());
polygonShapeComponent.polygonizedVertices = PolygonRuntimeUtils.polygonize(polygonShapeComponent.vertices.toArray());
follower.update();
follower.draggingAnchorId = vertexIndex;
@@ -195,7 +196,7 @@ public class PolygonTool extends SelectionTool implements PolygonTransformationL
// check if any of near lines intersect
IntSet intersections = PolygonUtils.checkForIntersection(anchor, points, intersectionProblems);
if(intersections == null) {
polygonShapeComponent.polygonizedVertices = PolygonUtils.polygonize(points.toArray());
polygonShapeComponent.polygonizedVertices = PolygonRuntimeUtils.polygonize(points.toArray());
follower.setProblems(null);
} else {
follower.setProblems(intersections);
@@ -252,7 +253,7 @@ public class PolygonTool extends SelectionTool implements PolygonTransformationL
follower.setSelectedAnchor(follower.getSelectedAnchorId() - 1);
if (!polygonShapeComponent.openEnded) {
polygonShapeComponent.polygonizedVertices = PolygonUtils.polygonize(polygonShapeComponent.vertices.toArray());
polygonShapeComponent.polygonizedVertices = PolygonRuntimeUtils.polygonize(polygonShapeComponent.vertices.toArray());
if(polygonShapeComponent.polygonizedVertices == null) {
// restore from backup
@@ -5,7 +5,7 @@ import com.badlogic.gdx.utils.Array;
import com.kotcrab.vis.ui.util.dialog.Dialogs;
import games.rednblack.editor.renderer.components.shape.PolygonShapeComponent;
import games.rednblack.editor.renderer.components.TextureRegionComponent;
import games.rednblack.editor.utils.poly.PolygonUtils;
import games.rednblack.editor.renderer.utils.poly.PolygonRuntimeUtils;
import games.rednblack.editor.utils.poly.tracer.Tracer;
import games.rednblack.editor.utils.runtime.SandboxComponentRetriever;
import games.rednblack.editor.view.stage.Sandbox;
@@ -71,7 +71,7 @@ public class AutoTraceDialogMediator extends Mediator<AutoTraceDialog> {
.flatMap(Stream::of)
.toArray(Vector2[]::new);
polygonShapeComponent.vertices = new Array<>(points);
polygonShapeComponent.polygonizedVertices = PolygonUtils.polygonize(points);
polygonShapeComponent.polygonizedVertices = PolygonRuntimeUtils.polygonize(points);
FollowersUIMediator followersUIMediator = Facade.getInstance().retrieveMediator(FollowersUIMediator.NAME);
BasicFollower follower = followersUIMediator.getFollower(entity);