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 738b1c2c4..3bb0f3be8 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 @@ -116,7 +116,8 @@ 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); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); 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 8cd9b8018..6554a4c64 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 @@ -174,19 +174,24 @@ public class SoftwareClippingTest extends ApplicationAdapter { } private void clip () { - boolean clipped = clipper.clip(triangle, 0, triangle.length, 5, clippingPolygon, clippedPolygon); + float x1 = triangle[0]; + float y1 = triangle[1]; + float x2 = triangle[5]; + 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 + 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 x1 = triangle[0]; - float y1 = triangle[1]; - float x2 = triangle[5]; - float y2 = triangle[6]; - float x3 = triangle[10]; - float y3 = triangle[11]; - float d0 = y2 - y3; float d1 = x3 - x2; float d2 = x1 - x3; @@ -222,6 +227,8 @@ public class SoftwareClippingTest extends ApplicationAdapter { } else { clippedPolygon.clear(); } + + clippingPolygon.setSize(clippingPolygon.size - 2); } public static void main (String[] args) { 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 5e9d307c0..76af4a231 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java @@ -62,7 +62,7 @@ public class SkeletonRenderer { private ClippingAttachment clipAttachment; private Slot clipEnd; private FloatArray clippingArea = new FloatArray(); - private float[] clipInput = new float[6]; + private boolean clippingAreaClockwise; private FloatArray clipOutput = new FloatArray(400); private FloatArray clippedVertices = new FloatArray(400); private ShortArray clippedTriangles = new ShortArray(400); @@ -100,9 +100,9 @@ public class SkeletonRenderer { } else if (attachment instanceof ClippingAttachment) { ClippingAttachment clip = (ClippingAttachment) attachment; - batch.end(); + if (!softwareClipping) batch.end(); clipStart(batch.getProjectionMatrix(), batch.getTransformMatrix(), slot, clip); - batch.begin(); + if (!softwareClipping) batch.begin(); continue; } else if (attachment instanceof MeshAttachment) { @@ -179,9 +179,9 @@ public class SkeletonRenderer { } else if (attachment instanceof ClippingAttachment) { ClippingAttachment clip = (ClippingAttachment) attachment; - batch.end(); + if (!softwareClipping) batch.end(); clipStart(batch.getProjectionMatrix(), batch.getTransformMatrix(), slot, clip); - batch.begin(); + if (!softwareClipping) batch.begin(); continue; } else if (attachment instanceof SkeletonAttachment) { @@ -284,9 +284,9 @@ public class SkeletonRenderer { } else if (attachment instanceof ClippingAttachment) { ClippingAttachment clip = (ClippingAttachment) attachment; - batch.end(); + if (!softwareClipping) batch.end(); clipStart(batch.getProjectionMatrix(), batch.getTransformMatrix(), slot, clip); - batch.begin(); + if (!softwareClipping) batch.begin(); continue; } else if (attachment instanceof SkeletonAttachment) { @@ -393,6 +393,9 @@ public class SkeletonRenderer { int n = clip.getWorldVerticesLength(); float[] vertices = this.clippingArea.setSize(n); clip.computeWorldVertices(slot, 0, n, vertices, 0, 2); + clippingAreaClockwise = SutherlandHodgmanClipper.clockwise(this.clippingArea); + clippingArea.add(clippingArea.items[0]); + clippingArea.add(clippingArea.items[1]); } } @@ -402,7 +405,7 @@ public class SkeletonRenderer { if (!softwareClipping) Gdx.gl.glDisable(GL20.GL_STENCIL_TEST); } - private void clipSoftware(float[] vertices, int offset, int verticesLength, short[] triangles, int triangleOffset, int trianglesLength, FloatArray clippedVertices, ShortArray clippedTriangles, float dark, float light, boolean twoColor) { + private void clipSoftware(final float[] vertices, final int offset, final int verticesLength, final short[] triangles, final int triangleOffset, final int trianglesLength, final FloatArray clippedVertices, final ShortArray clippedTriangles, final float dark, final float light, final boolean twoColor) { int idx = 0; clippedVertices.clear(); clippedTriangles.clear(); @@ -410,32 +413,25 @@ public class SkeletonRenderer { final int uvOffset = twoColor ? 4 : 3; for (int i = 0; i < trianglesLength; i += 3) { int triOffset = triangles[i] * vertexSize; - clipInput[0] = vertices[triOffset]; - clipInput[1] = vertices[triOffset + 1]; + float x1 = vertices[triOffset]; + float y1= vertices[triOffset + 1]; float u1 = vertices[triOffset + uvOffset]; float v1 = vertices[triOffset + uvOffset + 1]; triOffset = triangles[i + 1] * vertexSize; - clipInput[2] = vertices[triOffset]; - clipInput[3] = vertices[triOffset + 1]; + float x2 = vertices[triOffset]; + float y2 = vertices[triOffset + 1]; float u2 = vertices[triOffset + uvOffset]; float v2 = vertices[triOffset + uvOffset + 1]; triOffset = triangles[i + 2] * vertexSize; - clipInput[4] = vertices[triOffset]; - clipInput[5] = vertices[triOffset + 1]; + float x3 = vertices[triOffset]; + float y3 = vertices[triOffset + 1]; float u3 = vertices[triOffset + uvOffset]; float v3 = vertices[triOffset + uvOffset + 1]; - boolean clipped = clipper.clip(clipInput, 0, 6, 2, clippingArea, clipOutput); + boolean clipped = clipper.clip(x1, y1, x2, y2, x3, y3, clippingArea, clipOutput, clippingAreaClockwise); if (clipped) { - float x1 = clipInput[0]; - float y1 = clipInput[1]; - float x2 = clipInput[2]; - float y2 = clipInput[3]; - float x3 = clipInput[4]; - float y3 = clipInput[5]; - float d0 = y2 - y3; float d1 = x3 - x2; float d2 = x1 - x3; @@ -450,8 +446,10 @@ public class SkeletonRenderer { float x = clipVertices[j]; float y = clipVertices[j + 1]; - float a = (d0 * (x - x3) + d1 * (y - y3)) * denom; - float b = (d4 * (x - x3) + d2 * (y - y3)) * denom; + float c0 = x - x3; + float c1 = y - y3; + float a = (d0 * c0 + d1 * c1) * denom; + float b = (d4 * c0 + d2 * c1) * denom; float c = 1.0f - a - b; float u = u1 * a + u2 * b + u3 * c; @@ -472,22 +470,22 @@ public class SkeletonRenderer { idx += clipOutput.size >> 1; } else { - clippedVertices.add(clipInput[0]); - clippedVertices.add(clipInput[1]); + clippedVertices.add(x1); + clippedVertices.add(y1); clippedVertices.add(light); if (twoColor) clippedVertices.add(dark); clippedVertices.add(u1); clippedVertices.add(v1); - clippedVertices.add(clipInput[2]); - clippedVertices.add(clipInput[3]); + clippedVertices.add(x2); + clippedVertices.add(y2); clippedVertices.add(light); if (twoColor) clippedVertices.add(dark); clippedVertices.add(u2); clippedVertices.add(v2); - clippedVertices.add(clipInput[4]); - clippedVertices.add(clipInput[5]); + clippedVertices.add(x3); + clippedVertices.add(y3); clippedVertices.add(light); if (twoColor) clippedVertices.add(dark); clippedVertices.add(u3); diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/SutherlandHodgmanClipper.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/SutherlandHodgmanClipper.java index 88a0fa01c..9caa3b3c4 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/SutherlandHodgmanClipper.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/SutherlandHodgmanClipper.java @@ -1,67 +1,91 @@ package com.esotericsoftware.spine.utils; -import com.badlogic.gdx.math.Intersector; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.FloatArray; public class SutherlandHodgmanClipper { Vector2 tmp = new Vector2(); final FloatArray scratch = new FloatArray(); - - /** - * Clips the input triangle against the convex clipping area. If the triangle lies entirely - * within the clipping area, false is returned. - */ - public boolean clip (float[] triangle, int offset, int length, int stride, FloatArray clippingArea, FloatArray output) { + + public boolean clip(float x1, float y1, float x2, float y2, float x3, float y3, FloatArray clippingArea, + FloatArray output) { boolean isClockwise = clockwise(clippingArea); - - FloatArray originalOutput = output; + return clip(x1, y1, x2, y2, x3, y3, clippingArea, output, isClockwise); + } + + /** + * Clips the input triangle against the convex clipping area. If the + * triangle lies entirely within the clipping area, false is returned. The + * clipping area must duplicate the first vertex at the end of the vertices + * list! + */ + public boolean clip(float x1, float y1, float x2, float y2, float x3, float y3, FloatArray clippingArea, + FloatArray output, boolean isClockwise) { + final FloatArray originalOutput = output; boolean clipped = false; - FloatArray input = scratch; - input.clear(); - for (int i = offset; i < offset + length; i+= stride) { - input.add(triangle[i]); - input.add(triangle[i + 1]); + FloatArray input = null; + // avoid copy at the end + if ((clippingArea.size / 2) % 2 != 0) { + input = output; + output = scratch; + } else { + input = scratch; } + + input.clear(); + input.add(x1); + input.add(y1); + input.add(x2); + input.add(y2); + input.add(x3); + input.add(y3); + input.add(x1); + input.add(y1); output.clear(); - - for (int i = 0; i < clippingArea.size; i += 2) { - float edgeX = clippingArea.items[i]; - float edgeY = clippingArea.items[i + 1]; - float edgeX2 = clippingArea.items[(i + 2) % clippingArea.size]; - float edgeY2 = clippingArea.items[(i + 3) % clippingArea.size]; - + + final float[] clippingVertices = clippingArea.items; + final int clippingVerticesLength = clippingArea.size - 2; + for (int i = 0; i < clippingVerticesLength; i += 2) { + float edgeX = clippingVertices[i]; + float edgeY = clippingVertices[i + 1]; + float edgeX2 = clippingVertices[i + 2]; + float edgeY2 = clippingVertices[i + 3]; + if (!isClockwise) { float tmp = edgeX; edgeX = edgeX2; edgeX2 = tmp; - + tmp = edgeY; edgeY = edgeY2; edgeY2 = tmp; } - for (int j = 0; j < input.size; j += 2) { - float inputX = input.items[j % input.size]; - float inputY = input.items[(j + 1) % input.size]; - float inputX2 = input.items[(j + 2) % input.size]; - float inputY2 = input.items[(j + 3) % input.size]; - - int side = pointLineSide(edgeX2, edgeY2, edgeX, edgeY, inputX, inputY); - int side2 = pointLineSide(edgeX2, edgeY2, edgeX, edgeY, inputX2, inputY2); - + final float deltaX = edgeX - edgeX2; + final float deltaY = edgeY - edgeY2; + + final float[] inputVertices = input.items; + final int inputVerticesLength = input.size - 2; + + for (int j = 0; j < inputVerticesLength; j += 2) { + final float inputX = inputVertices[j]; + final float inputY = inputVertices[j + 1]; + final float inputX2 = inputVertices[j + 2]; + final float inputY2 = inputVertices[j + 3]; + + final int side = pointLineSide(deltaX, deltaY, edgeX2, edgeY2, inputX, inputY); + final int side2 = pointLineSide(deltaX, deltaY, edgeX2, edgeY2, inputX2, inputY2); + // v1 inside, v2 inside if (side >= 0 && side2 >= 0) { output.add(inputX2); output.add(inputY2); - } + } // v1 inside, v2 outside else if (side >= 0 && side2 < 0) { - if (!Intersector.intersectLines(edgeX, edgeY, edgeX2, edgeY2, inputX, inputY, inputX2, inputY2, tmp)) { - throw new RuntimeException("Lines should intersect, but didn't"); - } + intersectLines(edgeX, edgeY, edgeX2, edgeY2, inputX, inputY, inputX2, inputY2, tmp); output.add(tmp.x); output.add(tmp.y); clipped = true; @@ -73,9 +97,7 @@ public class SutherlandHodgmanClipper { } // v1 outside, v2 inside else if (side < 0 && side2 >= 0) { - if (!Intersector.intersectLines(edgeX, edgeY, edgeX2, edgeY2, inputX, inputY, inputX2, inputY2, tmp)) { - throw new RuntimeException("Lines should intersect, but didn't"); - } + intersectLines(edgeX, edgeY, edgeX2, edgeY2, inputX, inputY, inputX2, inputY2, tmp); output.add(tmp.x); output.add(tmp.y); output.add(inputX2); @@ -84,50 +106,57 @@ public class SutherlandHodgmanClipper { } } - if (i < clippingArea.size - 2) { + output.add(output.items[0]); + output.add(output.items[1]); + + if (i < clippingVerticesLength - 2) { FloatArray tmp = output; output = input; output.clear(); input = tmp; } } - + if (originalOutput != output) { originalOutput.clear(); originalOutput.addAll(output.items, 0, output.size); } + originalOutput.setSize(originalOutput.size - 2); + return clipped; } - - private int pointLineSide(float lineX, float lineY, float lineX2, float lineY2, float pointX, float pointY) { - return (int)Math.signum((lineX2 - lineX) * (pointY - lineY) - (lineY2 - lineY) * (pointX - lineX)); - } - - public static boolean intersectLines (float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, - Vector2 intersection) { - float d = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1); - if (d == 0) return false; - if (intersection != null) { - float ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / d; - intersection.set(x1 + (x2 - x1) * ua, y1 + (y2 - y1) * ua); - } - return true; + private int pointLineSide(float deltaX, float deltaY, float lineX, float lineY, float pointX, float pointY) { + return (int) Math.signum(deltaX * (pointY - lineY) - deltaY * (pointX - lineX)); } - public static boolean clockwise (FloatArray poly) { + public static void intersectLines(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, + Vector2 intersection) { + float c0 = y4 - y3; + float c1 = x2 - x1; + float c2 = x4 - x3; + float c3 = y2 - y1; + float d = c0 * c1 - c2 * c3; + + float ua = (c2 * (y1 - y3) - c0 * (x1 - x3)) / d; + intersection.set(x1 + (x2 - x1) * ua, y1 + (y2 - y1) * ua); + } + + public static boolean clockwise(FloatArray poly) { return area(poly) < 0; } - public static float area (FloatArray poly) { + public static float area(FloatArray poly) { float area = 0; - for (int i = 0; i < poly.size; i += 2) { - float x = poly.items[i]; - float y = poly.items[i + 1]; - float x2 = poly.items[(i + 2) % poly.size]; - float y2 = poly.items[(i + 3) % poly.size]; + final float[] polyVertices = poly.items; + final int polySize = poly.size; + for (int i = 0; i < polySize; i += 2) { + float x = polyVertices[i]; + float y = polyVertices[i + 1]; + float x2 = polyVertices[(i + 2) % poly.size]; + float y2 = polyVertices[(i + 3) % poly.size]; area += x * y2 - y * x2; }