Merge remote-tracking branch 'origin/3.6-beta' into 3.6-beta

This commit is contained in:
Nathan Sweet 2017-03-31 09:09:02 +09:00
commit 5287642645
7 changed files with 375 additions and 241 deletions

View File

@ -89,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, //
@ -115,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);

View File

@ -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);
}

View File

@ -6,6 +6,7 @@ import org.lwjgl.opengl.GL11;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Buttons;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;
import com.badlogic.gdx.graphics.Color;
@ -16,9 +17,11 @@ import com.badlogic.gdx.graphics.g2d.PolygonSpriteBatch;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.FloatArray;
import com.badlogic.gdx.utils.ShortArray;
import com.esotericsoftware.spine.utils.SutherlandHodgmanClipper;
import com.esotericsoftware.spine.utils.Clipper;
import com.esotericsoftware.spine.utils.ConvexDecomposer;
public class SoftwareClippingTest extends ApplicationAdapter {
OrthographicCamera sceneCamera;
@ -26,29 +29,28 @@ public class SoftwareClippingTest extends ApplicationAdapter {
PolygonSpriteBatch polyBatcher;
Texture image;
float[] triangleOutline = { 100, 100, 300, 100, 200, 300 };
float[] triangle = {
100, 100, Color.WHITE.toFloatBits(), 0, 1,
300, 100, Color.WHITE.toFloatBits(), 1, 1,
200, 300, Color.WHITE.toFloatBits(), 0.5f, 0
};
short[] triangleIndices = { 0, 1, 2 };
float[] triangleOutline = {100, 100, 300, 100, 200, 300};
float[] triangle = {100, 100, Color.WHITE.toFloatBits(), 0, 1, 300, 100, Color.WHITE.toFloatBits(), 1, 1, 200, 300,
Color.WHITE.toFloatBits(), 0.5f, 0};
short[] triangleIndices = {0, 1, 2};
FloatArray clippingPolygon = new FloatArray();
FloatArray clippedPolygon = new FloatArray();
FloatArray clippedPolygonVertices = new FloatArray();
ShortArray clippedPolygonIndices = new ShortArray();
boolean isCreatingClippingArea = false;
Vector3 tmp = new Vector3();
SutherlandHodgmanClipper clipper;
Clipper clipper;
ConvexDecomposer decomposer;
@Override
public void create () {
sceneCamera = new OrthographicCamera();
shapes = new ShapeRenderer();
polyBatcher = new PolygonSpriteBatch();
clipper = new SutherlandHodgmanClipper();
clipper = new Clipper();
decomposer = new ConvexDecomposer();
image = new Texture("skin/skin.png");
}
@ -74,15 +76,19 @@ public class SoftwareClippingTest extends ApplicationAdapter {
if (!isCreatingClippingArea) {
clippingPolygon.clear();
isCreatingClippingArea = true;
}
clippingPolygon.add(tmp.x);
clippingPolygon.add(tmp.y);
if (Gdx.input.isButtonPressed(Buttons.RIGHT)) {
isCreatingClippingArea = false;
clip();
}
clippingPolygon.add((int)tmp.x);
clippingPolygon.add((int)tmp.y);
if (Gdx.input.isButtonPressed(Buttons.RIGHT)) {
isCreatingClippingArea = false;
clip();
}
}
if (Gdx.input.isKeyJustPressed(Keys.T)) {
clip();
}
}
@ -90,24 +96,26 @@ public class SoftwareClippingTest extends ApplicationAdapter {
sceneCamera.update();
shapes.setProjectionMatrix(sceneCamera.combined);
polyBatcher.setProjectionMatrix(sceneCamera.combined);
polyBatcher.begin();
polyBatcher.disableBlending();
if (clippedPolygon.size == 0) {
// clipped polygon
if (clippedPolygonVertices.size == 0) {
polyBatcher.draw(image, triangle, 0, 15, triangleIndices, 0, 3);
} else {
polyBatcher.draw(image, clippedPolygonVertices.items, 0, clippedPolygonVertices.size, clippedPolygonIndices.items, 0, clippedPolygonIndices.size);
polyBatcher.draw(image, clippedPolygonVertices.items, 0, clippedPolygonVertices.size, clippedPolygonIndices.items, 0,
clippedPolygonIndices.size);
}
polyBatcher.end();
shapes.begin(ShapeType.Line);
// triangle
shapes.setColor(Color.GREEN);
shapes.polygon(triangleOutline);
// clipped polygons
// clipping area
shapes.setColor(Color.RED);
if (isCreatingClippingArea) {
tmp.set(Gdx.input.getX(), Gdx.input.getY(), 0);
@ -115,7 +123,7 @@ public class SoftwareClippingTest extends ApplicationAdapter {
clippingPolygon.add(tmp.x);
clippingPolygon.add(tmp.y);
}
switch (clippingPolygon.size) {
case 0:
break;
@ -134,41 +142,41 @@ public class SoftwareClippingTest extends ApplicationAdapter {
shapes.polygon(clippingPolygon.items, 0, clippingPolygon.size);
}
// edge normals
shapes.setColor(Color.YELLOW);
if (clippingPolygon.size > 2) {
boolean clockwise = SutherlandHodgmanClipper.isClockwise(clippingPolygon);
for (int i = 0; i < clippingPolygon.size; i += 2) {
float x = clippingPolygon.get(i);
float y = clippingPolygon.get(i + 1);
float x2 = clippingPolygon.get((i + 2) % clippingPolygon.size);
float y2 = clippingPolygon.get((i + 3) % clippingPolygon.size);
// // edge normals
// shapes.setColor(Color.YELLOW);
// if (clippingPolygon.size > 2) {
// 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);
// float x2 = clippingPolygon.get((i + 2) % clippingPolygon.size);
// float y2 = clippingPolygon.get((i + 3) % clippingPolygon.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);
}
}
if (isCreatingClippingArea) {
clippingPolygon.setSize(clippingPolygon.size - 2);
}
// clipped polygon
shapes.setColor(Color.PINK);
if (clippedPolygon.size > 0) {
shapes.polygon(clippedPolygon.items, 0, clippedPolygon.size);
}
// // clipped polygon
// shapes.setColor(Color.PINK);
// if (clippedPolygon.size > 0) {
// shapes.polygon(clippedPolygon.items, 0, clippedPolygon.size);
// }
shapes.end();
}
@ -180,56 +188,58 @@ public class SoftwareClippingTest extends ApplicationAdapter {
float y2 = triangle[6];
float x3 = triangle[10];
float y3 = triangle[11];
// must duplicate first vertex at end of polygon
// so we can avoid module/branch in clipping code
SutherlandHodgmanClipper.makeClockwise(clippingPolygon);
clippingPolygon.add(clippingPolygon.get(0));
clippingPolygon.add(clippingPolygon.get(1));
boolean clipped = clipper.clip(x1, y1, x2, y2, x3, y3, clippingPolygon, clippedPolygon);
System.out.println("Clipped: " + clipped);
if (clipped) {
clippedPolygonVertices.clear();
clippedPolygonIndices.clear();
float d0 = y2 - y3;
float d1 = x3 - x2;
float d2 = x1 - x3;
float d3 = y1 - y3;
float d4 = y3 - y1;
float denom = 1 / (d0 * d2 + d1 * d3);
// triangulate by creating a triangle fan, duplicate vertices
float color = Color.WHITE.toFloatBits();
for (int i = 0; i < clippedPolygon.size; i+=2) {
float x = clippedPolygon.get(i);
float y = clippedPolygon.get(i + 1);
float a = (d0 * (x - x3) + d1 * (y - y3)) * denom;
float b = (d4 * (x - x3) + d2 * (y - y3)) * denom;
float c = 1.0f - a - b;
float u = triangle[3] * a + triangle[8] * b + triangle[13] * c;
float v = triangle[4] * a + triangle[9] * b + triangle[14] * c;
clippedPolygonVertices.add(x);
clippedPolygonVertices.add(y);
clippedPolygonVertices.add(color);
clippedPolygonVertices.add(u);
clippedPolygonVertices.add(v);
Array<FloatArray> clippingPolygons = decomposer.decompose(clippingPolygon);
clippedPolygonVertices.clear();
clippedPolygonIndices.clear();
for (FloatArray poly : clippingPolygons) {
Clipper.makeClockwise(poly);
poly.add(poly.get(0));
poly.add(poly.get(1));
boolean clipped = clipper.clip(x1, y1, x2, y2, x3, y3, poly, clippedPolygon);
System.out.println("Clipped: " + clipped);
if (clipped) {
float d0 = y2 - y3;
float d1 = x3 - x2;
float d2 = x1 - x3;
float d3 = y1 - y3;
float d4 = y3 - y1;
float denom = 1 / (d0 * d2 + d1 * d3);
// triangulate by creating a triangle fan, duplicate vertices
int o = clippedPolygonVertices.size / 5;
float color = Color.WHITE.toFloatBits();
for (int i = 0; i < clippedPolygon.size; i += 2) {
float x = clippedPolygon.get(i);
float y = clippedPolygon.get(i + 1);
float a = (d0 * (x - x3) + d1 * (y - y3)) * denom;
float b = (d4 * (x - x3) + d2 * (y - y3)) * denom;
float c = 1.0f - a - b;
float u = triangle[3] * a + triangle[8] * b + triangle[13] * c;
float v = triangle[4] * a + triangle[9] * b + triangle[14] * c;
clippedPolygonVertices.add(x);
clippedPolygonVertices.add(y);
clippedPolygonVertices.add(color);
clippedPolygonVertices.add(u);
clippedPolygonVertices.add(v);
}
for (int i = 1; i < (clippedPolygon.size >> 1) - 1; i++) {
clippedPolygonIndices.add(o);
clippedPolygonIndices.add(o + i);
clippedPolygonIndices.add(o + i + 1);
}
} else {
clippedPolygon.clear();
}
for (int i = 1; i < (clippedPolygon.size >> 1) - 1; i++) {
clippedPolygonIndices.add(0);
clippedPolygonIndices.add(i);
clippedPolygonIndices.add(i + 1);
}
} else {
clippedPolygon.clear();
poly.setSize(poly.size - 2);
}
clippingPolygon.setSize(clippingPolygon.size - 2);
}
public static void main (String[] args) {

View File

@ -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]);

View File

@ -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

View File

@ -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<FloatArray> polygonPool = new Pool<FloatArray>() {
@Override
protected FloatArray newObject () {
return new FloatArray(16);
}
};
private Pool<ShortArray> polygonIndicesPool = new Pool<ShortArray>() {
@Override
protected ShortArray newObject () {
return new ShortArray(16);
}
};
private Array<FloatArray> convexPolygons = new Array<FloatArray>();
private Array<ShortArray> convexPolygonsIndices = new Array<ShortArray>();
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<FloatArray> polyResult = new Array<FloatArray>();
Array<ShortArray> polyIndicesResult = new Array<ShortArray>();
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<FloatArray> 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;
}
}

View File

@ -305,9 +305,11 @@ namespace Spine.Unity.Modules.AttachmentTools {
var skinAttachments = o.Attachments;
var newSkin = new Skin(newName);
// Use these to detect and use shared regions.
var existingRegions = new Dictionary<AtlasRegion, int>();
var regionIndexes = new List<int>();
// Collect all textures from the attachments of the original skin.
var repackedAttachments = new List<Attachment>();
var texturesToPack = new List<Texture2D>();
var originalRegions = new List<AtlasRegion>();
@ -334,11 +336,13 @@ namespace Spine.Unity.Modules.AttachmentTools {
newSkin.AddAttachment(key.slotIndex, key.name, newAttachment);
}
// Fill a new texture with the collected attachment textures.
var newTexture = new Texture2D(maxAtlasSize, maxAtlasSize, textureFormat, mipmaps);
newTexture.anisoLevel = texturesToPack[0].anisoLevel;
newTexture.name = newName;
var rects = newTexture.PackTextures(texturesToPack.ToArray(), padding, maxAtlasSize);
// Rehydrate the repacked textures as a Material, Spine atlas and Spine.AtlasAttachments
var newMaterial = new Material(shader);
newMaterial.name = newName;
newMaterial.mainTexture = newTexture;
@ -352,11 +356,16 @@ namespace Spine.Unity.Modules.AttachmentTools {
repackedRegions.Add(newRegion);
}
// Map the cloned attachments to the repacked atlas.
for (int i = 0, n = repackedAttachments.Count; i < n; i++) {
var a = repackedAttachments[i];
a.SetRegion(repackedRegions[regionIndexes[i]]);
}
// Clean up
foreach (var ttp in texturesToPack)
UnityEngine.Object.Destroy(ttp);
t = newTexture;
m = newMaterial;
return newSkin;
@ -706,7 +715,7 @@ namespace Spine.Unity.Modules.AttachmentTools {
ma.hulllength = o.hulllength;
// Nonessential.
ma.Edges = o.Edges.Clone() as int[];
ma.Edges = (o.Edges == null) ? null : o.Edges.Clone() as int[]; // Allow absence of Edges array when nonessential data is not exported.
ma.Width = o.Width;
ma.Height = o.Height;
}