From 23fd36a5f1a76cd56480cb7edb32dfc68e5ce4f4 Mon Sep 17 00:00:00 2001 From: badlogic Date: Thu, 30 Mar 2017 15:31:24 +0200 Subject: [PATCH] [libgdx] Improved and cleaned up decomposer --- .../esotericsoftware/spine/ClippingTest.java | 17 +- .../spine/ConvexDecomposerTest.java | 103 ++++---- .../spine/SoftwareClippingTest.java | 10 +- .../spine/SkeletonRenderer.java | 8 +- ...erlandHodgmanClipper.java => Clipper.java} | 31 ++- .../spine/utils/ConvexDecomposer.java | 229 ++++++++++++------ 6 files changed, 256 insertions(+), 142 deletions(-) rename spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/{SutherlandHodgmanClipper.java => Clipper.java} (70%) diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/ClippingTest.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/ClippingTest.java index 3bb0f3be8..eb7dc8b2c 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/ClippingTest.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/ClippingTest.java @@ -42,7 +42,6 @@ import com.badlogic.gdx.graphics.g2d.PolygonSpriteBatch; import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.math.WindowedMean; import com.esotericsoftware.spine.attachments.ClippingAttachment; -import com.esotericsoftware.spine.utils.TwoColorPolygonBatch; public class ClippingTest extends ApplicationAdapter { OrthographicCamera camera; @@ -90,12 +89,14 @@ public class ClippingTest extends ApplicationAdapter { // Create a clipping attachment, slot data, and slot. ClippingAttachment clip = new ClippingAttachment("clip"); // Rectangle: - clip.setVertices(new float[] { // - -140, 50, // - 250, 50, // - 250, 350, // - -140, 350, // - }); + clip.setVertices( + new float[] { 87, 288, 217, 371, 456, 361, 539, 175, 304, 194, 392, 290, 193, 214, 123, 15, 14, 137 }); +// new float[] { // +// -140, 50, // +// 250, 50, // +// 250, 350, // +// -140, 350, // +// }); // Self intersection: // clip.setVertices(new float[] { // // -140, -50, // @@ -116,7 +117,7 @@ public class ClippingTest extends ApplicationAdapter { } public void render () { - // state.update(Gdx.graphics.getDeltaTime() * 0.3f); + state.update(Gdx.graphics.getDeltaTime() * 0.3f); state.update(0); Gdx.gl.glClearColor(0.3f, 0.3f, 0.3f, 1); diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/ConvexDecomposerTest.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/ConvexDecomposerTest.java index 1deff9086..2b5a50363 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/ConvexDecomposerTest.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/ConvexDecomposerTest.java @@ -24,7 +24,7 @@ import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.FloatArray; import com.esotericsoftware.spine.utils.ConvexDecomposer; -import com.esotericsoftware.spine.utils.SutherlandHodgmanClipper; +import com.esotericsoftware.spine.utils.Clipper; public class ConvexDecomposerTest extends ApplicationAdapter { OrthographicCamera sceneCamera; @@ -46,9 +46,11 @@ public class ConvexDecomposerTest extends ApplicationAdapter { polyBatcher = new PolygonSpriteBatch(); image = new Texture("skin/skin.png"); font = new BitmapFont(); - - // float[] v = { 87, 288, 217, 371, 456, 361, 539, 175, 304, 194, 392, 290, 193, 214, 123, 15, 14, 137 }; - float[] v = { 336, 153, 207, 184, 364, 333, 529, 326, 584, 130, 438, 224 }; + + float[] v = {100, 100, 120, 100, 200, 100, 200, 400, 100, 400}; + +// float[] v = {87, 288, 217, 371, 456, 361, 539, 175, 304, 194, 392, 290, 193, 214, 123, 15, 14, 137}; +// float[] v = { 336, 153, 207, 184, 364, 333, 529, 326, 584, 130, 438, 224 }; polygon.addAll(v); triangulate(); } @@ -92,7 +94,7 @@ public class ConvexDecomposerTest extends ApplicationAdapter { triangulate(); } } - + if (Gdx.input.isKeyJustPressed(Keys.R)) { long start = System.nanoTime(); generateRandomPolygon(); @@ -103,43 +105,52 @@ public class ConvexDecomposerTest extends ApplicationAdapter { if (i != polygon.size - 1) System.out.print(", "); } System.out.println("};"); -// triangulate(); + triangulate(); + } + + if (Gdx.input.isKeyJustPressed(Keys.T)) { + triangulate(); } } private void generateRandomPolygon () { polygon.clear(); - - int numVertices = 10; // MathUtils.random(3, 3); + convexPolygons.clear(); + + int numVertices = MathUtils.random(3, 30); for (int i = 0; i < numVertices; i++) { float x = (float)(50 + Math.random() * (Gdx.graphics.getWidth() - 50)); float y = (float)(50 + Math.random() * (Gdx.graphics.getHeight() - 50)); polygon.add(x); polygon.add(y); - + System.out.println(polygon.toString(",")); if (selfIntersects(polygon)) { polygon.size -= 2; i--; } } } - - private boolean selfIntersects(FloatArray polygon) { + + private boolean selfIntersects (FloatArray polygon) { Vector2 tmp = new Vector2(); - for(int i = 0, n = polygon.size; i <= n; i+=4) { + if (polygon.size == 6) return false; + for (int i = 0, n = polygon.size; i <= n; i += 2) { float x1 = polygon.get(i % n); float y1 = polygon.get((i + 1) % n); float x2 = polygon.get((i + 2) % n); float y2 = polygon.get((i + 3) % n); - - for (int j = 0; j <= n; j+=4) { - if (j == i || j == i + 1) continue; + + for (int j = 0; j <= n; j += 2) { float x3 = polygon.get(j % n); float y3 = polygon.get((j + 1) % n); float x4 = polygon.get((j + 2) % n); float y4 = polygon.get((j + 3) % n); - if (Intersector.intersectSegments(x1, y1, x2, y2, x3, y3, x4, y4, tmp)) return true; + if (x1 == x3 && y1 == y3) continue; + if (x1 == x4 && y1 == y4) continue; + if (x2 == x3 && y2 == y3) continue; + if (x2 == x4 && y2 == y4) continue; + if (Intersector.intersectSegments(x1, y1, x2, y2, x3, y3, x4, y4, tmp)) return true; } } return false; @@ -154,7 +165,7 @@ public class ConvexDecomposerTest extends ApplicationAdapter { polyBatcher.disableBlending(); polyBatcher.end(); - + // polygon shapes.setColor(Color.RED); shapes.begin(ShapeType.Line); @@ -185,32 +196,32 @@ public class ConvexDecomposerTest extends ApplicationAdapter { } // edge normals - shapes.setColor(Color.YELLOW); - if (polygon.size > 2) { - boolean clockwise = SutherlandHodgmanClipper.isClockwise(polygon); - for (int i = 0; i < polygon.size; i += 2) { - float x = polygon.get(i); - float y = polygon.get(i + 1); - float x2 = polygon.get((i + 2) % polygon.size); - float y2 = polygon.get((i + 3) % polygon.size); +// shapes.setColor(Color.YELLOW); +// if (polygon.size > 2) { +// boolean clockwise = Clipper.isClockwise(polygon); +// for (int i = 0; i < polygon.size; i += 2) { +// float x = polygon.get(i); +// float y = polygon.get(i + 1); +// float x2 = polygon.get((i + 2) % polygon.size); +// float y2 = polygon.get((i + 3) % polygon.size); +// +// float mx = x + (x2 - x) / 2; +// float my = y + (y2 - y) / 2; +// float nx = (y2 - y); +// float ny = -(x2 - x); +// if (!clockwise) { +// nx = -nx; +// ny = -ny; +// } +// float l = 1 / (float)Math.sqrt(nx * nx + ny * ny); +// nx *= l * 20; +// ny *= l * 20; +// +// shapes.line(mx, my, mx + nx, my + ny); +// } +// } - float mx = x + (x2 - x) / 2; - float my = y + (y2 - y) / 2; - float nx = (y2 - y); - float ny = -(x2 - x); - if (!clockwise) { - nx = -nx; - ny = -ny; - } - float l = 1 / (float)Math.sqrt(nx * nx + ny * ny); - nx *= l * 20; - ny *= l * 20; - -// shapes.line(mx, my, mx + nx, my + ny); - } - } - - // decomposition + // decomposition if (convexPolygons != null) { for (int i = 0, n = convexPolygons.size; i < n; i++) { if (colors.size <= i) { @@ -223,12 +234,12 @@ public class ConvexDecomposerTest extends ApplicationAdapter { if (isCreatingPolygon) { polygon.setSize(polygon.size - 2); - } + } shapes.end(); - + polyBatcher.begin(); polyBatcher.enableBlending(); - for (int i = 0; i < polygon.size; i+=2) { + for (int i = 0; i < polygon.size; i += 2) { float x = polygon.get(i); float y = polygon.get(i + 1); font.draw(polyBatcher, "" + (i >> 1), x, y); // + ", " + x + ", " + y, x, y); @@ -238,7 +249,7 @@ public class ConvexDecomposerTest extends ApplicationAdapter { } private void triangulate () { - SutherlandHodgmanClipper.makeClockwise(polygon); + Clipper.makeClockwise(polygon); convexPolygons = decomposer.decompose(polygon); } diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/SoftwareClippingTest.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/SoftwareClippingTest.java index f9073ff8b..cee5d4a03 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/SoftwareClippingTest.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/SoftwareClippingTest.java @@ -18,7 +18,7 @@ import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.utils.FloatArray; import com.badlogic.gdx.utils.ShortArray; -import com.esotericsoftware.spine.utils.SutherlandHodgmanClipper; +import com.esotericsoftware.spine.utils.Clipper; public class SoftwareClippingTest extends ApplicationAdapter { OrthographicCamera sceneCamera; @@ -41,14 +41,14 @@ public class SoftwareClippingTest extends ApplicationAdapter { boolean isCreatingClippingArea = false; Vector3 tmp = new Vector3(); - SutherlandHodgmanClipper clipper; + Clipper clipper; @Override public void create () { sceneCamera = new OrthographicCamera(); shapes = new ShapeRenderer(); polyBatcher = new PolygonSpriteBatch(); - clipper = new SutherlandHodgmanClipper(); + clipper = new Clipper(); image = new Texture("skin/skin.png"); } @@ -137,7 +137,7 @@ public class SoftwareClippingTest extends ApplicationAdapter { // edge normals shapes.setColor(Color.YELLOW); if (clippingPolygon.size > 2) { - boolean clockwise = SutherlandHodgmanClipper.isClockwise(clippingPolygon); + boolean clockwise = Clipper.isClockwise(clippingPolygon); for (int i = 0; i < clippingPolygon.size; i += 2) { float x = clippingPolygon.get(i); float y = clippingPolygon.get(i + 1); @@ -183,7 +183,7 @@ public class SoftwareClippingTest extends ApplicationAdapter { // must duplicate first vertex at end of polygon // so we can avoid module/branch in clipping code - SutherlandHodgmanClipper.makeClockwise(clippingPolygon); + Clipper.makeClockwise(clippingPolygon); clippingPolygon.add(clippingPolygon.get(0)); clippingPolygon.add(clippingPolygon.get(1)); diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java index 7cf693873..84f574591 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java @@ -48,7 +48,7 @@ import com.esotericsoftware.spine.attachments.ClippingAttachment; import com.esotericsoftware.spine.attachments.MeshAttachment; import com.esotericsoftware.spine.attachments.RegionAttachment; import com.esotericsoftware.spine.attachments.SkeletonAttachment; -import com.esotericsoftware.spine.utils.SutherlandHodgmanClipper; +import com.esotericsoftware.spine.utils.Clipper; import com.esotericsoftware.spine.utils.TwoColorPolygonBatch; public class SkeletonRenderer { @@ -58,7 +58,7 @@ public class SkeletonRenderer { private boolean premultipliedAlpha; private final FloatArray vertices = new FloatArray(32); - private SutherlandHodgmanClipper clipper = new SutherlandHodgmanClipper(); + private Clipper clipper = new Clipper(); private ClippingAttachment clipAttachment; private Slot clipEnd; private FloatArray clippingArea = new FloatArray(); @@ -406,9 +406,9 @@ public class SkeletonRenderer { int n = clip.getWorldVerticesLength(); float[] vertices = this.clippingArea.setSize(n); clip.computeWorldVertices(slot, 0, n, vertices, 0, 2); - clippingAreaClockwise = SutherlandHodgmanClipper.isClockwise(this.clippingArea); + clippingAreaClockwise = Clipper.isClockwise(this.clippingArea); if (!clippingAreaClockwise) { - SutherlandHodgmanClipper.makeClockwise(clippingArea); + Clipper.makeClockwise(clippingArea); } clippingArea.add(clippingArea.items[0]); clippingArea.add(clippingArea.items[1]); diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/SutherlandHodgmanClipper.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/Clipper.java similarity index 70% rename from spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/SutherlandHodgmanClipper.java rename to spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/Clipper.java index 892751ba3..4622cc853 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/SutherlandHodgmanClipper.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/Clipper.java @@ -1,9 +1,38 @@ +/****************************************************************************** + * Spine Runtimes Software License v2.5 + * + * Copyright (c) 2013-2016, Esoteric Software + * All rights reserved. + * + * You are granted a perpetual, non-exclusive, non-sublicensable, and + * non-transferable license to use, install, execute, and perform the Spine + * Runtimes software and derivative works solely for personal or internal + * use. Without the written permission of Esoteric Software (see Section 2 of + * the Spine Software License Agreement), you may not (a) modify, translate, + * adapt, or develop new applications using the Spine Runtimes or otherwise + * create derivative works or improvements of the Spine Runtimes or (b) remove, + * delete, alter, or obscure any trademarks or any copyright, trademark, patent, + * or other intellectual property or proprietary rights notices on or in the + * Software, including any copy thereof. Redistributions in binary or source + * form must include this license and terms. + * + * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF + * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ package com.esotericsoftware.spine.utils; import com.badlogic.gdx.utils.FloatArray; -public class SutherlandHodgmanClipper { +public class Clipper { final FloatArray scratch = new FloatArray(); /** Clips the input triangle against the convex clipping area, which needs to be clockwise. If the triangle lies entirely within the clipping area, false is diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/ConvexDecomposer.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/ConvexDecomposer.java index 7fd21b916..dc25c88dd 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/ConvexDecomposer.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/ConvexDecomposer.java @@ -1,16 +1,64 @@ +/****************************************************************************** + * Spine Runtimes Software License v2.5 + * + * Copyright (c) 2013-2016, Esoteric Software + * All rights reserved. + * + * You are granted a perpetual, non-exclusive, non-sublicensable, and + * non-transferable license to use, install, execute, and perform the Spine + * Runtimes software and derivative works solely for personal or internal + * use. Without the written permission of Esoteric Software (see Section 2 of + * the Spine Software License Agreement), you may not (a) modify, translate, + * adapt, or develop new applications using the Spine Runtimes or otherwise + * create derivative works or improvements of the Spine Runtimes or (b) remove, + * delete, alter, or obscure any trademarks or any copyright, trademark, patent, + * or other intellectual property or proprietary rights notices on or in the + * Software, including any copy thereof. Redistributions in binary or source + * form must include this license and terms. + * + * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF + * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ package com.esotericsoftware.spine.utils; +import java.util.Iterator; + import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.FloatArray; import com.badlogic.gdx.utils.IntArray; +import com.badlogic.gdx.utils.Pool; import com.badlogic.gdx.utils.ShortArray; public class ConvexDecomposer { static private final int CONCAVE = -1; - static private final int TANGENTIAL = 0; static private final int CONVEX = 1; + private Pool polygonPool = new Pool() { + @Override + protected FloatArray newObject () { + return new FloatArray(16); + } + }; + + private Pool polygonIndicesPool = new Pool() { + @Override + protected ShortArray newObject () { + return new ShortArray(16); + } + }; + + private Array convexPolygons = new Array(); + private Array convexPolygonsIndices = new Array(); + private final ShortArray indicesArray = new ShortArray(); private short[] indices; private float[] vertices; @@ -36,17 +84,15 @@ public class ConvexDecomposer { for (int i = 0, n = vertexCount; i < n; ++i) vertexTypes.add(classifyVertex(i)); - // A polygon with n vertices has a triangulation of n-2 triangles. ShortArray triangles = this.triangles; triangles.clear(); triangles.ensureCapacity(Math.max(0, vertexCount - 2) * 4); + // Triangulate while (this.vertexCount > 3) { int earTipIndex = findEarTip(); - System.out.println("tip index: " + earTipIndex); cutEarTip(earTipIndex); - // The type of the two vertices adjacent to the clipped vertex may have changed. int previousIndex = previousIndex(earTipIndex); int nextIndex = earTipIndex == vertexCount ? 0 : earTipIndex; vertexTypes.set(previousIndex, classifyVertex(previousIndex)); @@ -59,57 +105,37 @@ public class ConvexDecomposer { triangles.add(indicesArray.get(1)); } - Array polyResult = new Array(); - Array polyIndicesResult = new Array(); + polygonPool.freeAll(convexPolygons); + convexPolygons.clear(); + polygonIndicesPool.freeAll(convexPolygonsIndices); + convexPolygonsIndices.clear(); - ShortArray polyIndices = new ShortArray(); - FloatArray poly = new FloatArray(); - int idx1 = triangles.get(0); - polyIndices.add(idx1); - idx1 <<= 1; - int idx2 = triangles.get(1); - polyIndices.add(idx2); - idx2 <<= 1; - int idx3 = triangles.get(2); - polyIndices.add(idx3); - idx3 <<= 1; - System.out.println("Triangle: " + idx1 / 2 + ", " + idx2 / 2 + ", " + idx3 / 2); - - float x1 = polygon.get(idx1); - float y1 = polygon.get(idx1 + 1); - float x2 = polygon.get(idx2); - float y2 = polygon.get(idx2 + 1); - float x3 = polygon.get(idx3); - float y3 = polygon.get(idx3 + 1); - - poly.add(x1); - poly.add(y1); - poly.add(x2); - poly.add(y2); - poly.add(x3); - poly.add(y3); - int lastWinding = winding(x1, y1, x2, y2, x3, y3); - int fanBaseIndex = idx1 >> 1; + ShortArray polyIndices = polygonIndicesPool.obtain(); + polyIndices.clear(); + FloatArray poly = polygonPool.obtain(); + poly.clear(); + int fanBaseIndex = -1; + int lastWinding = 0; - for (int i = 3, n = triangles.size; i < n; i += 3) { - idx1 = triangles.get(i); - idx2 = triangles.get(i + 1); - idx3 = triangles.get(i + 2); - System.out.println("Triangle: " + idx1 + ", " + idx2 + ", " + idx3); + // Merge subsequent triangles if they form a triangle fan + for (int i = 0, n = triangles.size; i < n; i += 3) { + int idx1 = triangles.get(i) << 1; + int idx2 = triangles.get(i + 1) << 1; + int idx3 = triangles.get(i + 2) << 1; - x1 = polygon.get(idx1 * 2); - y1 = polygon.get(idx1 * 2 + 1); - x2 = polygon.get(idx2 * 2); - y2 = polygon.get(idx2 * 2 + 1); - x3 = polygon.get(idx3 * 2); - y3 = polygon.get(idx3 * 2 + 1); + float x1 = polygon.get(idx1); + float y1 = polygon.get(idx1 + 1); + float x2 = polygon.get(idx2); + float y2 = polygon.get(idx2 + 1); + float x3 = polygon.get(idx3); + float y3 = polygon.get(idx3 + 1); // if the base of the last triangle // is the same as this triangle's base // check if they form a convex polygon (triangle fan) boolean merged = false; - if (fanBaseIndex == idx1) { - int o = poly.size - 4; + if (fanBaseIndex == idx1) { + int o = poly.size - 4; int winding1 = winding(poly.get(o), poly.get(o + 1), poly.get(o + 2), poly.get(o + 3), x3, y3); int winding2 = winding(x3, y3, poly.get(0), poly.get(1), poly.get(2), poly.get(3)); if (winding1 == lastWinding && winding2 == lastWinding) { @@ -123,16 +149,20 @@ public class ConvexDecomposer { // otherwise make this triangle // the new base if (!merged) { - polyResult.add(poly); - polyIndicesResult.add(polyIndices); - poly = new FloatArray(); + if (poly.size > 0) { + convexPolygons.add(poly); + convexPolygonsIndices.add(polyIndices); + } + poly = polygonPool.obtain(); + poly.clear(); poly.add(x1); poly.add(y1); poly.add(x2); poly.add(y2); poly.add(x3); poly.add(y3); - polyIndices = new ShortArray(); + polyIndices = polygonIndicesPool.obtain(); + polyIndices.clear(); polyIndices.add(idx1); polyIndices.add(idx2); polyIndices.add(idx3); @@ -142,24 +172,75 @@ public class ConvexDecomposer { } if (poly.size > 0) { - polyResult.add(poly); - polyIndicesResult.add(polyIndices); + convexPolygons.add(poly); + convexPolygonsIndices.add(polyIndices); } - for (ShortArray pIndices : polyIndicesResult) { - System.out.println("Poly: " + pIndices.toString(",")); + // go through the list of polygons and try + // to merge the remaining triangles with + // the found triangle fans + for (int i = 0, n = convexPolygons.size; i < n; i++) { + polyIndices = convexPolygonsIndices.get(i); + if (polyIndices.size == 0) continue; + int firstIndex = polyIndices.get(0); + int lastIndex = polyIndices.get(polyIndices.size - 1); + + poly = convexPolygons.get(i); + int o = poly.size - 4; + float prevPrevX = poly.get(o); + float prevPrevY = poly.get(o + 1); + float prevX = poly.get(o + 2); + float prevY = poly.get(o + 3); + float firstX = poly.get(0); + float firstY = poly.get(1); + float secondX = poly.get(2); + float secondY = poly.get(3); + int winding = winding(prevPrevX, prevPrevY, prevX, prevY, firstX, firstY); + + for (int j = 0; j < n; j++) { + if (j == i) continue; + ShortArray otherIndices = convexPolygonsIndices.get(j); + if (otherIndices.size != 3) continue; + int otherFirstIndex = otherIndices.get(0); + int otherSecondIndex = otherIndices.get(1); + int otherLastIndex = otherIndices.get(2); + + FloatArray otherPoly = convexPolygons.get(j); + float x3 = otherPoly.get(otherPoly.size - 2); + float y3 = otherPoly.get(otherPoly.size - 1); + + if (otherFirstIndex != firstIndex || otherSecondIndex != lastIndex) continue; + int winding1 = winding(prevPrevX, prevPrevY, prevX, prevY, x3, y3); + int winding2 = winding(x3, y3, firstX, firstY, secondX, secondY); + if (winding1 == winding && winding2 == winding) { + otherPoly.clear(); + otherIndices.clear(); + poly.add(x3); + poly.add(y3); + polyIndices.add(otherLastIndex); + prevPrevX = prevX; + prevPrevY = prevY; + prevX = x3; + prevY = y3; + j = 0; + } + } } - return polyResult; + // Remove empty polygons that resulted from the + // merge step above + Iterator polyIter = convexPolygons.iterator(); + while (polyIter.hasNext()) { + poly = polyIter.next(); + if (poly.size == 0) { + polyIter.remove(); + polygonPool.free(poly); + } + } + + return convexPolygons; } - public static int winding (float v1x, float v1y, float v2x, float v2y, float v3x, float v3y) { - float vx = v2x - v1x; - float vy = v2y - v1y; - return v3x * vy - v3y * vx + vx * v1y - v1x * vy >= 0 ? 1 : -1; - } - - /** @return {@link #CONCAVE}, {@link #TANGENTIAL} or {@link #CONVEX} */ private int classifyVertex (int index) { short[] indices = this.indices; int previous = indices[previousIndex(index)] * 2; @@ -175,17 +256,10 @@ public class ConvexDecomposer { for (int i = 0; i < vertexCount; i++) if (isEarTip(i)) return i; - // Desperate mode: if no vertex is an ear tip, we are dealing with a degenerate polygon (e.g. nearly collinear). - // Note that the input was not necessarily degenerate, but we could have made it so by clipping some valid ears. - - // Idea taken from Martin Held, "FIST: Fast industrial-strength triangulation of polygons", Algorithmica (1998), - // http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.115.291 - - // Return a convex or tangential vertex if one exists. int[] vertexTypes = this.vertexTypes.items; for (int i = 0; i < vertexCount; i++) if (vertexTypes[i] != CONCAVE) return i; - return 0; // If all vertices are concave, just return the first one. + return 0; } private boolean isEarTip (int earTipIndex) { @@ -203,18 +277,11 @@ public class ConvexDecomposer { float p2x = vertices[p2], p2y = vertices[p2 + 1]; float p3x = vertices[p3], p3y = vertices[p3 + 1]; - // Check if any point is inside the triangle formed by previous, current and next vertices. - // Only consider vertices that are not part of this triangle, or else we'll always find one inside. for (int i = nextIndex(nextIndex); i != previousIndex; i = nextIndex(i)) { - // Concave vertices can obviously be inside the candidate ear, but so can tangential vertices - // if they coincide with one of the triangle's vertices. if (vertexTypes[i] != CONVEX) { int v = indices[i] * 2; float vx = vertices[v]; float vy = vertices[v + 1]; - // Because the polygon has clockwise winding order, the area sign will be positive if the point is strictly inside. - // It will be 0 on the edge, which we want to include as well. - // note: check the edge defined by p1->p3 first since this fails _far_ more then the other 2 checks. if (computeSpannedAreaSign(p3x, p3y, p1x, p1y, vx, vy) >= 0) { if (computeSpannedAreaSign(p1x, p1y, p2x, p2y, vx, vy) >= 0) { if (computeSpannedAreaSign(p2x, p2y, p3x, p3y, vx, vy) >= 0) return false; @@ -255,4 +322,10 @@ public class ConvexDecomposer { area += p3x * (p2y - p1y); return (int)Math.signum(area); } + + public static int winding (float v1x, float v1y, float v2x, float v2y, float v3x, float v3y) { + float vx = v2x - v1x; + float vy = v2y - v1y; + return v3x * vy - v3y * vx + vx * v1y - v1x * vy >= 0 ? 1 : -1; + } }