diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AnimationStateTests.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AnimationStateTests.java index 2be3a7724..f35c200d5 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AnimationStateTests.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AnimationStateTests.java @@ -41,6 +41,7 @@ import com.esotericsoftware.spine.AnimationState.AnimationStateListener; import com.esotericsoftware.spine.AnimationState.TrackEntry; import com.esotericsoftware.spine.attachments.AttachmentLoader; import com.esotericsoftware.spine.attachments.BoundingBoxAttachment; +import com.esotericsoftware.spine.attachments.ClippingAttachment; import com.esotericsoftware.spine.attachments.MeshAttachment; import com.esotericsoftware.spine.attachments.PathAttachment; import com.esotericsoftware.spine.attachments.PointAttachment; @@ -60,6 +61,10 @@ public class AnimationStateTests { return null; } + public ClippingAttachment newClippingAttachment (Skin skin, String name) { + return null; + } + public PathAttachment newPathAttachment (Skin skin, String name) { return null; } diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/BonePlotting.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/BonePlotting.java index 00bbe6a32..aa9f08b8e 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/BonePlotting.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/BonePlotting.java @@ -33,6 +33,7 @@ package com.esotericsoftware.spine; import com.badlogic.gdx.files.FileHandle; import com.esotericsoftware.spine.attachments.AttachmentLoader; import com.esotericsoftware.spine.attachments.BoundingBoxAttachment; +import com.esotericsoftware.spine.attachments.ClippingAttachment; import com.esotericsoftware.spine.attachments.RegionAttachment; import com.esotericsoftware.spine.attachments.MeshAttachment; import com.esotericsoftware.spine.attachments.PathAttachment; @@ -54,6 +55,10 @@ public class BonePlotting { return null; } + public ClippingAttachment newClippingAttachment (Skin skin, String name) { + return null; + } + public PathAttachment newPathAttachment (Skin skin, String name) { return null; } 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 new file mode 100644 index 000000000..29123dab4 --- /dev/null +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/ClippingTest.java @@ -0,0 +1,141 @@ +/****************************************************************************** + * 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; + +import com.badlogic.gdx.ApplicationAdapter; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.backends.lwjgl.LwjglApplication; +import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.OrthographicCamera; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; +import com.esotericsoftware.spine.attachments.ClippingAttachment; + +public class ClippingTest extends ApplicationAdapter { + OrthographicCamera camera; + SpriteBatch batch; + SkeletonRenderer renderer; + SkeletonRendererDebug debugRenderer; + + TextureAtlas atlas; + Skeleton skeleton; + AnimationState state; + + public void create () { + camera = new OrthographicCamera(); + batch = new SpriteBatch(); + renderer = new SkeletonRenderer(); + renderer.setPremultipliedAlpha(true); + debugRenderer = new SkeletonRendererDebug(); + debugRenderer.setBoundingBoxes(false); + debugRenderer.setRegionAttachments(false); + + atlas = new TextureAtlas(Gdx.files.internal("spineboy/spineboy-pma.atlas")); + SkeletonJson json = new SkeletonJson(atlas); + json.setScale(0.6f); + SkeletonData skeletonData = json.readSkeletonData(Gdx.files.internal("spineboy/spineboy.json")); + + skeleton = new Skeleton(skeletonData); + skeleton.setPosition(250, 20); + + AnimationStateData stateData = new AnimationStateData(skeletonData); + stateData.setMix("run", "jump", 0.2f); + stateData.setMix("jump", "run", 0.2f); + + state = new AnimationState(stateData); + state.setTimeScale(0.5f); + + state.setAnimation(0, "run", true); + state.addAnimation(0, "jump", false, 2); + state.addAnimation(0, "run", true, 0); + + // Create a clipping attachment, slot data, and slot. + ClippingAttachment clip = new ClippingAttachment("clip"); + // Rectangle: +// clip.setVertices(new float[] { // +// -140, -50, // +// 120, -50, // +// 120, 50, // +// -140, 50, // +// }); + // Self intersection: + clip.setVertices(new float[] { // + -140, -50, // + 120, 50, // + 120, -50, // + -140, 50, // + }); + clip.setWorldVerticesLength(8); + clip.setEnd(skeleton.findSlot("muzzle")); + + SlotData clipSlotData = new SlotData(skeletonData.getSlots().size, "clip slot", skeletonData.getBones().first()); + skeletonData.getSlots().add(clipSlotData); + + Slot clipSlot = new Slot(clipSlotData, skeleton.getRootBone()); + clipSlot.setAttachment(clip); + skeleton.getSlots().add(clipSlot); + skeleton.getDrawOrder().insert(skeletonData.findSlot("neck").getIndex(), clipSlot); + } + + public void render () { + state.update(Gdx.graphics.getDeltaTime() * 0.3f); + + Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); + + state.apply(skeleton); + skeleton.updateWorldTransform(); + + camera.update(); + batch.getProjectionMatrix().set(camera.combined); + debugRenderer.getShapeRenderer().setProjectionMatrix(camera.combined); + + batch.begin(); + renderer.draw(batch, skeleton); + batch.end(); + + debugRenderer.draw(skeleton); + } + + public void resize (int width, int height) { + camera.setToOrtho(false); + } + + public void dispose () { + atlas.dispose(); + } + + public static void main (String[] args) throws Exception { + LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); + config.stencil = 8; + new LwjglApplication(new ClippingTest(), config); + } +} diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java index 1af4003cd..556e44aa2 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java @@ -377,6 +377,7 @@ public class SkeletonBinary { region.updateOffset(); return region; } + // BOZO! - Binary clip export. case boundingbox: { int vertexCount = input.readInt(true); Vertices vertices = readVertices(input, vertexCount); diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java index f600cb84b..cdd9fafab 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -337,6 +337,7 @@ public class SkeletonJson { region.updateOffset(); return region; } + // BOZO! - JSON clip export. case boundingbox: { BoundingBoxAttachment box = attachmentLoader.newBoundingBoxAttachment(skin, name); if (box == null) return null; 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 8ca103a50..9f8beb984 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java @@ -30,14 +30,20 @@ package com.esotericsoftware.spine; +import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.Batch; import com.badlogic.gdx.graphics.g2d.PolygonSpriteBatch; +import com.badlogic.gdx.graphics.glutils.ImmediateModeRenderer; +import com.badlogic.gdx.graphics.glutils.ImmediateModeRenderer20; +import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.FloatArray; import com.badlogic.gdx.utils.NumberUtils; import com.esotericsoftware.spine.attachments.Attachment; +import com.esotericsoftware.spine.attachments.ClippingAttachment; import com.esotericsoftware.spine.attachments.MeshAttachment; import com.esotericsoftware.spine.attachments.RegionAttachment; import com.esotericsoftware.spine.attachments.SkeletonAttachment; @@ -49,6 +55,10 @@ public class SkeletonRenderer { private boolean premultipliedAlpha; private final FloatArray vertices = new FloatArray(32); + private Slot clipEnd; + private final Matrix4 combinedMatrix = new Matrix4(); + private ImmediateModeRenderer renderer; // BOZO! - Dispose. + public void draw (Batch batch, Skeleton skeleton) { boolean premultipliedAlpha = this.premultipliedAlpha; float[] vertices = this.vertices.items; @@ -78,31 +88,44 @@ public class SkeletonRenderer { batch.setBlendFunction(blendMode.getSource(premultipliedAlpha), blendMode.getDest()); batch.draw(region.getRegion().getTexture(), vertices, 0, 20); + } else if (attachment instanceof ClippingAttachment) { + ClippingAttachment clip = (ClippingAttachment)attachment; + batch.end(); + clipStart(batch.getProjectionMatrix(), batch.getTransformMatrix(), slot, clip); + batch.begin(); + continue; + } else if (attachment instanceof MeshAttachment) { throw new RuntimeException("SkeletonMeshRenderer is required to render meshes."); } else if (attachment instanceof SkeletonAttachment) { Skeleton attachmentSkeleton = ((SkeletonAttachment)attachment).getSkeleton(); - if (attachmentSkeleton == null) continue; - Bone bone = slot.getBone(); - Bone rootBone = attachmentSkeleton.getRootBone(); - float oldScaleX = rootBone.getScaleX(); - float oldScaleY = rootBone.getScaleY(); - float oldRotation = rootBone.getRotation(); - attachmentSkeleton.setPosition(bone.getWorldX(), bone.getWorldY()); - // rootBone.setScaleX(1 + bone.getWorldScaleX() - oldScaleX); - // rootBone.setScaleY(1 + bone.getWorldScaleY() - oldScaleY); - // Set shear. - rootBone.setRotation(oldRotation + bone.getWorldRotationX()); - attachmentSkeleton.updateWorldTransform(); + if (attachmentSkeleton != null) { + Bone bone = slot.getBone(); + Bone rootBone = attachmentSkeleton.getRootBone(); + float oldScaleX = rootBone.getScaleX(); + float oldScaleY = rootBone.getScaleY(); + float oldRotation = rootBone.getRotation(); + attachmentSkeleton.setPosition(bone.getWorldX(), bone.getWorldY()); + // rootBone.setScaleX(1 + bone.getWorldScaleX() - oldScaleX); + // rootBone.setScaleY(1 + bone.getWorldScaleY() - oldScaleY); + // Set shear. + rootBone.setRotation(oldRotation + bone.getWorldRotationX()); + attachmentSkeleton.updateWorldTransform(); - draw(batch, attachmentSkeleton); + draw(batch, attachmentSkeleton); - attachmentSkeleton.setX(0); - attachmentSkeleton.setY(0); - rootBone.setScaleX(oldScaleX); - rootBone.setScaleY(oldScaleY); - rootBone.setRotation(oldRotation); + attachmentSkeleton.setX(0); + attachmentSkeleton.setY(0); + rootBone.setScaleX(oldScaleX); + rootBone.setScaleY(oldScaleY); + rootBone.setRotation(oldRotation); + } + } + + if (slot == clipEnd) { + batch.flush(); + clipEnd(); } } } @@ -142,28 +165,35 @@ public class SkeletonRenderer { uvs = mesh.getUVs(); color = mesh.getColor(); + } else if (attachment instanceof ClippingAttachment) { + ClippingAttachment clip = (ClippingAttachment)attachment; + batch.end(); + clipStart(batch.getProjectionMatrix(), batch.getTransformMatrix(), slot, clip); + batch.begin(); + continue; + } else if (attachment instanceof SkeletonAttachment) { Skeleton attachmentSkeleton = ((SkeletonAttachment)attachment).getSkeleton(); - if (attachmentSkeleton == null) continue; - Bone bone = slot.getBone(); - Bone rootBone = attachmentSkeleton.getRootBone(); - float oldScaleX = rootBone.getScaleX(); - float oldScaleY = rootBone.getScaleY(); - float oldRotation = rootBone.getRotation(); - attachmentSkeleton.setPosition(bone.getWorldX(), bone.getWorldY()); - // rootBone.setScaleX(1 + bone.getWorldScaleX() - oldScaleX); - // rootBone.setScaleY(1 + bone.getWorldScaleY() - oldScaleY); - // Also set shear. - rootBone.setRotation(oldRotation + bone.getWorldRotationX()); - attachmentSkeleton.updateWorldTransform(); + if (attachmentSkeleton != null) { + Bone bone = slot.getBone(); + Bone rootBone = attachmentSkeleton.getRootBone(); + float oldScaleX = rootBone.getScaleX(); + float oldScaleY = rootBone.getScaleY(); + float oldRotation = rootBone.getRotation(); + attachmentSkeleton.setPosition(bone.getWorldX(), bone.getWorldY()); + // rootBone.setScaleX(1 + bone.getWorldScaleX() - oldScaleX); + // rootBone.setScaleY(1 + bone.getWorldScaleY() - oldScaleY); + // Also set shear. + rootBone.setRotation(oldRotation + bone.getWorldRotationX()); + attachmentSkeleton.updateWorldTransform(); - draw(batch, attachmentSkeleton); + draw(batch, attachmentSkeleton); - attachmentSkeleton.setPosition(0, 0); - rootBone.setScaleX(oldScaleX); - rootBone.setScaleY(oldScaleY); - rootBone.setRotation(oldRotation); - continue; + attachmentSkeleton.setPosition(0, 0); + rootBone.setScaleX(oldScaleX); + rootBone.setScaleY(oldScaleY); + rootBone.setRotation(oldRotation); + } } if (texture != null) { @@ -186,6 +216,11 @@ public class SkeletonRenderer { } batch.draw(texture, vertices, 0, verticesLength, triangles, 0, triangles.length); } + + if (slot == clipEnd) { + batch.flush(); + clipEnd(); + } } } @@ -224,28 +259,35 @@ public class SkeletonRenderer { uvs = mesh.getUVs(); color = mesh.getColor(); + } else if (attachment instanceof ClippingAttachment) { + ClippingAttachment clip = (ClippingAttachment)attachment; + batch.end(); + clipStart(batch.getProjectionMatrix(), batch.getTransformMatrix(), slot, clip); + batch.begin(); + continue; + } else if (attachment instanceof SkeletonAttachment) { Skeleton attachmentSkeleton = ((SkeletonAttachment)attachment).getSkeleton(); - if (attachmentSkeleton == null) continue; - Bone bone = slot.getBone(); - Bone rootBone = attachmentSkeleton.getRootBone(); - float oldScaleX = rootBone.getScaleX(); - float oldScaleY = rootBone.getScaleY(); - float oldRotation = rootBone.getRotation(); - attachmentSkeleton.setPosition(bone.getWorldX(), bone.getWorldY()); - // rootBone.setScaleX(1 + bone.getWorldScaleX() - oldScaleX); - // rootBone.setScaleY(1 + bone.getWorldScaleY() - oldScaleY); - // Also set shear. - rootBone.setRotation(oldRotation + bone.getWorldRotationX()); - attachmentSkeleton.updateWorldTransform(); + if (attachmentSkeleton != null) { + Bone bone = slot.getBone(); + Bone rootBone = attachmentSkeleton.getRootBone(); + float oldScaleX = rootBone.getScaleX(); + float oldScaleY = rootBone.getScaleY(); + float oldRotation = rootBone.getRotation(); + attachmentSkeleton.setPosition(bone.getWorldX(), bone.getWorldY()); + // rootBone.setScaleX(1 + bone.getWorldScaleX() - oldScaleX); + // rootBone.setScaleY(1 + bone.getWorldScaleY() - oldScaleY); + // Also set shear. + rootBone.setRotation(oldRotation + bone.getWorldRotationX()); + attachmentSkeleton.updateWorldTransform(); - draw(batch, attachmentSkeleton); + draw(batch, attachmentSkeleton); - attachmentSkeleton.setPosition(0, 0); - rootBone.setScaleX(oldScaleX); - rootBone.setScaleY(oldScaleY); - rootBone.setRotation(oldRotation); - continue; + attachmentSkeleton.setPosition(0, 0); + rootBone.setScaleX(oldScaleX); + rootBone.setScaleY(oldScaleY); + rootBone.setRotation(oldRotation); + } } if (texture != null) { @@ -275,9 +317,47 @@ public class SkeletonRenderer { } batch.draw(texture, vertices, 0, verticesLength, triangles, 0, triangles.length); } + + if (slot == clipEnd) { + batch.flush(); + clipEnd(); + } } } + private void clipStart (Matrix4 transformMatrix, Matrix4 projectionMatrix, Slot slot, ClippingAttachment clip) { + if (clipEnd != null) return; + clipEnd = clip.getEnd(); + + int n = clip.getWorldVerticesLength(); + float[] vertices = this.vertices.setSize(n); + clip.computeWorldVertices(slot, 0, n, vertices, 0, 2); + + Gdx.gl.glClearStencil(0); + Gdx.gl.glClear(GL20.GL_STENCIL_BUFFER_BIT); + Gdx.gl.glEnable(GL20.GL_STENCIL_TEST); + Gdx.gl.glStencilFunc(GL20.GL_NEVER, 0, 1); + Gdx.gl.glStencilOp(GL20.GL_INVERT, GL20.GL_INVERT, GL20.GL_INVERT); + Gdx.gl.glColorMask(false, false, false, false); + + if (renderer == null || renderer.getMaxVertices() < n) + renderer = new ImmediateModeRenderer20(Math.max(100, n), false, false, 0); + renderer.begin(combinedMatrix.set(projectionMatrix).mul(transformMatrix), GL20.GL_TRIANGLE_FAN); + renderer.vertex(vertices[0], vertices[1], 0); + for (int i = 2; i < n; i += 2) + renderer.vertex(vertices[i], vertices[i + 1], 0); + renderer.end(); + + Gdx.gl.glColorMask(true, true, true, true); + Gdx.gl.glStencilFunc(clip.getInvert() ? GL20.GL_NOTEQUAL : GL20.GL_EQUAL, 1, 1); + Gdx.gl.glStencilOp(GL20.GL_KEEP, GL20.GL_KEEP, GL20.GL_KEEP); + } + + private void clipEnd () { + clipEnd = null; + Gdx.gl.glDisable(GL20.GL_STENCIL_TEST); + } + public void setPremultipliedAlpha (boolean premultipliedAlpha) { this.premultipliedAlpha = premultipliedAlpha; } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRendererDebug.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRendererDebug.java index c9b716d73..aa6b0a9ff 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRendererDebug.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRendererDebug.java @@ -40,6 +40,7 @@ import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.FloatArray; import com.esotericsoftware.spine.attachments.Attachment; import com.esotericsoftware.spine.attachments.BoundingBoxAttachment; +import com.esotericsoftware.spine.attachments.ClippingAttachment; import com.esotericsoftware.spine.attachments.MeshAttachment; import com.esotericsoftware.spine.attachments.PathAttachment; import com.esotericsoftware.spine.attachments.PointAttachment; @@ -51,10 +52,11 @@ public class SkeletonRendererDebug { static private final Color attachmentLineColor = new Color(0, 0, 1, 0.5f); static private final Color triangleLineColor = new Color(1, 0.64f, 0, 0.5f); static private final Color aabbColor = new Color(0, 1, 0, 0.5f); + static private final Color clippingLineColor = Color.MAGENTA; private final ShapeRenderer shapes; private boolean drawBones = true, drawRegionAttachments = true, drawBoundingBoxes = true, drawPoints = true; - private boolean drawMeshHull = true, drawMeshTriangles = true, drawPaths = true; + private boolean drawMeshHull = true, drawMeshTriangles = true, drawPaths = true, drawClipping = true; private final SkeletonBounds bounds = new SkeletonBounds(); private final FloatArray vertices = new FloatArray(32); private float scale = 1; @@ -179,6 +181,22 @@ public class SkeletonRendererDebug { } } + if (drawClipping) { + for (int i = 0, n = slots.size; i < n; i++) { + Slot slot = slots.get(i); + Attachment attachment = slot.attachment; + if (!(attachment instanceof ClippingAttachment)) continue; + ClippingAttachment clip = (ClippingAttachment)attachment; + int nn = clip.getWorldVerticesLength(); + float[] vertices = this.vertices.setSize(nn); + clip.computeWorldVertices(slot, 0, nn, vertices, 0, 2); + shapes.setColor(clippingLineColor); + for (int ii = 2; ii < nn; ii += 2) + shapes.line(vertices[ii - 2], vertices[ii - 1], vertices[ii], vertices[ii + 1]); + shapes.line(vertices[0], vertices[1], vertices[nn - 2], vertices[nn - 1]); + } + } + if (drawPaths) { for (int i = 0, n = slots.size; i < n; i++) { Slot slot = slots.get(i); diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/AtlasAttachmentLoader.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/AtlasAttachmentLoader.java index 127de6b57..0301f5fe1 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/AtlasAttachmentLoader.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/AtlasAttachmentLoader.java @@ -68,6 +68,10 @@ public class AtlasAttachmentLoader implements AttachmentLoader { return new BoundingBoxAttachment(name); } + public ClippingAttachment newClippingAttachment (Skin skin, String name) { + return new ClippingAttachment(name); + } + public PathAttachment newPathAttachment (Skin skin, String name) { return new PathAttachment(name); } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentLoader.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentLoader.java index d9594dd39..b18260ec4 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentLoader.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentLoader.java @@ -45,6 +45,9 @@ public interface AttachmentLoader { /** @return May be null to not load the attachment. */ public BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name); + + /** @return May be null to not load the attachment. */ + public ClippingAttachment newClippingAttachment (Skin skin, String name); /** @return May be null to not load the attachment. */ public PathAttachment newPathAttachment (Skin skin, String name); diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/ClippingAttachment.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/ClippingAttachment.java new file mode 100644 index 000000000..f1c719f98 --- /dev/null +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/ClippingAttachment.java @@ -0,0 +1,72 @@ +/****************************************************************************** + * 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.attachments; + +import com.badlogic.gdx.graphics.Color; +import com.esotericsoftware.spine.Slot; + +/** An attachment with vertices that make up a polygon used for clipping the rendering of other attachments. */ +public class ClippingAttachment extends VertexAttachment { + Slot end; + boolean invert; + + // Nonessential. + final Color color = new Color(0.38f, 0.94f, 0, 1); + + public ClippingAttachment (String name) { + super(name); + } + + /** Clipping is performed between the clipping polygon's slot and the end slot. */ + public Slot getEnd () { + return end; + } + + public void setEnd (Slot end) { + this.end = end; + } + + /** If false, attachments outside the clipping polygon will be drawn. If true, attachments inside the clipping polygon will be + * drawn. */ + public boolean getInvert () { + return invert; + } + + public void setInvert (boolean invert) { + this.invert = invert; + } + + /** The color of the clipping polygon as it was in Spine. Available only when nonessential data was exported. Clipping polygons + * are not usually rendered at runtime. */ + public Color getColor () { + return color; + } +}