diff --git a/spine-c/src/spine/SkeletonJson.c b/spine-c/src/spine/SkeletonJson.c index 8c611aa9b..d8a5d10e2 100644 --- a/spine-c/src/spine/SkeletonJson.c +++ b/spine-c/src/spine/SkeletonJson.c @@ -126,7 +126,7 @@ static void _spSkeletonJson_addLinkedMesh (spSkeletonJson* self, spAttachment* m internal->linkedMeshCapacity *= 2; if (internal->linkedMeshCapacity < 8) internal->linkedMeshCapacity = 8; linkedMeshes = MALLOC(_spLinkedMesh, internal->linkedMeshCapacity); - memcpy(linkedMeshes, internal->linkedMeshes, internal->linkedMeshCount); + memcpy(linkedMeshes, internal->linkedMeshes, sizeof(_spLinkedMesh) * internal->linkedMeshCount); FREE(internal->linkedMeshes); internal->linkedMeshes = linkedMeshes; } diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AnimationStateTest.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AnimationStateTest.java index 2b571bd61..67503c58a 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AnimationStateTest.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AnimationStateTest.java @@ -37,13 +37,13 @@ import com.badlogic.gdx.utils.Array; import com.esotericsoftware.spine.AnimationState.AnimationStateListener; import com.esotericsoftware.spine.attachments.AttachmentLoader; import com.esotericsoftware.spine.attachments.BoundingBoxAttachment; -import com.esotericsoftware.spine.attachments.MeshAttachment; import com.esotericsoftware.spine.attachments.RegionAttachment; -import com.esotericsoftware.spine.attachments.WeightedMeshAttachment; +import com.esotericsoftware.spine.attachments.MeshAttachment; +import com.esotericsoftware.spine.attachments.PathAttachment; public class AnimationStateTest { final SkeletonJson json = new SkeletonJson(new AttachmentLoader() { - public WeightedMeshAttachment newWeightedMeshAttachment (Skin skin, String name, String path) { + public MeshAttachment newMeshAttachment (Skin skin, String name, String path) { return null; } @@ -51,11 +51,11 @@ public class AnimationStateTest { return null; } - public MeshAttachment newMeshAttachment (Skin skin, String name, String path) { + public BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name) { return null; } - public BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name) { + public PathAttachment newPathAttachment (Skin skin, String name) { return null; } }); diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AttachmentTimelineTests.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AttachmentTimelineTests.java index 2134db0e5..2b3cdaa7a 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AttachmentTimelineTests.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AttachmentTimelineTests.java @@ -46,10 +46,10 @@ public class AttachmentTimelineTests { public AttachmentTimelineTests () { skeletonData = new SkeletonData(); - BoneData boneData = new BoneData("bone", null); + BoneData boneData = new BoneData(0, "bone", null); skeletonData.getBones().add(boneData); - skeletonData.getSlots().add(new SlotData("slot", boneData)); + skeletonData.getSlots().add(new SlotData(0, "slot", boneData)); Attachment attachment1 = new Attachment("attachment1") {}; Attachment attachment2 = new Attachment("attachment2") {}; 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 2eb022270..b96161237 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 @@ -34,18 +34,14 @@ 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.MeshAttachment; import com.esotericsoftware.spine.attachments.RegionAttachment; -import com.esotericsoftware.spine.attachments.WeightedMeshAttachment; +import com.esotericsoftware.spine.attachments.MeshAttachment; +import com.esotericsoftware.spine.attachments.PathAttachment; public class BonePlotting { static public void main (String[] args) throws Exception { // This example shows how to load skeleton data and plot a bone transform for each animation. SkeletonJson json = new SkeletonJson(new AttachmentLoader() { - public WeightedMeshAttachment newWeightedMeshAttachment (Skin skin, String name, String path) { - return null; - } - public RegionAttachment newRegionAttachment (Skin skin, String name, String path) { return null; } @@ -57,6 +53,10 @@ public class BonePlotting { public BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name) { return null; } + + public PathAttachment newPathAttachment (Skin skin, String name) { + return null; + } }); SkeletonData skeletonData = json.readSkeletonData(new FileHandle("assets/spineboy/spineboy.json")); Skeleton skeleton = new Skeleton(skeletonData); diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java index a05848644..baefe5c3f 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -36,7 +36,7 @@ import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.FloatArray; import com.esotericsoftware.spine.attachments.Attachment; -import com.esotericsoftware.spine.attachments.FfdAttachment; +import com.esotericsoftware.spine.attachments.VertexAttachment; public class Animation { final String name; @@ -66,7 +66,7 @@ public class Animation { /** Poses the skeleton at the specified time for this animation. * @param lastTime The last time the animation was applied. - * @param events Any triggered events are added. */ + * @param events Any triggered events are added. May be null. */ public void apply (Skeleton skeleton, float lastTime, float time, boolean loop, Array events) { if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); @@ -82,7 +82,7 @@ public class Animation { /** Poses the skeleton at the specified time for this animation mixed with the current pose. * @param lastTime The last time the animation was applied. - * @param events Any triggered events are added. + * @param events Any triggered events are added. May be null. * @param alpha The amount of this animation that affects the current pose. */ public void mix (Skeleton skeleton, float lastTime, float time, boolean loop, Array events, float alpha) { if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); @@ -154,7 +154,7 @@ public class Animation { /** Base class for frames that use an interpolation bezier curve. */ abstract static public class CurveTimeline implements Timeline { static public final float LINEAR = 0, STEPPED = 1, BEZIER = 2; - static private final int BEZIER_SEGMENTS = 10, BEZIER_SIZE = BEZIER_SEGMENTS * 2 - 1; + static private final int BEZIER_SIZE = 10 * 2 - 1; private final float[] curves; // type, x, y, ... @@ -188,12 +188,10 @@ public class Animation { * cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of * the difference between the keyframe's values. */ public void setCurve (int frameIndex, float cx1, float cy1, float cx2, float cy2) { - float subdiv1 = 1f / BEZIER_SEGMENTS, subdiv2 = subdiv1 * subdiv1, subdiv3 = subdiv2 * subdiv1; - float pre1 = 3 * subdiv1, pre2 = 3 * subdiv2, pre4 = 6 * subdiv2, pre5 = 6 * subdiv3; - float tmp1x = -cx1 * 2 + cx2, tmp1y = -cy1 * 2 + cy2, tmp2x = (cx1 - cx2) * 3 + 1, tmp2y = (cy1 - cy2) * 3 + 1; - float dfx = cx1 * pre1 + tmp1x * pre2 + tmp2x * subdiv3, dfy = cy1 * pre1 + tmp1y * pre2 + tmp2y * subdiv3; - float ddfx = tmp1x * pre4 + tmp2x * pre5, ddfy = tmp1y * pre4 + tmp2y * pre5; - float dddfx = tmp2x * pre5, dddfy = tmp2y * pre5; + float tmpx = (-cx1 * 2 + cx2) * 0.03f, tmpy = (-cy1 * 2 + cy2) * 0.03f; + float dddfx = ((cx1 - cx2) * 3 + 1) * 0.006f, dddfy = ((cy1 - cy2) * 3 + 1) * 0.006f; + float ddfx = tmpx * 2 + dddfx, ddfy = tmpy * 2 + dddfy; + float dfx = cx1 * 0.3f + tmpx + dddfx * 0.16666667f, dfy = cy1 * 0.3f + tmpy + dddfy * 0.16666667f; int i = frameIndex * BEZIER_SIZE; float[] curves = this.curves; @@ -213,6 +211,7 @@ public class Animation { } public float getCurvePercent (int frameIndex, float percent) { + percent = MathUtils.clamp(percent, 0, 1); float[] curves = this.curves; int i = frameIndex * BEZIER_SIZE; float type = curves[i]; @@ -240,19 +239,21 @@ public class Animation { } static public class RotateTimeline extends CurveTimeline { - static final int PREV_TIME = -2; - static final int VALUE = 1; + static public final int ENTRIES = 2; + static private final int PREV_TIME = -2, PREV_ROTATION = -1; + static private final int ROTATION = 1; int boneIndex; - final float[] frames; // time, angle, ... + final float[] frames; // time, degrees, ... public RotateTimeline (int frameCount) { super(frameCount); frames = new float[frameCount << 1]; } - public void setBoneIndex (int boneIndex) { - this.boneIndex = boneIndex; + public void setBoneIndex (int index) { + if (index < 0) throw new IllegalArgumentException("index must be >= 0."); + this.boneIndex = index; } public int getBoneIndex () { @@ -264,10 +265,10 @@ public class Animation { } /** Sets the time and angle of the specified keyframe. */ - public void setFrame (int frameIndex, float time, float angle) { - frameIndex *= 2; + public void setFrame (int frameIndex, float time, float degrees) { + frameIndex <<= 1; frames[frameIndex] = time; - frames[frameIndex + 1] = angle; + frames[frameIndex + ROTATION] = degrees; } public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha) { @@ -276,8 +277,8 @@ public class Animation { Bone bone = skeleton.bones.get(boneIndex); - if (time >= frames[frames.length - 2]) { // Time is after last frame. - float amount = bone.data.rotation + frames[frames.length - 1] - bone.rotation; + if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. + float amount = bone.data.rotation + frames[frames.length + PREV_ROTATION] - bone.rotation; while (amount > 180) amount -= 360; while (amount < -180) @@ -287,18 +288,17 @@ public class Animation { } // Interpolate between the previous frame and the current frame. - int frame = binarySearch(frames, time, 2); - float prevFrameValue = frames[frame - 1]; + int frame = binarySearch(frames, time, ENTRIES); + float prevRotation = frames[frame + PREV_ROTATION]; float frameTime = frames[frame]; - float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime), 0, 1); - percent = getCurvePercent((frame >> 1) - 1, percent); + float percent = getCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - float amount = frames[frame + VALUE] - prevFrameValue; + float amount = frames[frame + ROTATION] - prevRotation; while (amount > 180) amount -= 360; while (amount < -180) amount += 360; - amount = bone.data.rotation + (prevFrameValue + amount * percent) - bone.rotation; + amount = bone.data.rotation + (prevRotation + amount * percent) - bone.rotation; while (amount > 180) amount -= 360; while (amount < -180) @@ -308,20 +308,21 @@ public class Animation { } static public class TranslateTimeline extends CurveTimeline { - static final int PREV_TIME = -3; - static final int X = 1; - static final int Y = 2; + static public final int ENTRIES = 3; + static final int PREV_TIME = -3, PREV_X = -2, PREV_Y = -1; + static final int X = 1, Y = 2; int boneIndex; final float[] frames; // time, x, y, ... public TranslateTimeline (int frameCount) { super(frameCount); - frames = new float[frameCount * 3]; + frames = new float[frameCount * ENTRIES]; } - public void setBoneIndex (int boneIndex) { - this.boneIndex = boneIndex; + public void setBoneIndex (int index) { + if (index < 0) throw new IllegalArgumentException("index must be >= 0."); + this.boneIndex = index; } public int getBoneIndex () { @@ -334,10 +335,10 @@ public class Animation { /** Sets the time and value of the specified keyframe. */ public void setFrame (int frameIndex, float time, float x, float y) { - frameIndex *= 3; + frameIndex *= ENTRIES; frames[frameIndex] = time; - frames[frameIndex + 1] = x; - frames[frameIndex + 2] = y; + frames[frameIndex + X] = x; + frames[frameIndex + Y] = y; } public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha) { @@ -346,22 +347,21 @@ public class Animation { Bone bone = skeleton.bones.get(boneIndex); - if (time >= frames[frames.length - 3]) { // Time is after last frame. - bone.x += (bone.data.x + frames[frames.length - 2] - bone.x) * alpha; - bone.y += (bone.data.y + frames[frames.length - 1] - bone.y) * alpha; + if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. + bone.x += (bone.data.x + frames[frames.length + PREV_X] - bone.x) * alpha; + bone.y += (bone.data.y + frames[frames.length + PREV_Y] - bone.y) * alpha; return; } // Interpolate between the previous frame and the current frame. - int frame = binarySearch(frames, time, 3); - float prevFrameX = frames[frame - 2]; - float prevFrameY = frames[frame - 1]; + int frame = binarySearch(frames, time, ENTRIES); + float prevX = frames[frame + PREV_X]; + float prevY = frames[frame + PREV_Y]; float frameTime = frames[frame]; - float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime), 0, 1); - percent = getCurvePercent(frame / 3 - 1, percent); + float percent = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - bone.x += (bone.data.x + prevFrameX + (frames[frame + X] - prevFrameX) * percent - bone.x) * alpha; - bone.y += (bone.data.y + prevFrameY + (frames[frame + Y] - prevFrameY) * percent - bone.y) * alpha; + bone.x += (bone.data.x + prevX + (frames[frame + X] - prevX) * percent - bone.x) * alpha; + bone.y += (bone.data.y + prevY + (frames[frame + Y] - prevY) * percent - bone.y) * alpha; } } @@ -375,22 +375,21 @@ public class Animation { if (time < frames[0]) return; // Time is before first frame. Bone bone = skeleton.bones.get(boneIndex); - if (time >= frames[frames.length - 3]) { // Time is after last frame. - bone.scaleX += (bone.data.scaleX * frames[frames.length - 2] - bone.scaleX) * alpha; - bone.scaleY += (bone.data.scaleY * frames[frames.length - 1] - bone.scaleY) * alpha; + if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. + bone.scaleX += (bone.data.scaleX * frames[frames.length + PREV_X] - bone.scaleX) * alpha; + bone.scaleY += (bone.data.scaleY * frames[frames.length + PREV_Y] - bone.scaleY) * alpha; return; } // Interpolate between the previous frame and the current frame. - int frame = binarySearch(frames, time, 3); - float prevFrameX = frames[frame - 2]; - float prevFrameY = frames[frame - 1]; + int frame = binarySearch(frames, time, ENTRIES); + float prevX = frames[frame + PREV_X]; + float prevY = frames[frame + PREV_Y]; float frameTime = frames[frame]; - float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime), 0, 1); - percent = getCurvePercent(frame / 3 - 1, percent); + float percent = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - bone.scaleX += (bone.data.scaleX * (prevFrameX + (frames[frame + X] - prevFrameX) * percent) - bone.scaleX) * alpha; - bone.scaleY += (bone.data.scaleY * (prevFrameY + (frames[frame + Y] - prevFrameY) * percent) - bone.scaleY) * alpha; + bone.scaleX += (bone.data.scaleX * (prevX + (frames[frame + X] - prevX) * percent) - bone.scaleX) * alpha; + bone.scaleY += (bone.data.scaleY * (prevY + (frames[frame + Y] - prevY) * percent) - bone.scaleY) * alpha; } } @@ -404,42 +403,40 @@ public class Animation { if (time < frames[0]) return; // Time is before first frame. Bone bone = skeleton.bones.get(boneIndex); - if (time >= frames[frames.length - 3]) { // Time is after last frame. - bone.shearX += (bone.data.shearX + frames[frames.length - 2] - bone.shearX) * alpha; - bone.shearY += (bone.data.shearY + frames[frames.length - 1] - bone.shearY) * alpha; + if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. + bone.shearX += (bone.data.shearX + frames[frames.length + PREV_X] - bone.shearX) * alpha; + bone.shearY += (bone.data.shearY + frames[frames.length + PREV_Y] - bone.shearY) * alpha; return; } // Interpolate between the previous frame and the current frame. - int frame = binarySearch(frames, time, 3); - float prevFrameX = frames[frame - 2]; - float prevFrameY = frames[frame - 1]; + int frame = binarySearch(frames, time, ENTRIES); + float prevX = frames[frame + PREV_X]; + float prevY = frames[frame + PREV_Y]; float frameTime = frames[frame]; - float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime), 0, 1); - percent = getCurvePercent(frame / 3 - 1, percent); + float percent = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - bone.shearX += (bone.data.shearX + (prevFrameX + (frames[frame + X] - prevFrameX) * percent) - bone.shearX) * alpha; - bone.shearY += (bone.data.shearY + (prevFrameY + (frames[frame + Y] - prevFrameY) * percent) - bone.shearY) * alpha; + bone.shearX += (bone.data.shearX + (prevX + (frames[frame + X] - prevX) * percent) - bone.shearX) * alpha; + bone.shearY += (bone.data.shearY + (prevY + (frames[frame + Y] - prevY) * percent) - bone.shearY) * alpha; } } static public class ColorTimeline extends CurveTimeline { - static private final int PREV_TIME = -5; - static private final int R = 1; - static private final int G = 2; - static private final int B = 3; - static private final int A = 4; + static public final int ENTRIES = 5; + static private final int PREV_TIME = -5, PREV_R = -4, PREV_G = -3, PREV_B = -2, PREV_A = -1; + static private final int R = 1, G = 2, B = 3, A = 4; int slotIndex; private final float[] frames; // time, r, g, b, a, ... public ColorTimeline (int frameCount) { super(frameCount); - frames = new float[frameCount * 5]; + frames = new float[frameCount * ENTRIES]; } - public void setSlotIndex (int slotIndex) { - this.slotIndex = slotIndex; + public void setSlotIndex (int index) { + if (index < 0) throw new IllegalArgumentException("index must be >= 0."); + this.slotIndex = index; } public int getSlotIndex () { @@ -452,12 +449,12 @@ public class Animation { /** Sets the time and value of the specified keyframe. */ public void setFrame (int frameIndex, float time, float r, float g, float b, float a) { - frameIndex *= 5; + frameIndex *= ENTRIES; frames[frameIndex] = time; - frames[frameIndex + 1] = r; - frames[frameIndex + 2] = g; - frames[frameIndex + 3] = b; - frames[frameIndex + 4] = a; + frames[frameIndex + R] = r; + frames[frameIndex + G] = g; + frames[frameIndex + B] = b; + frames[frameIndex + A] = a; } public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha) { @@ -465,23 +462,23 @@ public class Animation { if (time < frames[0]) return; // Time is before first frame. float r, g, b, a; - if (time >= frames[frames.length - 5]) { // Time is after last frame. - int i = frames.length - 1; - r = frames[i - 3]; - g = frames[i - 2]; - b = frames[i - 1]; - a = frames[i]; + if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. + int i = frames.length; + r = frames[i + PREV_R]; + g = frames[i + PREV_G]; + b = frames[i + PREV_B]; + a = frames[i + PREV_A]; } else { // Interpolate between the previous frame and the current frame. - int frame = binarySearch(frames, time, 5); + int frame = binarySearch(frames, time, ENTRIES); + r = frames[frame + PREV_R]; + g = frames[frame + PREV_G]; + b = frames[frame + PREV_B]; + a = frames[frame + PREV_A]; float frameTime = frames[frame]; - float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime), 0, 1); - percent = getCurvePercent(frame / 5 - 1, percent); + float percent = getCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - r = frames[frame - 4]; - g = frames[frame - 3]; - b = frames[frame - 2]; - a = frames[frame - 1]; r += (frames[frame + R] - r) * percent; g += (frames[frame + G] - g) * percent; b += (frames[frame + B] - b) * percent; @@ -509,12 +506,13 @@ public class Animation { return frames.length; } - public int getSlotIndex () { - return slotIndex; + public void setSlotIndex (int index) { + if (index < 0) throw new IllegalArgumentException("index must be >= 0."); + this.slotIndex = index; } - public void setSlotIndex (int slotIndex) { - this.slotIndex = slotIndex; + public int getSlotIndex () { + return slotIndex; } public float[] getFrames () { @@ -654,27 +652,28 @@ public class Animation { } } - static public class FfdTimeline extends CurveTimeline { + static public class DeformTimeline extends CurveTimeline { private final float[] frames; // time, ... private final float[][] frameVertices; int slotIndex; - Attachment attachment; + VertexAttachment attachment; - public FfdTimeline (int frameCount) { + public DeformTimeline (int frameCount) { super(frameCount); frames = new float[frameCount]; frameVertices = new float[frameCount][]; } - public void setSlotIndex (int slotIndex) { - this.slotIndex = slotIndex; + public void setSlotIndex (int index) { + if (index < 0) throw new IllegalArgumentException("index must be >= 0."); + this.slotIndex = index; } public int getSlotIndex () { return slotIndex; } - public void setAttachment (Attachment attachment) { + public void setAttachment (VertexAttachment attachment) { this.attachment = attachment; } @@ -699,7 +698,7 @@ public class Animation { public void apply (Skeleton skeleton, float lastTime, float time, Array firedEvents, float alpha) { Slot slot = skeleton.slots.get(slotIndex); Attachment slotAttachment = slot.getAttachment(); - if (!(slotAttachment instanceof FfdAttachment) || !((FfdAttachment)slotAttachment).applyFFD(attachment)) return; + if (!(slotAttachment instanceof VertexAttachment) || !((VertexAttachment)slotAttachment).applyDeform(attachment)) return; float[] frames = this.frames; if (time < frames[0]) return; // Time is before first frame. @@ -709,10 +708,7 @@ public class Animation { FloatArray verticesArray = slot.getAttachmentVertices(); if (verticesArray.size != vertexCount) alpha = 1; // Don't mix from uninitialized slot vertices. - verticesArray.size = 0; - verticesArray.ensureCapacity(vertexCount); - verticesArray.size = vertexCount; - float[] vertices = verticesArray.items; + float[] vertices = verticesArray.setSize(vertexCount); if (time >= frames[frames.length - 1]) { // Time is after last frame. float[] lastVertices = frameVertices[frames.length - 1]; @@ -726,12 +722,11 @@ public class Animation { // Interpolate between the previous frame and the current frame. int frame = binarySearch(frames, time); - float frameTime = frames[frame]; - float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frame - 1] - frameTime), 0, 1); - percent = getCurvePercent(frame - 1, percent); - float[] prevVertices = frameVertices[frame - 1]; float[] nextVertices = frameVertices[frame]; + float frameTime = frames[frame]; + float percent = getCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime)); + if (alpha < 1) { for (int i = 0; i < vertexCount; i++) { float prev = prevVertices[i]; @@ -747,21 +742,21 @@ public class Animation { } static public class IkConstraintTimeline extends CurveTimeline { - static private final int PREV_TIME = -3; - static private final int PREV_MIX = -2; - static private final int PREV_BEND_DIRECTION = -1; - static private final int MIX = 1; + static public final int ENTRIES = 3; + static private final int PREV_TIME = -3, PREV_MIX = -2, PREV_BEND_DIRECTION = -1; + static private final int MIX = 1, BEND_DIRECTION = 2; int ikConstraintIndex; private final float[] frames; // time, mix, bendDirection, ... public IkConstraintTimeline (int frameCount) { super(frameCount); - frames = new float[frameCount * 3]; + frames = new float[frameCount * ENTRIES]; } - public void setIkConstraintIndex (int ikConstraint) { - this.ikConstraintIndex = ikConstraint; + public void setIkConstraintIndex (int index) { + if (index < 0) throw new IllegalArgumentException("index must be >= 0."); + this.ikConstraintIndex = index; } public int getIkConstraintIndex () { @@ -774,10 +769,10 @@ public class Animation { /** Sets the time, mix and bend direction of the specified keyframe. */ public void setFrame (int frameIndex, float time, float mix, int bendDirection) { - frameIndex *= 3; + frameIndex *= ENTRIES; frames[frameIndex] = time; - frames[frameIndex + 1] = mix; - frames[frameIndex + 2] = bendDirection; + frames[frameIndex + MIX] = mix; + frames[frameIndex + BEND_DIRECTION] = bendDirection; } public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha) { @@ -786,45 +781,39 @@ public class Animation { IkConstraint constraint = skeleton.ikConstraints.get(ikConstraintIndex); - if (time >= frames[frames.length - 3]) { // Time is after last frame. + if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. constraint.mix += (frames[frames.length + PREV_MIX] - constraint.mix) * alpha; constraint.bendDirection = (int)frames[frames.length + PREV_BEND_DIRECTION]; return; } // Interpolate between the previous frame and the current frame. - int frame = binarySearch(frames, time, 3); - float frameTime = frames[frame]; - float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime), 0, 1); - percent = getCurvePercent(frame / 3 - 1, percent); - + int frame = binarySearch(frames, time, ENTRIES); float mix = frames[frame + PREV_MIX]; + float frameTime = frames[frame]; + float percent = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha; constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION]; } } static public class TransformConstraintTimeline extends CurveTimeline { - static private final int PREV_TIME = -5; - static private final int PREV_ROTATE_MIX = -4; - static private final int PREV_TRANSLATE_MIX = -3; - static private final int PREV_SCALE_MIX = -2; - static private final int PREV_SHEAR_MIX = -1; - static private final int ROTATE_MIX = 1; - static private final int TRANSLATE_MIX = 2; - static private final int SCALE_MIX = 3; - static private final int SHEAR_MIX = 4; + static public final int ENTRIES = 5; + static private final int PREV_TIME = -5, PREV_ROTATE = -4, PREV_TRANSLATE = -3, PREV_SCALE = -2, PREV_SHEAR = -1; + static private final int ROTATE = 1, TRANSLATE = 2, SCALE = 3, SHEAR = 4; int transformConstraintIndex; private final float[] frames; // time, rotate mix, translate mix, scale mix, shear mix, ... public TransformConstraintTimeline (int frameCount) { super(frameCount); - frames = new float[frameCount * 5]; + frames = new float[frameCount * ENTRIES]; } - public void setTransformConstraintIndex (int ikConstraint) { - this.transformConstraintIndex = ikConstraint; + public void setTransformConstraintIndex (int index) { + if (index < 0) throw new IllegalArgumentException("index must be >= 0."); + this.transformConstraintIndex = index; } public int getTransformConstraintIndex () { @@ -837,7 +826,7 @@ public class Animation { /** Sets the time and mixes of the specified keyframe. */ public void setFrame (int frameIndex, float time, float rotateMix, float translateMix, float scaleMix, float shearMix) { - frameIndex *= 5; + frameIndex *= ENTRIES; frames[frameIndex] = time; frames[frameIndex + 1] = rotateMix; frames[frameIndex + 2] = translateMix; @@ -851,30 +840,173 @@ public class Animation { TransformConstraint constraint = skeleton.transformConstraints.get(transformConstraintIndex); - if (time >= frames[frames.length - 5]) { // Time is after last frame. - int i = frames.length - 1; - constraint.rotateMix += (frames[i - 3] - constraint.rotateMix) * alpha; - constraint.translateMix += (frames[i - 2] - constraint.translateMix) * alpha; - constraint.scaleMix += (frames[i - 1] - constraint.scaleMix) * alpha; - constraint.shearMix += (frames[i] - constraint.shearMix) * alpha; + if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. + int i = frames.length; + constraint.rotateMix += (frames[i + PREV_ROTATE] - constraint.rotateMix) * alpha; + constraint.translateMix += (frames[i + PREV_TRANSLATE] - constraint.translateMix) * alpha; + constraint.scaleMix += (frames[i + PREV_SCALE] - constraint.scaleMix) * alpha; + constraint.shearMix += (frames[i + PREV_SHEAR] - constraint.shearMix) * alpha; return; } // Interpolate between the previous frame and the current frame. - int frame = binarySearch(frames, time, 5); + int frame = binarySearch(frames, time, ENTRIES); float frameTime = frames[frame]; - float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime), 0, 1); - percent = getCurvePercent(frame / 5 - 1, percent); + float percent = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - float rotate = frames[frame + PREV_ROTATE_MIX]; - float translate = frames[frame + PREV_TRANSLATE_MIX]; - float scale = frames[frame + PREV_SCALE_MIX]; - float shear = frames[frame + PREV_SHEAR_MIX]; - constraint.rotateMix += (rotate + (frames[frame + ROTATE_MIX] - rotate) * percent - constraint.rotateMix) * alpha; - constraint.translateMix += (translate + (frames[frame + TRANSLATE_MIX] - translate) * percent - constraint.translateMix) + float rotate = frames[frame + PREV_ROTATE]; + float translate = frames[frame + PREV_TRANSLATE]; + float scale = frames[frame + PREV_SCALE]; + float shear = frames[frame + PREV_SHEAR]; + constraint.rotateMix += (rotate + (frames[frame + ROTATE] - rotate) * percent - constraint.rotateMix) * alpha; + constraint.translateMix += (translate + (frames[frame + TRANSLATE] - translate) * percent - constraint.translateMix) + * alpha; + constraint.scaleMix += (scale + (frames[frame + SCALE] - scale) * percent - constraint.scaleMix) * alpha; + constraint.shearMix += (shear + (frames[frame + SHEAR] - shear) * percent - constraint.shearMix) * alpha; + } + } + + static public class PathConstraintPositionTimeline extends CurveTimeline { + static public final int ENTRIES = 2; + static final int PREV_TIME = -2, PREV_VALUE = -1; + static final int VALUE = 1; + + int pathConstraintIndex; + + final float[] frames; // time, position, ... + + public PathConstraintPositionTimeline (int frameCount) { + super(frameCount); + frames = new float[frameCount * ENTRIES]; + } + + public void setPathConstraintIndex (int index) { + if (index < 0) throw new IllegalArgumentException("index must be >= 0."); + this.pathConstraintIndex = index; + } + + public int getPathConstraintIndex () { + return pathConstraintIndex; + } + + public float[] getFrames () { + return frames; + } + + /** Sets the time and value of the specified keyframe. */ + public void setFrame (int frameIndex, float time, float value) { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + VALUE] = value; + } + + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha) { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + PathConstraint constraint = skeleton.pathConstraints.get(pathConstraintIndex); + + if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. + int i = frames.length; + constraint.position += (frames[i + PREV_VALUE] - constraint.position) * alpha; + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = binarySearch(frames, time, ENTRIES); + float position = frames[frame + PREV_VALUE]; + float frameTime = frames[frame]; + float percent = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + constraint.position += (position + (frames[frame + VALUE] - position) * percent - constraint.position) * alpha; + } + } + + static public class PathConstraintSpacingTimeline extends PathConstraintPositionTimeline { + public PathConstraintSpacingTimeline (int frameCount) { + super(frameCount); + } + + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha) { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + PathConstraint constraint = skeleton.pathConstraints.get(pathConstraintIndex); + + if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. + int i = frames.length; + constraint.spacing += (frames[i + PREV_VALUE] - constraint.spacing) * alpha; + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = binarySearch(frames, time, ENTRIES); + float spacing = frames[frame + PREV_VALUE]; + float frameTime = frames[frame]; + float percent = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + constraint.spacing += (spacing + (frames[frame + VALUE] - spacing) * percent - constraint.spacing) * alpha; + } + } + + static public class PathConstraintMixTimeline extends CurveTimeline { + static public final int ENTRIES = 3; + static private final int PREV_TIME = -3, PREV_ROTATE = -2, PREV_TRANSLATE = -1; + static private final int ROTATE = 1, TRANSLATE = 2; + + int pathConstraintIndex; + + private final float[] frames; // time, rotate mix, translate mix, ... + + public PathConstraintMixTimeline (int frameCount) { + super(frameCount); + frames = new float[frameCount * ENTRIES]; + } + + public void setPathConstraintIndex (int index) { + if (index < 0) throw new IllegalArgumentException("index must be >= 0."); + this.pathConstraintIndex = index; + } + + public int getPathConstraintIndex () { + return pathConstraintIndex; + } + + public float[] getFrames () { + return frames; + } + + /** Sets the time and mixes of the specified keyframe. */ + public void setFrame (int frameIndex, float time, float rotateMix, float translateMix) { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + ROTATE] = rotateMix; + frames[frameIndex + TRANSLATE] = translateMix; + } + + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha) { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + PathConstraint constraint = skeleton.pathConstraints.get(pathConstraintIndex); + + if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. + int i = frames.length; + constraint.rotateMix += (frames[i + PREV_ROTATE] - constraint.rotateMix) * alpha; + constraint.translateMix += (frames[i + PREV_TRANSLATE] - constraint.translateMix) * alpha; + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = binarySearch(frames, time, ENTRIES); + float rotate = frames[frame + PREV_ROTATE]; + float translate = frames[frame + PREV_TRANSLATE]; + float frameTime = frames[frame]; + float percent = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + constraint.rotateMix += (rotate + (frames[frame + ROTATE] - rotate) * percent - constraint.rotateMix) * alpha; + constraint.translateMix += (translate + (frames[frame + TRANSLATE] - translate) * percent - constraint.translateMix) * alpha; - constraint.scaleMix += (scale + (frames[frame + SCALE_MIX] - scale) * percent - constraint.scaleMix) * alpha; - constraint.shearMix += (shear + (frames[frame + SHEAR_MIX] - shear) * percent - constraint.shearMix) * alpha; } } } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationStateData.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationStateData.java index 84a2151f4..d0a806726 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationStateData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationStateData.java @@ -41,6 +41,7 @@ public class AnimationStateData { float defaultMix; public AnimationStateData (SkeletonData skeletonData) { + if (skeletonData == null) throw new IllegalArgumentException("skeletonData cannot be null."); this.skeletonData = skeletonData; } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java index ef52a0712..f0e61f622 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java @@ -36,23 +36,21 @@ import static com.badlogic.gdx.math.Matrix3.*; import com.badlogic.gdx.math.Matrix3; import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.Array; public class Bone implements Updatable { final BoneData data; final Skeleton skeleton; final Bone parent; + final Array children = new Array(); float x, y, rotation, scaleX, scaleY, shearX, shearY; - float appliedRotation, appliedScaleX, appliedScaleY; + float appliedRotation; float a, b, worldX; float c, d, worldY; float worldSignX, worldSignY; - Bone (BoneData data) { - this.data = data; - parent = null; - skeleton = null; - } + boolean sorted; /** @param parent May be null. */ public Bone (BoneData data, Skeleton skeleton, Bone parent) { @@ -68,6 +66,7 @@ public class Bone implements Updatable { * @param parent May be null. */ public Bone (Bone bone, Skeleton skeleton, Bone parent) { if (bone == null) throw new IllegalArgumentException("bone cannot be null."); + if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); this.skeleton = skeleton; this.parent = parent; data = bone.data; @@ -85,16 +84,14 @@ public class Bone implements Updatable { updateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); } - /** Computes the world SRT using the parent bone and this bone's local SRT. */ + /** Computes the world transform using the parent bone and this bone's local transform. */ public void updateWorldTransform () { updateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); } - /** Computes the world SRT using the parent bone and the specified local SRT. */ + /** Computes the world transform using the parent bone and the specified local transform. */ public void updateWorldTransform (float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) { appliedRotation = rotation; - appliedScaleX = scaleX; - appliedScaleY = scaleY; float rotationY = rotation + 90 + shearY; float la = cosDeg(rotation + shearX) * scaleX, lb = cosDeg(rotationY) * scaleY; @@ -144,10 +141,10 @@ public class Bone implements Updatable { do { float cos = cosDeg(parent.appliedRotation), sin = sinDeg(parent.appliedRotation); float temp = pa * cos + pb * sin; - pb = pa * -sin + pb * cos; + pb = pb * cos - pa * sin; pa = temp; temp = pc * cos + pd * sin; - pd = pc * -sin + pd * cos; + pd = pd * cos - pc * sin; pc = temp; if (!parent.data.inheritRotation) break; @@ -163,24 +160,22 @@ public class Bone implements Updatable { pc = 0; pd = 1; do { - float r = parent.appliedRotation, cos = cosDeg(r), sin = sinDeg(r); - float psx = parent.appliedScaleX, psy = parent.appliedScaleY; - float za = cos * psx, zb = -sin * psy, zc = sin * psx, zd = cos * psy; + float cos = cosDeg(parent.appliedRotation), sin = sinDeg(parent.appliedRotation); + float psx = parent.scaleX, psy = parent.scaleY; + float za = cos * psx, zb = sin * psy, zc = sin * psx, zd = cos * psy; float temp = pa * za + pb * zc; - pb = pa * zb + pb * zd; + pb = pb * zd - pa * zb; pa = temp; temp = pc * za + pd * zc; - pd = pc * zb + pd * zd; + pd = pd * zd - pc * zb; pc = temp; - if (psx < 0) r = -r; - cos = cosDeg(-r); - sin = sinDeg(-r); + if (psx >= 0) sin = -sin; temp = pa * cos + pb * sin; - pb = pa * -sin + pb * cos; + pb = pb * cos - pa * sin; pa = temp; temp = pc * cos + pd * sin; - pd = pc * -sin + pd * cos; + pd = pd * cos - pc * sin; pc = temp; if (!parent.data.inheritScale) break; @@ -230,6 +225,10 @@ public class Bone implements Updatable { return parent; } + public Array getChildren () { + return children; + } + public float getX () { return x; } @@ -349,6 +348,76 @@ public class Bone implements Updatable { return (float)Math.sqrt(c * c + d * d) * worldSignY; } + public float worldToLocalRotationX () { + Bone parent = this.parent; + if (parent == null) return rotation; + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, a = this.a, c = this.c; + return atan2(pa * c - pc * a, pd * a - pb * c) * radDeg; + } + + public float worldToLocalRotationY () { + Bone parent = this.parent; + if (parent == null) return rotation; + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, b = this.b, d = this.d; + return atan2(pa * d - pc * b, pd * b - pb * d) * radDeg; + } + + public void rotateWorld (float degrees) { + float a = this.a, b = this.b, c = this.c, d = this.d; + float cos = cosDeg(degrees), sin = sinDeg(degrees); + this.a = cos * a - sin * c; + this.b = cos * b - sin * d; + this.c = sin * a + cos * c; + this.d = sin * b + cos * d; + } + + /** Computes the local transform from the world transform. This can be useful to perform processing on the local transform + * after the world transform has been modified directly (eg, by a constraint). + *

+ * Some redundant information is lost by the world transform, such as -1,-1 scale versus 180 rotation. The computed local + * transform values may differ from the original values but are functionally the same. */ + public void updateLocalTransform () { + Bone parent = this.parent; + if (parent == null) { + x = worldX; + y = worldY; + rotation = atan2(c, a) * radDeg; + scaleX = (float)Math.sqrt(a * a + c * c); + scaleY = (float)Math.sqrt(b * b + d * d); + float det = a * d - b * c; + shearX = 0; + shearY = atan2(a * b + c * d, det) * radDeg; + return; + } + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; + float pid = 1 / (pa * pd - pb * pc); + float dx = worldX - parent.worldX, dy = worldY - parent.worldY; + x = (dx * pd * pid - dy * pb * pid); + y = (dy * pa * pid - dx * pc * pid); + float ia = pid * pd; + float id = pid * pa; + float ib = pid * pb; + float ic = pid * pc; + float ra = ia * a - ib * c; + float rb = ia * b - ib * d; + float rc = id * c - ic * a; + float rd = id * d - ic * b; + shearX = 0; + scaleX = (float)Math.sqrt(ra * ra + rc * rc); + if (scaleX > 0.0001f) { + float det = ra * rd - rb * rc; + scaleY = det / scaleX; + shearY = atan2(ra * rb + rc * rd, det) * radDeg; + rotation = atan2(rc, ra) * radDeg; + } else { + scaleX = 0; + scaleY = (float)Math.sqrt(rb * rb + rd * rd); + shearY = 0; + rotation = 90 - atan2(rd, rb) * radDeg; + } + appliedRotation = rotation; + } + public Matrix3 getWorldTransform (Matrix3 worldTransform) { if (worldTransform == null) throw new IllegalArgumentException("worldTransform cannot be null."); float[] val = worldTransform.val; @@ -365,9 +434,9 @@ public class Bone implements Updatable { } public Vector2 worldToLocal (Vector2 world) { - float x = world.x - worldX, y = world.y - worldY; float a = this.a, b = this.b, c = this.c, d = this.d; float invDet = 1 / (a * d - b * c); + float x = world.x - worldX, y = world.y - worldY; world.x = (x * d * invDet - y * b * invDet); world.y = (y * a * invDet - x * c * invDet); return world; diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/BoneData.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/BoneData.java index fdc5cff99..821403b95 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/BoneData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/BoneData.java @@ -34,18 +34,21 @@ package com.esotericsoftware.spine; import com.badlogic.gdx.graphics.Color; public class BoneData { - final BoneData parent; + final int index; final String name; + final BoneData parent; float length; float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY; - boolean inheritScale = true, inheritRotation = true; + boolean inheritRotation = true, inheritScale = true; // Nonessential. final Color color = new Color(0.61f, 0.61f, 0.61f, 1); /** @param parent May be null. */ - public BoneData (String name, BoneData parent) { + public BoneData (int index, String name, BoneData parent) { + if (index < 0) throw new IllegalArgumentException("index must be >= 0."); if (name == null) throw new IllegalArgumentException("name cannot be null."); + this.index = index; this.name = name; this.parent = parent; } @@ -54,8 +57,9 @@ public class BoneData { * @param parent May be null. */ public BoneData (BoneData bone, BoneData parent) { if (bone == null) throw new IllegalArgumentException("bone cannot be null."); - this.parent = parent; + index = bone.index; name = bone.name; + this.parent = parent; length = bone.length; x = bone.x; y = bone.y; @@ -66,15 +70,19 @@ public class BoneData { shearY = bone.shearY; } - /** @return May be null. */ - public BoneData getParent () { - return parent; + public int getIndex () { + return index; } public String getName () { return name; } + /** @return May be null. */ + public BoneData getParent () { + return parent; + } + public float getLength () { return length; } @@ -149,14 +157,6 @@ public class BoneData { this.shearY = shearY; } - public boolean getInheritScale () { - return inheritScale; - } - - public void setInheritScale (boolean inheritScale) { - this.inheritScale = inheritScale; - } - public boolean getInheritRotation () { return inheritRotation; } @@ -165,6 +165,14 @@ public class BoneData { this.inheritRotation = inheritRotation; } + public boolean getInheritScale () { + return inheritScale; + } + + public void setInheritScale (boolean inheritScale) { + this.inheritScale = inheritScale; + } + public Color getColor () { return color; } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Event.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Event.java index dcf57551e..e4730fbc9 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Event.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Event.java @@ -39,6 +39,7 @@ public class Event { final float time; public Event (float time, EventData data) { + if (data == null) throw new IllegalArgumentException("data cannot be null."); this.time = time; this.data = data; } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java index 9969688f1..50dd12514 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java @@ -42,28 +42,32 @@ public class IkConstraint implements Updatable { float mix = 1; int bendDirection; + int level; + public IkConstraint (IkConstraintData data, Skeleton skeleton) { + if (data == null) throw new IllegalArgumentException("data cannot be null."); + if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); this.data = data; mix = data.mix; bendDirection = data.bendDirection; bones = new Array(data.bones.size); - if (skeleton != null) { - for (BoneData boneData : data.bones) - bones.add(skeleton.findBone(boneData.name)); - target = skeleton.findBone(data.target.name); - } + for (BoneData boneData : data.bones) + bones.add(skeleton.findBone(boneData.name)); + target = skeleton.findBone(data.target.name); } /** Copy constructor. */ - public IkConstraint (IkConstraint ikConstraint, Skeleton skeleton) { - data = ikConstraint.data; - bones = new Array(ikConstraint.bones.size); - for (Bone bone : ikConstraint.bones) - bones.add(skeleton.bones.get(bone.skeleton.bones.indexOf(bone, true))); - target = skeleton.bones.get(ikConstraint.target.skeleton.bones.indexOf(ikConstraint.target, true)); - mix = ikConstraint.mix; - bendDirection = ikConstraint.bendDirection; + public IkConstraint (IkConstraint constraint, Skeleton skeleton) { + if (constraint == null) throw new IllegalArgumentException("constraint cannot be null."); + if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); + data = constraint.data; + bones = new Array(constraint.bones.size); + for (Bone bone : constraint.bones) + bones.add(skeleton.bones.get(bone.data.index)); + target = skeleton.bones.get(constraint.target.data.index); + mix = constraint.mix; + bendDirection = constraint.bendDirection; } public void apply () { @@ -131,16 +135,19 @@ public class IkConstraint implements Updatable { if (rotationIK > 180) rotationIK -= 360; else if (rotationIK < -180) rotationIK += 360; - bone.updateWorldTransform(bone.x, bone.y, bone.rotation + (rotationIK - bone.rotation) * alpha, bone.appliedScaleX, - bone.appliedScaleY, bone.shearX, bone.shearY); + bone.updateWorldTransform(bone.x, bone.y, bone.rotation + (rotationIK - bone.rotation) * alpha, bone.scaleX, bone.scaleY, + bone.shearX, bone.shearY); } /** Adjusts the parent and child bone rotations so the tip of the child is as close to the target position as possible. The * target is specified in the world coordinate system. * @param child A direct descendant of the parent bone. */ static public void apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, float alpha) { - if (alpha == 0) return; - float px = parent.x, py = parent.y, psx = parent.appliedScaleX, psy = parent.appliedScaleY; + if (alpha == 0) { + child.updateWorldTransform(); + return; + } + float px = parent.x, py = parent.y, psx = parent.scaleX, psy = parent.scaleY, csx = child.scaleX; int os1, os2, s2; if (psx < 0) { psx = -psx; @@ -154,25 +161,32 @@ public class IkConstraint implements Updatable { psy = -psy; s2 = -s2; } - float cx = child.x, cy = child.y, csx = child.appliedScaleX; - boolean u = Math.abs(psx - psy) <= 0.0001f; - if (!u && cy != 0) { - child.worldX = parent.a * cx + parent.worldX; - child.worldY = parent.c * cx + parent.worldY; - cy = 0; - } if (csx < 0) { csx = -csx; os2 = 180; } else os2 = 0; + float cx = child.x, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d; + boolean u = Math.abs(psx - psy) <= 0.0001f; + if (!u) { + cy = 0; + cwx = a * cx + parent.worldX; + cwy = c * cx + parent.worldY; + } else { + cy = child.y; + cwx = a * cx + b * cy + parent.worldX; + cwy = c * cx + d * cy + parent.worldY; + } Bone pp = parent.parent; - float ppa = pp.a, ppb = pp.b, ppc = pp.c, ppd = pp.d, id = 1 / (ppa * ppd - ppb * ppc); - float x = targetX - pp.worldX, y = targetY - pp.worldY; - float tx = (x * ppd - y * ppb) * id - px, ty = (y * ppa - x * ppc) * id - py; - x = child.worldX - pp.worldX; - y = child.worldY - pp.worldY; - float dx = (x * ppd - y * ppb) * id - px, dy = (y * ppa - x * ppc) * id - py; + a = pp.a; + b = pp.b; + c = pp.c; + d = pp.d; + float id = 1 / (a * d - b * c), x = targetX - pp.worldX, y = targetY - pp.worldY; + float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py; + x = cwx - pp.worldX; + y = cwy - pp.worldY; + float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py; float l1 = (float)Math.sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2; outer: if (u) { @@ -182,18 +196,21 @@ public class IkConstraint implements Updatable { cos = -1; else if (cos > 1) cos = 1; a2 = (float)Math.acos(cos) * bendDir; - float a = l1 + l2 * cos, o = l2 * sin(a2); - a1 = atan2(ty * a - tx * o, tx * a + ty * o); + a = l1 + l2 * cos; + b = l2 * sin(a2); + a1 = atan2(ty * a - tx * b, tx * a + ty * b); } else { - float a = psx * l2, b = psy * l2, ta = atan2(ty, tx); - float aa = a * a, bb = b * b, ll = l1 * l1, dd = tx * tx + ty * ty; - float c0 = bb * ll + aa * dd - aa * bb, c1 = -2 * bb * l1, c2 = bb - aa; - float d = c1 * c1 - 4 * c2 * c0; + a = psx * l2; + b = psy * l2; + float aa = a * a, bb = b * b, dd = tx * tx + ty * ty, ta = atan2(ty, tx); + c = bb * l1 * l1 + aa * dd - aa * bb; + float c1 = -2 * bb * l1, c2 = bb - aa; + d = c1 * c1 - 4 * c2 * c; if (d >= 0) { float q = (float)Math.sqrt(d); if (c1 < 0) q = -q; q = -(c1 + q) / 2; - float r0 = q / c2, r1 = c0 / q; + float r0 = q / c2, r1 = c / q; float r = Math.abs(r0) < Math.abs(r1) ? r0 : r1; if (r * r <= dd) { y = (float)Math.sqrt(dd - r * r) * bendDir; @@ -243,18 +260,17 @@ public class IkConstraint implements Updatable { } } float os = atan2(cy, cx) * s2; - a1 = (a1 - os) * radDeg + os1; - a2 = ((a2 + os) * radDeg - child.shearX) * s2 + os2; + float rotation = parent.rotation; + a1 = (a1 - os) * radDeg + os1 - rotation; if (a1 > 180) a1 -= 360; else if (a1 < -180) a1 += 360; + parent.updateWorldTransform(px, py, rotation + a1 * alpha, parent.scaleX, parent.scaleY, 0, 0); + rotation = child.rotation; + a2 = ((a2 + os) * radDeg - child.shearX) * s2 + os2 - rotation; if (a2 > 180) a2 -= 360; else if (a2 < -180) a2 += 360; - float rotation = parent.rotation; - parent.updateWorldTransform(px, py, rotation + (a1 - rotation) * alpha, parent.appliedScaleX, parent.appliedScaleY, 0, 0); - rotation = child.rotation; - child.updateWorldTransform(cx, cy, rotation + (a2 - rotation) * alpha, child.appliedScaleX, child.appliedScaleY, - child.shearX, child.shearY); + child.updateWorldTransform(cx, cy, rotation + a2 * alpha, child.scaleX, child.scaleY, child.shearX, child.shearY); } } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraintData.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraintData.java index ef1bf0bc2..dfced65cc 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraintData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraintData.java @@ -41,6 +41,7 @@ public class IkConstraintData { float mix = 1; public IkConstraintData (String name) { + if (name == null) throw new IllegalArgumentException("name cannot be null."); this.name = name; } @@ -57,6 +58,7 @@ public class IkConstraintData { } public void setTarget (BoneData target) { + if (target == null) throw new IllegalArgumentException("target cannot be null."); this.target = target; } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java new file mode 100644 index 000000000..b9c4b1460 --- /dev/null +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java @@ -0,0 +1,436 @@ + +package com.esotericsoftware.spine; + +import static com.badlogic.gdx.math.MathUtils.*; + +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.FloatArray; +import com.esotericsoftware.spine.PathConstraintData.PositionMode; +import com.esotericsoftware.spine.PathConstraintData.RotateMode; +import com.esotericsoftware.spine.PathConstraintData.SpacingMode; +import com.esotericsoftware.spine.attachments.Attachment; +import com.esotericsoftware.spine.attachments.PathAttachment; + +public class PathConstraint implements Updatable { + static private final int NONE = -1, BEFORE = -2, AFTER = -3; + + final PathConstraintData data; + final Array bones; + Slot target; + float position, spacing, rotateMix, translateMix; + + private final FloatArray spaces = new FloatArray(), positions = new FloatArray(); + private final FloatArray world = new FloatArray(), curves = new FloatArray(), lengths = new FloatArray(); + private final float[] segments = new float[10]; + + public PathConstraint (PathConstraintData data, Skeleton skeleton) { + if (data == null) throw new IllegalArgumentException("data cannot be null."); + if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); + this.data = data; + bones = new Array(data.bones.size); + for (BoneData boneData : data.bones) + bones.add(skeleton.findBone(boneData.name)); + target = skeleton.findSlot(data.target.name); + position = data.position; + spacing = data.spacing; + rotateMix = data.rotateMix; + translateMix = data.translateMix; + } + + /** Copy constructor. */ + public PathConstraint (PathConstraint constraint, Skeleton skeleton) { + if (constraint == null) throw new IllegalArgumentException("constraint cannot be null."); + if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); + data = constraint.data; + bones = new Array(constraint.bones.size); + for (Bone bone : constraint.bones) + bones.add(skeleton.bones.get(bone.data.index)); + target = skeleton.slots.get(constraint.target.data.index); + position = constraint.position; + spacing = constraint.spacing; + rotateMix = constraint.rotateMix; + translateMix = constraint.translateMix; + } + + public void apply () { + update(); + } + + @SuppressWarnings("null") + public void update () { + Attachment attachment = target.getAttachment(); + if (!(attachment instanceof PathAttachment)) return; + + float rotateMix = this.rotateMix, translateMix = this.translateMix; + boolean translate = translateMix > 0, rotate = rotateMix > 0; + if (!translate && !rotate) return; + + PathConstraintData data = this.data; + SpacingMode spacingMode = data.spacingMode; + boolean lengthSpacing = spacingMode == SpacingMode.length; + RotateMode rotateMode = data.rotateMode; + boolean tangents = rotateMode == RotateMode.tangent, scale = rotateMode == RotateMode.chainScale; + int boneCount = this.bones.size, spacesCount = tangents ? boneCount : boneCount + 1; + Object[] bones = this.bones.items; + float[] spaces = this.spaces.setSize(spacesCount), lengths = null; + float spacing = this.spacing; + if (scale || lengthSpacing) { + if (scale) lengths = this.lengths.setSize(boneCount); + for (int i = 0, n = spacesCount - 1; i < n;) { + Bone bone = (Bone)bones[i]; + float length = bone.data.length, x = length * bone.a, y = length * bone.c; + length = (float)Math.sqrt(x * x + y * y); + if (scale) lengths[i] = length; + spaces[++i] = lengthSpacing ? Math.max(0, length + spacing) : spacing; + } + } else { + for (int i = 1; i < spacesCount; i++) + spaces[i] = spacing; + } + + float[] positions = computeWorldPositions((PathAttachment)attachment, spacesCount, tangents, + data.positionMode == PositionMode.percent, spacingMode == SpacingMode.percent); + Skeleton skeleton = target.getSkeleton(); + float skeletonX = skeleton.x, skeletonY = skeleton.y; + float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation; + boolean tip = rotateMode == RotateMode.chain && offsetRotation == 0; + for (int i = 0, p = 3; i < boneCount; i++, p += 3) { + Bone bone = (Bone)bones[i]; + bone.worldX += (boneX - skeletonX - bone.worldX) * translateMix; + bone.worldY += (boneY - skeletonY - bone.worldY) * translateMix; + float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; + if (scale) { + float length = lengths[i]; + if (length != 0) { + float s = ((float)Math.sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1; + bone.a *= s; + bone.c *= s; + } + } + boneX = x; + boneY = y; + if (rotate) { + float a = bone.a, b = bone.b, c = bone.c, d = bone.d, r, cos, sin; + if (tangents) + r = positions[p - 1]; + else if (spaces[i + 1] == 0) + r = positions[p + 2]; + else + r = atan2(dy, dx); + r -= atan2(c, a) - offsetRotation * degRad; + if (tip) { + cos = cos(r); + sin = sin(r); + float length = bone.data.length; + boneX += (length * (cos * a - sin * c) - dx) * rotateMix; + boneY += (length * (sin * a + cos * c) - dy) * rotateMix; + } + if (r > PI) + r -= PI2; + else if (r < -PI) // + r += PI2; + r *= rotateMix; + cos = cos(r); + sin = sin(r); + bone.a = cos * a - sin * c; + bone.b = cos * b - sin * d; + bone.c = sin * a + cos * c; + bone.d = sin * b + cos * d; + } + } + } + + private float[] computeWorldPositions (PathAttachment path, int spacesCount, boolean tangents, boolean percentPosition, + boolean percentSpacing) { + Slot target = this.target; + float position = this.position; + float[] spaces = this.spaces.items, out = this.positions.setSize(spacesCount * 3 + 2), world; + boolean closed = path.getClosed(); + int verticesLength = path.getWorldVerticesLength(), curveCount = verticesLength / 6, prevCurve = NONE; + + if (!path.getConstantSpeed()) { + float[] lengths = path.getLengths(); + curveCount -= closed ? 1 : 2; + float pathLength = lengths[curveCount]; + if (percentPosition) position *= pathLength; + if (percentSpacing) { + for (int i = 0; i < spacesCount; i++) + spaces[i] *= pathLength; + } + world = this.world.setSize(8); + for (int i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) { + float space = spaces[i]; + position += space; + float p = position; + + if (closed) { + p %= pathLength; + if (p < 0) p += pathLength; + curve = 0; + } else if (p < 0) { + if (prevCurve != BEFORE) { + prevCurve = BEFORE; + path.computeWorldVertices(target, 2, 4, world, 0); + } + addBeforePosition(p, world, 0, out, o); + continue; + } else if (p > pathLength) { + if (prevCurve != AFTER) { + prevCurve = AFTER; + path.computeWorldVertices(target, verticesLength - 6, 4, world, 0); + } + addAfterPosition(p - pathLength, world, 0, out, o); + continue; + } + + // Determine curve containing position. + for (;; curve++) { + float length = lengths[curve]; + if (p > length) continue; + if (curve == 0) + p /= length; + else { + float prev = lengths[curve - 1]; + p = (p - prev) / (length - prev); + } + break; + } + if (curve != prevCurve) { + prevCurve = curve; + if (closed && curve == curveCount) { + path.computeWorldVertices(target, verticesLength - 4, 4, world, 0); + path.computeWorldVertices(target, 0, 4, world, 4); + } else + path.computeWorldVertices(target, curve * 6 + 2, 8, world, 0); + } + addCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], out, o, + tangents || (i > 0 && space == 0)); + } + return out; + } + + // World vertices. + if (closed) { + verticesLength += 2; + world = this.world.setSize(verticesLength); + path.computeWorldVertices(target, 2, verticesLength - 4, world, 0); + path.computeWorldVertices(target, 0, 2, world, verticesLength - 4); + world[verticesLength - 2] = world[0]; + world[verticesLength - 1] = world[1]; + } else { + curveCount--; + verticesLength -= 4; + world = this.world.setSize(verticesLength); + path.computeWorldVertices(target, 2, verticesLength, world, 0); + } + + // Curve lengths. + float[] curves = this.curves.setSize(curveCount); + float pathLength = 0; + float x1 = world[0], y1 = world[1], cx1 = 0, cy1 = 0, cx2 = 0, cy2 = 0, x2 = 0, y2 = 0; + float tmpx, tmpy, dddfx, dddfy, ddfx, ddfy, dfx, dfy; + for (int i = 0, w = 2; i < curveCount; i++, w += 6) { + cx1 = world[w]; + cy1 = world[w + 1]; + cx2 = world[w + 2]; + cy2 = world[w + 3]; + x2 = world[w + 4]; + y2 = world[w + 5]; + tmpx = (x1 - cx1 * 2 + cx2) * 0.1875f; + tmpy = (y1 - cy1 * 2 + cy2) * 0.1875f; + dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375f; + dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.09375f; + ddfx = tmpx * 2 + dddfx; + ddfy = tmpy * 2 + dddfy; + dfx = (cx1 - x1) * 0.75f + tmpx + dddfx * 0.16666667f; + dfy = (cy1 - y1) * 0.75f + tmpy + dddfy * 0.16666667f; + pathLength += (float)Math.sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + pathLength += (float)Math.sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + pathLength += (float)Math.sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + pathLength += (float)Math.sqrt(dfx * dfx + dfy * dfy); + curves[i] = pathLength; + x1 = x2; + y1 = y2; + } + if (percentPosition) position *= pathLength; + if (percentSpacing) { + for (int i = 0; i < spacesCount; i++) + spaces[i] *= pathLength; + } + + float[] segments = this.segments; + float curveLength = 0; + for (int i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) { + float space = spaces[i]; + position += space; + float p = position; + + if (closed) { + p %= pathLength; + if (p < 0) p += pathLength; + curve = 0; + } else if (p < 0) { + addBeforePosition(p, world, 0, out, o); + continue; + } else if (p > pathLength) { + addAfterPosition(p - pathLength, world, verticesLength - 4, out, o); + continue; + } + + // Determine curve containing position. + for (;; curve++) { + float length = curves[curve]; + if (p > length) continue; + if (curve == 0) + p /= length; + else { + float prev = curves[curve - 1]; + p = (p - prev) / (length - prev); + } + break; + } + + // Curve segment lengths. + if (curve != prevCurve) { + prevCurve = curve; + int ii = curve * 6; + x1 = world[ii]; + y1 = world[ii + 1]; + cx1 = world[ii + 2]; + cy1 = world[ii + 3]; + cx2 = world[ii + 4]; + cy2 = world[ii + 5]; + x2 = world[ii + 6]; + y2 = world[ii + 7]; + tmpx = (x1 - cx1 * 2 + cx2) * 0.03f; + tmpy = (y1 - cy1 * 2 + cy2) * 0.03f; + dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.006f; + dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.006f; + ddfx = tmpx * 2 + dddfx; + ddfy = tmpy * 2 + dddfy; + dfx = (cx1 - x1) * 0.3f + tmpx + dddfx * 0.16666667f; + dfy = (cy1 - y1) * 0.3f + tmpy + dddfy * 0.16666667f; + curveLength = (float)Math.sqrt(dfx * dfx + dfy * dfy); + segments[0] = curveLength; + for (ii = 1; ii < 8; ii++) { + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + curveLength += (float)Math.sqrt(dfx * dfx + dfy * dfy); + segments[ii] = curveLength; + } + dfx += ddfx; + dfy += ddfy; + curveLength += (float)Math.sqrt(dfx * dfx + dfy * dfy); + segments[8] = curveLength; + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + curveLength += (float)Math.sqrt(dfx * dfx + dfy * dfy); + segments[9] = curveLength; + segment = 0; + } + + // Weight by segment length. + p *= curveLength; + for (;; segment++) { + float length = segments[segment]; + if (p > length) continue; + if (segment == 0) + p /= length; + else { + float prev = segments[segment - 1]; + p = segment + (p - prev) / (length - prev); + } + break; + } + addCurvePosition(p * 0.1f, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, tangents || (i > 0 && space == 0)); + } + return out; + } + + private void addBeforePosition (float p, float[] temp, int i, float[] out, int o) { + float x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = atan2(dy, dx); + out[o] = x1 + p * cos(r); + out[o + 1] = y1 + p * sin(r); + out[o + 2] = r; + } + + private void addAfterPosition (float p, float[] temp, int i, float[] out, int o) { + float x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = atan2(dy, dx); + out[o] = x1 + p * cos(r); + out[o + 1] = y1 + p * sin(r); + out[o + 2] = r; + } + + private void addCurvePosition (float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, + float[] out, int o, boolean tangents) { + if (p == 0) p = 0.0001f; + float tt = p * p, ttt = tt * p, u = 1 - p, uu = u * u, uuu = uu * u; + float ut = u * p, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * p; + float x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt; + out[o] = x; + out[o + 1] = y; + if (tangents) out[o + 2] = atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt)); + } + + public float getPosition () { + return position; + } + + public void setPosition (float position) { + this.position = position; + } + + public float getSpacing () { + return spacing; + } + + public void setSpacing (float spacing) { + this.spacing = spacing; + } + + public float getRotateMix () { + return rotateMix; + } + + public void setRotateMix (float rotateMix) { + this.rotateMix = rotateMix; + } + + public float getTranslateMix () { + return translateMix; + } + + public void setTranslateMix (float translateMix) { + this.translateMix = translateMix; + } + + public Array getBones () { + return bones; + } + + public Slot getTarget () { + return target; + } + + public void setTarget (Slot target) { + this.target = target; + } + + public PathConstraintData getData () { + return data; + } + + public String toString () { + return data.name; + } +} diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java new file mode 100644 index 000000000..c3be9bd2f --- /dev/null +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java @@ -0,0 +1,122 @@ + +package com.esotericsoftware.spine; + +import com.badlogic.gdx.utils.Array; + +public class PathConstraintData { + final String name; + final Array bones = new Array(); + SlotData target; + PositionMode positionMode; + SpacingMode spacingMode; + RotateMode rotateMode; + float offsetRotation; + float position, spacing, rotateMix, translateMix; + + public PathConstraintData (String name) { + if (name == null) throw new IllegalArgumentException("name cannot be null."); + this.name = name; + } + + public Array getBones () { + return bones; + } + + public SlotData getTarget () { + return target; + } + + public void setTarget (SlotData target) { + this.target = target; + } + + public PositionMode getPositionMode () { + return positionMode; + } + + public void setPositionMode (PositionMode positionMode) { + this.positionMode = positionMode; + } + + public SpacingMode getSpacingMode () { + return spacingMode; + } + + public void setSpacingMode (SpacingMode spacingMode) { + this.spacingMode = spacingMode; + } + + public RotateMode getRotateMode () { + return rotateMode; + } + + public void setRotateMode (RotateMode rotateMode) { + this.rotateMode = rotateMode; + } + + public float getOffsetRotation () { + return offsetRotation; + } + + public void setOffsetRotation (float offsetRotation) { + this.offsetRotation = offsetRotation; + } + + public float getPosition () { + return position; + } + + public void setPosition (float position) { + this.position = position; + } + + public float getSpacing () { + return spacing; + } + + public void setSpacing (float spacing) { + this.spacing = spacing; + } + + public float getRotateMix () { + return rotateMix; + } + + public void setRotateMix (float rotateMix) { + this.rotateMix = rotateMix; + } + + public float getTranslateMix () { + return translateMix; + } + + public void setTranslateMix (float translateMix) { + this.translateMix = translateMix; + } + + public String getName () { + return name; + } + + public String toString () { + return name; + } + + static public enum PositionMode { + fixed, percent; + + static public final PositionMode[] values = PositionMode.values(); + } + + static public enum SpacingMode { + length, fixed, percent; + + static public final SpacingMode[] values = SpacingMode.values(); + } + + static public enum RotateMode { + tangent, chain, chainScale; + + static public final RotateMode[] values = RotateMode.values(); + } +} diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java index 7f5390073..1e614a608 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java @@ -34,19 +34,22 @@ package com.esotericsoftware.spine; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.ObjectMap.Entry; +import com.esotericsoftware.spine.Skin.Key; import com.esotericsoftware.spine.attachments.Attachment; import com.esotericsoftware.spine.attachments.MeshAttachment; +import com.esotericsoftware.spine.attachments.PathAttachment; import com.esotericsoftware.spine.attachments.RegionAttachment; -import com.esotericsoftware.spine.attachments.WeightedMeshAttachment; public class Skeleton { final SkeletonData data; final Array bones; final Array slots; Array drawOrder; - final Array ikConstraints; + final Array ikConstraints, ikConstraintsSorted; final Array transformConstraints; - private final Array updateCache = new Array(); + final Array pathConstraints; + final Array updateCache = new Array(); Skin skin; final Color color; float time; @@ -59,20 +62,28 @@ public class Skeleton { bones = new Array(data.bones.size); for (BoneData boneData : data.bones) { - Bone parent = boneData.parent == null ? null : bones.get(data.bones.indexOf(boneData.parent, true)); - bones.add(new Bone(boneData, this, parent)); + Bone bone; + if (boneData.parent == null) + bone = new Bone(boneData, this, null); + else { + Bone parent = bones.get(boneData.parent.index); + bone = new Bone(boneData, this, parent); + parent.children.add(bone); + } + bones.add(bone); } slots = new Array(data.slots.size); drawOrder = new Array(data.slots.size); for (SlotData slotData : data.slots) { - Bone bone = bones.get(data.bones.indexOf(slotData.boneData, true)); + Bone bone = bones.get(slotData.boneData.index); Slot slot = new Slot(slotData, bone); slots.add(slot); drawOrder.add(slot); } ikConstraints = new Array(data.ikConstraints.size); + ikConstraintsSorted = new Array(ikConstraints.size); for (IkConstraintData ikConstraintData : data.ikConstraints) ikConstraints.add(new IkConstraint(ikConstraintData, this)); @@ -80,6 +91,10 @@ public class Skeleton { for (TransformConstraintData transformConstraintData : data.transformConstraints) transformConstraints.add(new TransformConstraint(transformConstraintData, this)); + pathConstraints = new Array(data.pathConstraints.size); + for (PathConstraintData pathConstraintData : data.pathConstraints) + pathConstraints.add(new PathConstraint(pathConstraintData, this)); + color = new Color(1, 1, 1, 1); updateCache(); @@ -92,21 +107,22 @@ public class Skeleton { bones = new Array(skeleton.bones.size); for (Bone bone : skeleton.bones) { - Bone parent = bone.parent == null ? null : bones.get(skeleton.bones.indexOf(bone.parent, true)); + Bone parent = bone.parent == null ? null : bones.get(bone.parent.data.index); bones.add(new Bone(bone, this, parent)); } slots = new Array(skeleton.slots.size); for (Slot slot : skeleton.slots) { - Bone bone = bones.get(skeleton.bones.indexOf(slot.bone, true)); + Bone bone = bones.get(slot.bone.data.index); slots.add(new Slot(slot, bone)); } drawOrder = new Array(slots.size); for (Slot slot : skeleton.drawOrder) - drawOrder.add(slots.get(skeleton.slots.indexOf(slot, true))); + drawOrder.add(slots.get(slot.data.index)); ikConstraints = new Array(skeleton.ikConstraints.size); + ikConstraintsSorted = new Array(ikConstraints.size); for (IkConstraint ikConstraint : skeleton.ikConstraints) ikConstraints.add(new IkConstraint(ikConstraint, this)); @@ -114,6 +130,10 @@ public class Skeleton { for (TransformConstraint transformConstraint : skeleton.transformConstraints) transformConstraints.add(new TransformConstraint(transformConstraint, this)); + pathConstraints = new Array(skeleton.pathConstraints.size); + for (PathConstraint pathConstraint : skeleton.pathConstraints) + pathConstraints.add(new PathConstraint(pathConstraint, this)); + skin = skeleton.skin; color = new Color(skeleton.color); time = skeleton.time; @@ -123,36 +143,135 @@ public class Skeleton { updateCache(); } - /** Caches information about bones and constraints. Must be called if bones or constraints are added or removed. */ + /** Caches information about bones and constraints. Must be called if bones, constraints, or weighted path attachments are + * added or removed. */ public void updateCache () { - Array bones = this.bones; Array updateCache = this.updateCache; - Array ikConstraints = this.ikConstraints; - Array transformConstraints = this.transformConstraints; - int ikConstraintsCount = ikConstraints.size; - int transformConstraintsCount = transformConstraints.size; updateCache.clear(); - for (int i = 0, n = bones.size; i < n; i++) { - Bone bone = bones.get(i); - updateCache.add(bone); - for (int ii = 0; ii < ikConstraintsCount; ii++) { - IkConstraint ikConstraint = ikConstraints.get(ii); - if (bone == ikConstraint.bones.peek()) { - updateCache.add(ikConstraint); - break; - } + Array bones = this.bones; + for (int i = 0, n = bones.size; i < n; i++) + bones.get(i).sorted = false; + + // IK first, lowest hierarchy depth first. + Array ikConstraints = this.ikConstraintsSorted; + ikConstraints.clear(); + ikConstraints.addAll(this.ikConstraints); + int ikCount = ikConstraints.size; + for (int i = 0, level, n = ikCount; i < n; i++) { + IkConstraint ik = ikConstraints.get(i); + Bone bone = ik.bones.first().parent; + for (level = 0; bone != null; level++) + bone = bone.parent; + ik.level = level; + } + for (int i = 1, ii; i < ikCount; i++) { + IkConstraint ik = ikConstraints.get(i); + int level = ik.level; + for (ii = i - 1; ii >= 0; ii--) { + IkConstraint other = ikConstraints.get(ii); + if (other.level < level) break; + ikConstraints.set(ii + 1, other); } + ikConstraints.set(ii + 1, ik); + } + for (int i = 0, n = ikConstraints.size; i < n; i++) { + IkConstraint constraint = ikConstraints.get(i); + Bone target = constraint.target; + sortBone(target); + + Array constrained = constraint.bones; + Bone parent = constrained.first(); + sortBone(parent); + + updateCache.add(constraint); + + sortReset(parent.children); + constrained.peek().sorted = true; } - for (int i = 0; i < transformConstraintsCount; i++) { - TransformConstraint transformConstraint = transformConstraints.get(i); - for (int ii = updateCache.size - 1; ii >= 0; ii--) { - if (updateCache.get(ii) == transformConstraint.bone) { - updateCache.insert(ii + 1, transformConstraint); - break; - } - } + Array pathConstraints = this.pathConstraints; + for (int i = 0, n = pathConstraints.size; i < n; i++) { + PathConstraint constraint = pathConstraints.get(i); + + Slot slot = constraint.target; + int slotIndex = slot.getData().index; + Bone slotBone = slot.bone; + if (skin != null) sortPathConstraintAttachment(skin, slotIndex, slotBone); + if (data.defaultSkin != null && data.defaultSkin != skin) + sortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone); + for (int ii = 0, nn = data.skins.size; ii < nn; ii++) + sortPathConstraintAttachment(data.skins.get(ii), slotIndex, slotBone); + + Attachment attachment = slot.getAttachment(); + if (attachment instanceof PathAttachment) sortPathConstraintAttachment(attachment, slotBone); + + Array constrained = constraint.bones; + int boneCount = constrained.size; + for (int ii = 0; ii < boneCount; ii++) + sortBone(constrained.get(ii)); + + updateCache.add(constraint); + + for (int ii = 0; ii < boneCount; ii++) + sortReset(constrained.get(ii).children); + for (int ii = 0; ii < boneCount; ii++) + constrained.get(ii).sorted = true; + } + + Array transformConstraints = this.transformConstraints; + for (int i = 0, n = transformConstraints.size; i < n; i++) { + TransformConstraint constraint = transformConstraints.get(i); + + sortBone(constraint.target); + + Array constrained = constraint.bones; + int boneCount = constrained.size; + for (int ii = 0; ii < boneCount; ii++) + sortBone(constrained.get(ii)); + + updateCache.add(constraint); + + for (int ii = 0; ii < boneCount; ii++) + sortReset(constrained.get(ii).children); + for (int ii = 0; ii < boneCount; ii++) + constrained.get(ii).sorted = true; + } + + for (int i = 0, n = bones.size; i < n; i++) + sortBone(bones.get(i)); + } + + private void sortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) { + for (Entry entry : skin.attachments.entries()) + if (entry.key.slotIndex == slotIndex) sortPathConstraintAttachment(entry.value, slotBone); + } + + private void sortPathConstraintAttachment (Attachment attachment, Bone slotBone) { + if (!(attachment instanceof PathAttachment)) return; + int[] pathBones = ((PathAttachment)attachment).getBones(); + if (pathBones == null) + sortBone(slotBone); + else { + Array bones = this.bones; + for (int boneIndex : pathBones) + sortBone(bones.get(boneIndex)); + } + } + + private void sortBone (Bone bone) { + if (bone.sorted) return; + Bone parent = bone.parent; + if (parent != null) sortBone(parent); + bone.sorted = true; + updateCache.add(bone); + } + + private void sortReset (Array bones) { + for (int i = 0, n = bones.size; i < n; i++) { + Bone bone = bones.get(i); + if (bone.sorted) sortReset(bone.children); + bone.sorted = false; } } @@ -191,13 +310,23 @@ public class Skeleton { constraint.scaleMix = data.scaleMix; constraint.shearMix = data.shearMix; } + + Array pathConstraints = this.pathConstraints; + for (int i = 0, n = pathConstraints.size; i < n; i++) { + PathConstraint constraint = pathConstraints.get(i); + PathConstraintData data = constraint.data; + constraint.position = data.position; + constraint.spacing = data.spacing; + constraint.rotateMix = data.rotateMix; + constraint.translateMix = data.translateMix; + } } public void setSlotsToSetupPose () { Array slots = this.slots; System.arraycopy(slots.items, 0, drawOrder.items, 0, slots.size); for (int i = 0, n = slots.size; i < n; i++) - slots.get(i).setToSetupPose(i); + slots.get(i).setToSetupPose(); } public SkeletonData getData () { @@ -208,6 +337,10 @@ public class Skeleton { return bones; } + public Array getUpdateCache () { + return updateCache; + } + /** @return May return null. */ public Bone getRootBone () { if (bones.size == 0) return null; @@ -265,6 +398,7 @@ public class Skeleton { /** Sets the slots and the order they will be drawn. */ public void setDrawOrder (Array drawOrder) { + if (drawOrder == null) throw new IllegalArgumentException("drawOrder cannot be null."); this.drawOrder = drawOrder; } @@ -370,10 +504,27 @@ public class Skeleton { return null; } - /** Returns the axis aligned bounding box (AABB) of the region, mesh, and skinned mesh attachments for the current pose. + public Array getPathConstraints () { + return pathConstraints; + } + + /** @return May be null. */ + public PathConstraint findPathConstraint (String constraintName) { + if (constraintName == null) throw new IllegalArgumentException("constraintName cannot be null."); + Array pathConstraints = this.pathConstraints; + for (int i = 0, n = pathConstraints.size; i < n; i++) { + PathConstraint constraint = pathConstraints.get(i); + if (constraint.data.name.equals(constraintName)) return constraint; + } + return null; + } + + /** Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose. * @param offset The distance from the skeleton origin to the bottom left corner of the AABB. * @param size The width and height of the AABB. */ public void getBounds (Vector2 offset, Vector2 size) { + if (offset == null) throw new IllegalArgumentException("offset cannot be null."); + if (size == null) throw new IllegalArgumentException("size cannot be null."); Array drawOrder = this.drawOrder; float minX = Integer.MAX_VALUE, minY = Integer.MAX_VALUE, maxX = Integer.MIN_VALUE, maxY = Integer.MIN_VALUE; for (int i = 0, n = drawOrder.size; i < n; i++) { @@ -382,12 +533,8 @@ public class Skeleton { Attachment attachment = slot.attachment; if (attachment instanceof RegionAttachment) { vertices = ((RegionAttachment)attachment).updateWorldVertices(slot, false); - } else if (attachment instanceof MeshAttachment) { vertices = ((MeshAttachment)attachment).updateWorldVertices(slot, true); - - } else if (attachment instanceof WeightedMeshAttachment) { - vertices = ((WeightedMeshAttachment)attachment).updateWorldVertices(slot, true); } if (vertices != null) { for (int ii = 0, nn = vertices.length; ii < nn; ii += 5) { @@ -409,6 +556,7 @@ public class Skeleton { /** A convenience method for setting the skeleton color. The color can also be set by modifying {@link #getColor()}. */ public void setColor (Color color) { + if (color == null) throw new IllegalArgumentException("color cannot be null."); this.color.set(color); } 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 4fc13a0dc..a24fe562f 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java @@ -31,6 +31,7 @@ package com.esotericsoftware.spine; +import java.io.EOFException; import java.io.IOException; import com.badlogic.gdx.files.FileHandle; @@ -44,16 +45,22 @@ import com.badlogic.gdx.utils.SerializationException; import com.esotericsoftware.spine.Animation.AttachmentTimeline; import com.esotericsoftware.spine.Animation.ColorTimeline; import com.esotericsoftware.spine.Animation.CurveTimeline; +import com.esotericsoftware.spine.Animation.DeformTimeline; import com.esotericsoftware.spine.Animation.DrawOrderTimeline; import com.esotericsoftware.spine.Animation.EventTimeline; -import com.esotericsoftware.spine.Animation.FfdTimeline; import com.esotericsoftware.spine.Animation.IkConstraintTimeline; +import com.esotericsoftware.spine.Animation.PathConstraintMixTimeline; +import com.esotericsoftware.spine.Animation.PathConstraintPositionTimeline; +import com.esotericsoftware.spine.Animation.PathConstraintSpacingTimeline; import com.esotericsoftware.spine.Animation.RotateTimeline; import com.esotericsoftware.spine.Animation.ScaleTimeline; import com.esotericsoftware.spine.Animation.ShearTimeline; import com.esotericsoftware.spine.Animation.Timeline; import com.esotericsoftware.spine.Animation.TransformConstraintTimeline; import com.esotericsoftware.spine.Animation.TranslateTimeline; +import com.esotericsoftware.spine.PathConstraintData.PositionMode; +import com.esotericsoftware.spine.PathConstraintData.RotateMode; +import com.esotericsoftware.spine.PathConstraintData.SpacingMode; import com.esotericsoftware.spine.SkeletonJson.LinkedMesh; import com.esotericsoftware.spine.attachments.AtlasAttachmentLoader; import com.esotericsoftware.spine.attachments.Attachment; @@ -61,16 +68,22 @@ import com.esotericsoftware.spine.attachments.AttachmentLoader; import com.esotericsoftware.spine.attachments.AttachmentType; import com.esotericsoftware.spine.attachments.BoundingBoxAttachment; import com.esotericsoftware.spine.attachments.MeshAttachment; +import com.esotericsoftware.spine.attachments.PathAttachment; import com.esotericsoftware.spine.attachments.RegionAttachment; -import com.esotericsoftware.spine.attachments.WeightedMeshAttachment; +import com.esotericsoftware.spine.attachments.VertexAttachment; public class SkeletonBinary { - static public final int TIMELINE_ROTATE = 0; - static public final int TIMELINE_TRANSLATE = 1; - static public final int TIMELINE_SCALE = 2; - static public final int TIMELINE_SHEAR = 3; - static public final int TIMELINE_ATTACHMENT = 4; - static public final int TIMELINE_COLOR = 5; + static public final int BONE_ROTATE = 0; + static public final int BONE_TRANSLATE = 1; + static public final int BONE_SCALE = 2; + static public final int BONE_SHEAR = 3; + + static public final int SLOT_ATTACHMENT = 0; + static public final int SLOT_COLOR = 1; + + static public final int PATH_POSITION = 0; + static public final int PATH_SPACING = 1; + static public final int PATH_MIX = 2; static public final int CURVE_LINEAR = 0; static public final int CURVE_STEPPED = 1; @@ -87,6 +100,7 @@ public class SkeletonBinary { } public SkeletonBinary (AttachmentLoader attachmentLoader) { + if (attachmentLoader == null) throw new IllegalArgumentException("attachmentLoader cannot be null."); this.attachmentLoader = attachmentLoader; } @@ -125,6 +139,8 @@ public class SkeletonBinary { for (int i = 0; i < byteCount;) { int b = read(); switch (b >> 4) { + case -1: + throw new EOFException(); case 12: case 13: chars[charCount++] = (char)((b & 0x1F) << 6 | read() & 0x3F); @@ -161,59 +177,79 @@ public class SkeletonBinary { for (int i = 0, n = input.readInt(true); i < n; i++) { String name = input.readString(); BoneData parent = i == 0 ? null : skeletonData.bones.get(input.readInt(true)); - BoneData boneData = new BoneData(name, parent); - boneData.rotation = input.readFloat(); - boneData.x = input.readFloat() * scale; - boneData.y = input.readFloat() * scale; - boneData.scaleX = input.readFloat(); - boneData.scaleY = input.readFloat(); - boneData.shearX = input.readFloat(); - boneData.shearY = input.readFloat(); - boneData.length = input.readFloat() * scale; - boneData.inheritRotation = input.readBoolean(); - boneData.inheritScale = input.readBoolean(); - if (nonessential) Color.rgba8888ToColor(boneData.color, input.readInt()); - skeletonData.bones.add(boneData); - } - - // IK constraints. - for (int i = 0, n = input.readInt(true); i < n; i++) { - IkConstraintData ikConstraintData = new IkConstraintData(input.readString()); - for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) - ikConstraintData.bones.add(skeletonData.bones.get(input.readInt(true))); - ikConstraintData.target = skeletonData.bones.get(input.readInt(true)); - ikConstraintData.mix = input.readFloat(); - ikConstraintData.bendDirection = input.readByte(); - skeletonData.ikConstraints.add(ikConstraintData); - } - - // Transform constraints. - for (int i = 0, n = input.readInt(true); i < n; i++) { - TransformConstraintData transformConstraintData = new TransformConstraintData(input.readString()); - transformConstraintData.bone = skeletonData.bones.get(input.readInt(true)); - transformConstraintData.target = skeletonData.bones.get(input.readInt(true)); - transformConstraintData.offsetRotation = input.readFloat(); - transformConstraintData.offsetX = input.readFloat() * scale; - transformConstraintData.offsetY = input.readFloat() * scale; - transformConstraintData.offsetScaleX = input.readFloat(); - transformConstraintData.offsetScaleY = input.readFloat(); - transformConstraintData.offsetShearY = input.readFloat(); - transformConstraintData.rotateMix = input.readFloat(); - transformConstraintData.translateMix = input.readFloat(); - transformConstraintData.scaleMix = input.readFloat(); - transformConstraintData.shearMix = input.readFloat(); - skeletonData.transformConstraints.add(transformConstraintData); + BoneData data = new BoneData(i, name, parent); + data.rotation = input.readFloat(); + data.x = input.readFloat() * scale; + data.y = input.readFloat() * scale; + data.scaleX = input.readFloat(); + data.scaleY = input.readFloat(); + data.shearX = input.readFloat(); + data.shearY = input.readFloat(); + data.length = input.readFloat() * scale; + data.inheritRotation = input.readBoolean(); + data.inheritScale = input.readBoolean(); + if (nonessential) Color.rgba8888ToColor(data.color, input.readInt()); + skeletonData.bones.add(data); } // Slots. for (int i = 0, n = input.readInt(true); i < n; i++) { String slotName = input.readString(); BoneData boneData = skeletonData.bones.get(input.readInt(true)); - SlotData slotData = new SlotData(slotName, boneData); - Color.rgba8888ToColor(slotData.color, input.readInt()); - slotData.attachmentName = input.readString(); - slotData.blendMode = BlendMode.values[input.readInt(true)]; - skeletonData.slots.add(slotData); + SlotData data = new SlotData(i, slotName, boneData); + Color.rgba8888ToColor(data.color, input.readInt()); + data.attachmentName = input.readString(); + data.blendMode = BlendMode.values[input.readInt(true)]; + skeletonData.slots.add(data); + } + + // IK constraints. + for (int i = 0, n = input.readInt(true); i < n; i++) { + IkConstraintData data = new IkConstraintData(input.readString()); + for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) + data.bones.add(skeletonData.bones.get(input.readInt(true))); + data.target = skeletonData.bones.get(input.readInt(true)); + data.mix = input.readFloat(); + data.bendDirection = input.readByte(); + skeletonData.ikConstraints.add(data); + } + + // Transform constraints. + for (int i = 0, n = input.readInt(true); i < n; i++) { + TransformConstraintData data = new TransformConstraintData(input.readString()); + for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) + data.bones.add(skeletonData.bones.get(input.readInt(true))); + data.target = skeletonData.bones.get(input.readInt(true)); + data.offsetRotation = input.readFloat(); + data.offsetX = input.readFloat() * scale; + data.offsetY = input.readFloat() * scale; + data.offsetScaleX = input.readFloat(); + data.offsetScaleY = input.readFloat(); + data.offsetShearY = input.readFloat(); + data.rotateMix = input.readFloat(); + data.translateMix = input.readFloat(); + data.scaleMix = input.readFloat(); + data.shearMix = input.readFloat(); + skeletonData.transformConstraints.add(data); + } + + // Path constraints. + for (int i = 0, n = input.readInt(true); i < n; i++) { + PathConstraintData data = new PathConstraintData(input.readString()); + for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) + data.bones.add(skeletonData.bones.get(input.readInt(true))); + data.target = skeletonData.slots.get(input.readInt(true)); + data.positionMode = PositionMode.values[input.readInt(true)]; + data.spacingMode = SpacingMode.values[input.readInt(true)]; + data.rotateMode = RotateMode.values[input.readInt(true)]; + data.offsetRotation = input.readFloat(); + data.position = input.readFloat(); + if (data.positionMode == PositionMode.fixed) data.position *= scale; + data.spacing = input.readFloat(); + if (data.spacingMode == SpacingMode.length || data.spacingMode == SpacingMode.fixed) data.spacing *= scale; + data.rotateMix = input.readFloat(); + data.translateMix = input.readFloat(); + skeletonData.pathConstraints.add(data); } // Default skin. @@ -234,25 +270,18 @@ public class SkeletonBinary { if (skin == null) throw new SerializationException("Skin not found: " + linkedMesh.skin); Attachment parent = skin.getAttachment(linkedMesh.slotIndex, linkedMesh.parent); if (parent == null) throw new SerializationException("Parent mesh not found: " + linkedMesh.parent); - if (linkedMesh.mesh instanceof MeshAttachment) { - MeshAttachment mesh = (MeshAttachment)linkedMesh.mesh; - mesh.setParentMesh((MeshAttachment)parent); - mesh.updateUVs(); - } else { - WeightedMeshAttachment mesh = (WeightedMeshAttachment)linkedMesh.mesh; - mesh.setParentMesh((WeightedMeshAttachment)parent); - mesh.updateUVs(); - } + linkedMesh.mesh.setParentMesh((MeshAttachment)parent); + linkedMesh.mesh.updateUVs(); } linkedMeshes.clear(); // Events. for (int i = 0, n = input.readInt(true); i < n; i++) { - EventData eventData = new EventData(input.readString()); - eventData.intValue = input.readInt(false); - eventData.floatValue = input.readFloat(); - eventData.stringValue = input.readString(); - skeletonData.events.add(eventData); + EventData data = new EventData(input.readString()); + data.intValue = input.readInt(false); + data.floatValue = input.readFloat(); + data.stringValue = input.readString(); + skeletonData.events.add(data); } // Animations. @@ -328,21 +357,26 @@ public class SkeletonBinary { return region; } case boundingbox: { - float[] vertices = readFloatArray(input, input.readInt(true) * 2, scale); + int vertexCount = input.readInt(true); + Vertices vertices = readVertices(input, vertexCount); + int color = nonessential ? input.readInt() : 0; + BoundingBoxAttachment box = attachmentLoader.newBoundingBoxAttachment(skin, name); if (box == null) return null; - box.setVertices(vertices); + box.setWorldVerticesLength(vertexCount << 1); + box.setVertices(vertices.vertices); + box.setBones(vertices.bones); + if (nonessential) Color.rgba8888ToColor(box.getColor(), color); return box; } case mesh: { String path = input.readString(); int color = input.readInt(); - int hullLength = 0; - int verticesLength = input.readInt(true) * 2; - float[] uvs = readFloatArray(input, verticesLength, 1); + int vertexCount = input.readInt(true); + float[] uvs = readFloatArray(input, vertexCount << 1, 1); short[] triangles = readShortArray(input); - float[] vertices = readFloatArray(input, verticesLength, scale); - hullLength = input.readInt(true); + Vertices vertices = readVertices(input, vertexCount); + int hullLength = input.readInt(true); short[] edges = null; float width = 0, height = 0; if (nonessential) { @@ -356,11 +390,13 @@ public class SkeletonBinary { if (mesh == null) return null; mesh.setPath(path); Color.rgba8888ToColor(mesh.getColor(), color); - mesh.setVertices(vertices); + mesh.setBones(vertices.bones); + mesh.setVertices(vertices.vertices); + mesh.setWorldVerticesLength(vertexCount << 1); mesh.setTriangles(triangles); mesh.setRegionUVs(uvs); mesh.updateUVs(); - mesh.setHullLength(hullLength * 2); + mesh.setHullLength(hullLength << 1); if (nonessential) { mesh.setEdges(edges); mesh.setWidth(width * scale); @@ -373,7 +409,7 @@ public class SkeletonBinary { int color = input.readInt(); String skinName = input.readString(); String parent = input.readString(); - boolean inheritFFD = input.readBoolean(); + boolean inheritDeform = input.readBoolean(); float width = 0, height = 0; if (nonessential) { width = input.readFloat(); @@ -385,7 +421,7 @@ public class SkeletonBinary { if (mesh == null) return null; mesh.setPath(path); Color.rgba8888ToColor(mesh.getColor(), color); - mesh.setInheritFFD(inheritFFD); + mesh.setInheritDeform(inheritDeform); if (nonessential) { mesh.setWidth(width * scale); mesh.setHeight(height * scale); @@ -393,80 +429,55 @@ public class SkeletonBinary { linkedMeshes.add(new LinkedMesh(mesh, skinName, slotIndex, parent)); return mesh; } - case weightedmesh: { - String path = input.readString(); - int color = input.readInt(); + case path: { + boolean closed = input.readBoolean(); + boolean constantSpeed = input.readBoolean(); int vertexCount = input.readInt(true); - float[] uvs = readFloatArray(input, vertexCount * 2, 1); - short[] triangles = readShortArray(input); - FloatArray weights = new FloatArray(uvs.length * 3 * 3); - IntArray bones = new IntArray(uvs.length * 3); - for (int i = 0; i < vertexCount; i++) { - int boneCount = (int)input.readFloat(); - bones.add(boneCount); - for (int ii = 0; ii < boneCount; ii++) { - bones.add((int)input.readFloat()); - weights.add(input.readFloat() * scale); - weights.add(input.readFloat() * scale); - weights.add(input.readFloat()); - } - } - int hullLength = input.readInt(true); - short[] edges = null; - float width = 0, height = 0; - if (nonessential) { - edges = readShortArray(input); - width = input.readFloat(); - height = input.readFloat(); - } + Vertices vertices = readVertices(input, vertexCount); + float[] lengths = new float[vertexCount / 3]; + for (int i = 0, n = lengths.length; i < n; i++) + lengths[i] = input.readFloat() * scale; + int color = nonessential ? input.readInt() : 0; - if (path == null) path = name; - WeightedMeshAttachment mesh = attachmentLoader.newWeightedMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.setPath(path); - Color.rgba8888ToColor(mesh.getColor(), color); - mesh.setBones(bones.toArray()); - mesh.setWeights(weights.toArray()); - mesh.setTriangles(triangles); - mesh.setRegionUVs(uvs); - mesh.updateUVs(); - mesh.setHullLength(hullLength * 2); - if (nonessential) { - mesh.setEdges(edges); - mesh.setWidth(width * scale); - mesh.setHeight(height * scale); - } - return mesh; - } - case weightedlinkedmesh: { - String path = input.readString(); - int color = input.readInt(); - String skinName = input.readString(); - String parent = input.readString(); - boolean inheritFFD = input.readBoolean(); - float width = 0, height = 0; - if (nonessential) { - width = input.readFloat(); - height = input.readFloat(); - } - - if (path == null) path = name; - WeightedMeshAttachment mesh = attachmentLoader.newWeightedMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.setPath(path); - Color.rgba8888ToColor(mesh.getColor(), color); - mesh.setInheritFFD(inheritFFD); - if (nonessential) { - mesh.setWidth(width * scale); - mesh.setHeight(height * scale); - } - linkedMeshes.add(new LinkedMesh(mesh, skinName, slotIndex, parent)); - return mesh; + PathAttachment path = attachmentLoader.newPathAttachment(skin, name); + if (path == null) return null; + path.setClosed(closed); + path.setConstantSpeed(constantSpeed); + path.setWorldVerticesLength(vertexCount << 1); + path.setVertices(vertices.vertices); + path.setBones(vertices.bones); + path.setLengths(lengths); + if (nonessential) Color.rgba8888ToColor(path.getColor(), color); + return path; } } return null; } + private Vertices readVertices (DataInput input, int vertexCount) throws IOException { + int verticesLength = vertexCount << 1; + Vertices vertices = new Vertices(); + if (!input.readBoolean()) { + vertices.vertices = readFloatArray(input, verticesLength, scale); + return vertices; + } + FloatArray weights = new FloatArray(verticesLength * 3 * 3); + IntArray bonesArray = new IntArray(verticesLength * 3); + for (int i = 0; i < vertexCount; i++) { + int boneCount = input.readInt(true); + bonesArray.add(boneCount); + for (int ii = 0; ii < boneCount; ii++) { + bonesArray.add(input.readInt(true)); + weights.add(input.readFloat() * scale); + weights.add(input.readFloat() * scale); + weights.add(input.readFloat()); + } + } + vertices.vertices = weights.toArray(); + vertices.bones = bonesArray.toArray(); + return vertices; + } + private float[] readFloatArray (DataInput input, int n, float scale) throws IOException { float[] array = new float[n]; if (scale == 1) { @@ -500,7 +511,7 @@ public class SkeletonBinary { int timelineType = input.readByte(); int frameCount = input.readInt(true); switch (timelineType) { - case TIMELINE_COLOR: { + case SLOT_COLOR: { ColorTimeline timeline = new ColorTimeline(frameCount); timeline.slotIndex = slotIndex; for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { @@ -510,10 +521,10 @@ public class SkeletonBinary { if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline); } timelines.add(timeline); - duration = Math.max(duration, timeline.getFrames()[frameCount * 5 - 5]); + duration = Math.max(duration, timeline.getFrames()[(frameCount - 1) * ColorTimeline.ENTRIES]); break; } - case TIMELINE_ATTACHMENT: + case SLOT_ATTACHMENT: AttachmentTimeline timeline = new AttachmentTimeline(frameCount); timeline.slotIndex = slotIndex; for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) @@ -532,7 +543,7 @@ public class SkeletonBinary { int timelineType = input.readByte(); int frameCount = input.readInt(true); switch (timelineType) { - case TIMELINE_ROTATE: { + case BONE_ROTATE: { RotateTimeline timeline = new RotateTimeline(frameCount); timeline.boneIndex = boneIndex; for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { @@ -540,17 +551,17 @@ public class SkeletonBinary { if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline); } timelines.add(timeline); - duration = Math.max(duration, timeline.getFrames()[frameCount * 2 - 2]); + duration = Math.max(duration, timeline.getFrames()[(frameCount - 1) * RotateTimeline.ENTRIES]); break; } - case TIMELINE_TRANSLATE: - case TIMELINE_SCALE: - case TIMELINE_SHEAR: { + case BONE_TRANSLATE: + case BONE_SCALE: + case BONE_SHEAR: { TranslateTimeline timeline; float timelineScale = 1; - if (timelineType == TIMELINE_SCALE) + if (timelineType == BONE_SCALE) timeline = new ScaleTimeline(frameCount); - else if (timelineType == TIMELINE_SHEAR) + else if (timelineType == BONE_SHEAR) timeline = new ShearTimeline(frameCount); else { timeline = new TranslateTimeline(frameCount); @@ -563,7 +574,7 @@ public class SkeletonBinary { if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline); } timelines.add(timeline); - duration = Math.max(duration, timeline.getFrames()[frameCount * 3 - 3]); + duration = Math.max(duration, timeline.getFrames()[(frameCount - 1) * TranslateTimeline.ENTRIES]); break; } } @@ -572,79 +583,116 @@ public class SkeletonBinary { // IK constraint timelines. for (int i = 0, n = input.readInt(true); i < n; i++) { - IkConstraintData constraint = skeletonData.ikConstraints.get(input.readInt(true)); + int index = input.readInt(true); int frameCount = input.readInt(true); IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount); - timeline.ikConstraintIndex = skeletonData.getIkConstraints().indexOf(constraint, true); + timeline.ikConstraintIndex = index; for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { timeline.setFrame(frameIndex, input.readFloat(), input.readFloat(), input.readByte()); if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline); } timelines.add(timeline); - duration = Math.max(duration, timeline.getFrames()[frameCount * 3 - 3]); + duration = Math.max(duration, timeline.getFrames()[(frameCount - 1) * IkConstraintTimeline.ENTRIES]); } // Transform constraint timelines. for (int i = 0, n = input.readInt(true); i < n; i++) { - TransformConstraintData constraint = skeletonData.transformConstraints.get(input.readInt(true)); + int index = input.readInt(true); int frameCount = input.readInt(true); TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount); - timeline.transformConstraintIndex = skeletonData.getTransformConstraints().indexOf(constraint, true); + timeline.transformConstraintIndex = index; for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { timeline.setFrame(frameIndex, input.readFloat(), input.readFloat(), input.readFloat(), input.readFloat(), input.readFloat()); if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline); } timelines.add(timeline); - duration = Math.max(duration, timeline.getFrames()[frameCount * 5 - 5]); + duration = Math.max(duration, timeline.getFrames()[(frameCount - 1) * TransformConstraintTimeline.ENTRIES]); } - // FFD timelines. + // Path constraint timelines. + for (int i = 0, n = input.readInt(true); i < n; i++) { + int index = input.readInt(true); + PathConstraintData data = skeletonData.getPathConstraints().get(index); + for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) { + int timelineType = input.readByte(); + int frameCount = input.readInt(true); + switch (timelineType) { + case PATH_POSITION: + case PATH_SPACING: { + PathConstraintPositionTimeline timeline; + float timelineScale = 1; + if (timelineType == PATH_SPACING) { + timeline = new PathConstraintSpacingTimeline(frameCount); + if (data.spacingMode == SpacingMode.length || data.spacingMode == SpacingMode.fixed) timelineScale = scale; + } else { + timeline = new PathConstraintPositionTimeline(frameCount); + if (data.positionMode == PositionMode.fixed) timelineScale = scale; + } + timeline.pathConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { + timeline.setFrame(frameIndex, input.readFloat(), input.readFloat() * timelineScale); + if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline); + } + timelines.add(timeline); + duration = Math.max(duration, timeline.getFrames()[(frameCount - 1) * PathConstraintPositionTimeline.ENTRIES]); + break; + } + case PATH_MIX: { + PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frameCount); + timeline.pathConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { + timeline.setFrame(frameIndex, input.readFloat(), input.readFloat(), input.readFloat()); + if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline); + } + timelines.add(timeline); + duration = Math.max(duration, timeline.getFrames()[(frameCount - 1) * PathConstraintMixTimeline.ENTRIES]); + break; + } + } + } + } + + // Deform timelines. for (int i = 0, n = input.readInt(true); i < n; i++) { Skin skin = skeletonData.skins.get(input.readInt(true)); for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) { int slotIndex = input.readInt(true); for (int iii = 0, nnn = input.readInt(true); iii < nnn; iii++) { - Attachment attachment = skin.getAttachment(slotIndex, input.readString()); + VertexAttachment attachment = (VertexAttachment)skin.getAttachment(slotIndex, input.readString()); + boolean weighted = attachment.getBones() != null; + float[] vertices = attachment.getVertices(); + int deformLength = weighted ? vertices.length / 3 * 2 : vertices.length; + int frameCount = input.readInt(true); - FfdTimeline timeline = new FfdTimeline(frameCount); + DeformTimeline timeline = new DeformTimeline(frameCount); timeline.slotIndex = slotIndex; timeline.attachment = attachment; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { float time = input.readFloat(); - - float[] vertices; - int vertexCount; - if (attachment instanceof MeshAttachment) - vertexCount = ((MeshAttachment)attachment).getVertices().length; - else - vertexCount = ((WeightedMeshAttachment)attachment).getWeights().length / 3 * 2; - + float[] deform; int end = input.readInt(true); - if (end == 0) { - if (attachment instanceof MeshAttachment) - vertices = ((MeshAttachment)attachment).getVertices(); - else - vertices = new float[vertexCount]; - } else { - vertices = new float[vertexCount]; + if (end == 0) + deform = weighted ? new float[deformLength] : vertices; + else { + deform = new float[deformLength]; int start = input.readInt(true); end += start; if (scale == 1) { for (int v = start; v < end; v++) - vertices[v] = input.readFloat(); + deform[v] = input.readFloat(); } else { for (int v = start; v < end; v++) - vertices[v] = input.readFloat() * scale; + deform[v] = input.readFloat() * scale; } - if (attachment instanceof MeshAttachment) { - float[] meshVertices = ((MeshAttachment)attachment).getVertices(); - for (int v = 0, vn = vertices.length; v < vn; v++) - vertices[v] += meshVertices[v]; + if (!weighted) { + for (int v = 0, vn = deform.length; v < vn; v++) + deform[v] += vertices[v]; } } - timeline.setFrame(frameIndex, time, vertices); + timeline.setFrame(frameIndex, time, deform); if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline); } timelines.add(timeline); @@ -708,6 +756,7 @@ public class SkeletonBinary { timelines.shrink(); skeletonData.animations.add(new Animation(name, timelines, duration)); + } private void readCurve (DataInput input, int frameIndex, CurveTimeline timeline) throws IOException { @@ -724,4 +773,9 @@ public class SkeletonBinary { void setCurve (CurveTimeline timeline, int frameIndex, float cx1, float cy1, float cx2, float cy2) { timeline.setCurve(frameIndex, cx1, cy1, cx2, cy2); } + + static class Vertices { + int[] bones; + float[] vertices; + } } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBounds.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBounds.java index d345a29a1..7ff9d0f86 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBounds.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBounds.java @@ -49,6 +49,7 @@ public class SkeletonBounds { }; public void update (Skeleton skeleton, boolean updateAabb) { + if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); Array boundingBoxes = this.boundingBoxes; Array polygons = this.polygons; Array slots = skeleton.slots; @@ -67,11 +68,7 @@ public class SkeletonBounds { FloatArray polygon = polygonPool.obtain(); polygons.add(polygon); - int vertexCount = boundingBox.getVertices().length; - polygon.ensureCapacity(vertexCount); - polygon.size = vertexCount; - - boundingBox.computeWorldVertices(slot.bone, polygon.items); + boundingBox.computeWorldVertices(slot, polygon.setSize(boundingBox.getWorldVerticesLength())); } } @@ -225,6 +222,7 @@ public class SkeletonBounds { /** Returns the polygon for the specified bounding box, or null. */ public FloatArray getPolygon (BoundingBoxAttachment boundingBox) { + if (boundingBox == null) throw new IllegalArgumentException("boundingBox cannot be null."); int index = boundingBoxes.indexOf(boundingBox, true); return index == -1 ? null : polygons.get(index); } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonData.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonData.java index 4914ebeb0..9b90d213f 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonData.java @@ -43,6 +43,7 @@ public class SkeletonData { final Array animations = new Array(); final Array ikConstraints = new Array(); final Array transformConstraints = new Array(); + final Array pathConstraints = new Array(); float width, height; String version, hash, imagesPath; @@ -89,7 +90,7 @@ public class SkeletonData { return null; } - /** @return -1 if the bone was not found. */ + /** @return -1 if the slot was not found. */ public int findSlotIndex (String slotName) { if (slotName == null) throw new IllegalArgumentException("slotName cannot be null."); Array slots = this.slots; @@ -188,6 +189,32 @@ public class SkeletonData { return null; } + // --- Path constraints + + public Array getPathConstraints () { + return pathConstraints; + } + + /** @return May be null. */ + public PathConstraintData findPathConstraint (String constraintName) { + if (constraintName == null) throw new IllegalArgumentException("constraintName cannot be null."); + Array pathConstraints = this.pathConstraints; + for (int i = 0, n = pathConstraints.size; i < n; i++) { + PathConstraintData constraint = pathConstraints.get(i); + if (constraint.name.equals(constraintName)) return constraint; + } + return null; + } + + /** @return -1 if the path constraint was not found. */ + public int findPathConstraintIndex (String pathConstraintName) { + if (pathConstraintName == null) throw new IllegalArgumentException("pathConstraintName cannot be null."); + Array pathConstraints = this.pathConstraints; + for (int i = 0, n = pathConstraints.size; i < n; i++) + if (pathConstraints.get(i).name.equals(pathConstraintName)) return i; + return -1; + } + // --- /** @return May be null. */ 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 5e97caabc..5a49e17c3 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -43,24 +43,31 @@ import com.badlogic.gdx.utils.SerializationException; import com.esotericsoftware.spine.Animation.AttachmentTimeline; import com.esotericsoftware.spine.Animation.ColorTimeline; import com.esotericsoftware.spine.Animation.CurveTimeline; +import com.esotericsoftware.spine.Animation.DeformTimeline; import com.esotericsoftware.spine.Animation.DrawOrderTimeline; import com.esotericsoftware.spine.Animation.EventTimeline; -import com.esotericsoftware.spine.Animation.FfdTimeline; import com.esotericsoftware.spine.Animation.IkConstraintTimeline; +import com.esotericsoftware.spine.Animation.PathConstraintMixTimeline; +import com.esotericsoftware.spine.Animation.PathConstraintPositionTimeline; +import com.esotericsoftware.spine.Animation.PathConstraintSpacingTimeline; import com.esotericsoftware.spine.Animation.RotateTimeline; import com.esotericsoftware.spine.Animation.ScaleTimeline; import com.esotericsoftware.spine.Animation.ShearTimeline; import com.esotericsoftware.spine.Animation.Timeline; import com.esotericsoftware.spine.Animation.TransformConstraintTimeline; import com.esotericsoftware.spine.Animation.TranslateTimeline; +import com.esotericsoftware.spine.PathConstraintData.PositionMode; +import com.esotericsoftware.spine.PathConstraintData.RotateMode; +import com.esotericsoftware.spine.PathConstraintData.SpacingMode; import com.esotericsoftware.spine.attachments.AtlasAttachmentLoader; import com.esotericsoftware.spine.attachments.Attachment; import com.esotericsoftware.spine.attachments.AttachmentLoader; import com.esotericsoftware.spine.attachments.AttachmentType; import com.esotericsoftware.spine.attachments.BoundingBoxAttachment; import com.esotericsoftware.spine.attachments.MeshAttachment; +import com.esotericsoftware.spine.attachments.PathAttachment; import com.esotericsoftware.spine.attachments.RegionAttachment; -import com.esotericsoftware.spine.attachments.WeightedMeshAttachment; +import com.esotericsoftware.spine.attachments.VertexAttachment; public class SkeletonJson { private final AttachmentLoader attachmentLoader; @@ -72,6 +79,7 @@ public class SkeletonJson { } public SkeletonJson (AttachmentLoader attachmentLoader) { + if (attachmentLoader == null) throw new IllegalArgumentException("attachmentLoader cannot be null."); this.attachmentLoader = attachmentLoader; } @@ -112,70 +120,22 @@ public class SkeletonJson { parent = skeletonData.findBone(parentName); if (parent == null) throw new SerializationException("Parent bone not found: " + parentName); } - BoneData boneData = new BoneData(boneMap.getString("name"), parent); - boneData.length = boneMap.getFloat("length", 0) * scale; - boneData.x = boneMap.getFloat("x", 0) * scale; - boneData.y = boneMap.getFloat("y", 0) * scale; - boneData.rotation = boneMap.getFloat("rotation", 0); - boneData.scaleX = boneMap.getFloat("scaleX", 1); - boneData.scaleY = boneMap.getFloat("scaleY", 1); - boneData.shearX = boneMap.getFloat("shearX", 0); - boneData.shearY = boneMap.getFloat("shearY", 0); - boneData.inheritScale = boneMap.getBoolean("inheritScale", true); - boneData.inheritRotation = boneMap.getBoolean("inheritRotation", true); + BoneData data = new BoneData(skeletonData.bones.size, boneMap.getString("name"), parent); + data.length = boneMap.getFloat("length", 0) * scale; + data.x = boneMap.getFloat("x", 0) * scale; + data.y = boneMap.getFloat("y", 0) * scale; + data.rotation = boneMap.getFloat("rotation", 0); + data.scaleX = boneMap.getFloat("scaleX", 1); + data.scaleY = boneMap.getFloat("scaleY", 1); + data.shearX = boneMap.getFloat("shearX", 0); + data.shearY = boneMap.getFloat("shearY", 0); + data.inheritRotation = boneMap.getBoolean("inheritRotation", true); + data.inheritScale = boneMap.getBoolean("inheritScale", true); String color = boneMap.getString("color", null); - if (color != null) boneData.getColor().set(Color.valueOf(color)); + if (color != null) data.getColor().set(Color.valueOf(color)); - skeletonData.bones.add(boneData); - } - - // IK constraints. - for (JsonValue ikMap = root.getChild("ik"); ikMap != null; ikMap = ikMap.next) { - IkConstraintData ikConstraintData = new IkConstraintData(ikMap.getString("name")); - - for (JsonValue boneMap = ikMap.getChild("bones"); boneMap != null; boneMap = boneMap.next) { - String boneName = boneMap.asString(); - BoneData bone = skeletonData.findBone(boneName); - if (bone == null) throw new SerializationException("IK bone not found: " + boneName); - ikConstraintData.bones.add(bone); - } - - String targetName = ikMap.getString("target"); - ikConstraintData.target = skeletonData.findBone(targetName); - if (ikConstraintData.target == null) throw new SerializationException("Target bone not found: " + targetName); - - ikConstraintData.bendDirection = ikMap.getBoolean("bendPositive", true) ? 1 : -1; - ikConstraintData.mix = ikMap.getFloat("mix", 1); - - skeletonData.ikConstraints.add(ikConstraintData); - } - - // Transform constraints. - for (JsonValue transformMap = root.getChild("transform"); transformMap != null; transformMap = transformMap.next) { - TransformConstraintData transformConstraintData = new TransformConstraintData(transformMap.getString("name")); - - String boneName = transformMap.getString("bone"); - transformConstraintData.bone = skeletonData.findBone(boneName); - if (transformConstraintData.bone == null) throw new SerializationException("Bone not found: " + boneName); - - String targetName = transformMap.getString("target"); - transformConstraintData.target = skeletonData.findBone(targetName); - if (transformConstraintData.target == null) throw new SerializationException("Target bone not found: " + targetName); - - transformConstraintData.offsetRotation = transformMap.getFloat("rotation", 0); - transformConstraintData.offsetX = transformMap.getFloat("x", 0) * scale; - transformConstraintData.offsetY = transformMap.getFloat("y", 0) * scale; - transformConstraintData.offsetScaleX = transformMap.getFloat("scaleX", 0) * scale; - transformConstraintData.offsetScaleY = transformMap.getFloat("scaleY", 0) * scale; - transformConstraintData.offsetShearY = transformMap.getFloat("shearY", 0) * scale; - - transformConstraintData.rotateMix = transformMap.getFloat("rotateMix", 1); - transformConstraintData.translateMix = transformMap.getFloat("translateMix", 1); - transformConstraintData.scaleMix = transformMap.getFloat("scaleMix", 1); - transformConstraintData.shearMix = transformMap.getFloat("shearMix", 1); - - skeletonData.transformConstraints.add(transformConstraintData); + skeletonData.bones.add(data); } // Slots. @@ -184,14 +144,94 @@ public class SkeletonJson { String boneName = slotMap.getString("bone"); BoneData boneData = skeletonData.findBone(boneName); if (boneData == null) throw new SerializationException("Slot bone not found: " + boneName); - SlotData slotData = new SlotData(slotName, boneData); + SlotData data = new SlotData(skeletonData.slots.size, slotName, boneData); String color = slotMap.getString("color", null); - if (color != null) slotData.getColor().set(Color.valueOf(color)); + if (color != null) data.getColor().set(Color.valueOf(color)); - slotData.attachmentName = slotMap.getString("attachment", null); - slotData.blendMode = BlendMode.valueOf(slotMap.getString("blend", BlendMode.normal.name())); - skeletonData.slots.add(slotData); + data.attachmentName = slotMap.getString("attachment", null); + data.blendMode = BlendMode.valueOf(slotMap.getString("blend", BlendMode.normal.name())); + skeletonData.slots.add(data); + } + + // IK constraints. + for (JsonValue constraintMap = root.getChild("ik"); constraintMap != null; constraintMap = constraintMap.next) { + IkConstraintData data = new IkConstraintData(constraintMap.getString("name")); + + for (JsonValue boneMap = constraintMap.getChild("bones"); boneMap != null; boneMap = boneMap.next) { + String boneName = boneMap.asString(); + BoneData bone = skeletonData.findBone(boneName); + if (bone == null) throw new SerializationException("IK bone not found: " + boneName); + data.bones.add(bone); + } + + String targetName = constraintMap.getString("target"); + data.target = skeletonData.findBone(targetName); + if (data.target == null) throw new SerializationException("Target bone not found: " + targetName); + + data.bendDirection = constraintMap.getBoolean("bendPositive", true) ? 1 : -1; + data.mix = constraintMap.getFloat("mix", 1); + + skeletonData.ikConstraints.add(data); + } + + // Transform constraints. + for (JsonValue constraintMap = root.getChild("transform"); constraintMap != null; constraintMap = constraintMap.next) { + TransformConstraintData data = new TransformConstraintData(constraintMap.getString("name")); + + for (JsonValue boneMap = constraintMap.getChild("bones"); boneMap != null; boneMap = boneMap.next) { + String boneName = boneMap.asString(); + BoneData bone = skeletonData.findBone(boneName); + if (bone == null) throw new SerializationException("Path bone not found: " + boneName); + data.bones.add(bone); + } + + String targetName = constraintMap.getString("target"); + data.target = skeletonData.findBone(targetName); + if (data.target == null) throw new SerializationException("Target bone not found: " + targetName); + + data.offsetRotation = constraintMap.getFloat("rotation", 0); + data.offsetX = constraintMap.getFloat("x", 0) * scale; + data.offsetY = constraintMap.getFloat("y", 0) * scale; + data.offsetScaleX = constraintMap.getFloat("scaleX", 0) * scale; + data.offsetScaleY = constraintMap.getFloat("scaleY", 0) * scale; + data.offsetShearY = constraintMap.getFloat("shearY", 0) * scale; + + data.rotateMix = constraintMap.getFloat("rotateMix", 1); + data.translateMix = constraintMap.getFloat("translateMix", 1); + data.scaleMix = constraintMap.getFloat("scaleMix", 1); + data.shearMix = constraintMap.getFloat("shearMix", 1); + + skeletonData.transformConstraints.add(data); + } + + // Path constraints. + for (JsonValue constraintMap = root.getChild("path"); constraintMap != null; constraintMap = constraintMap.next) { + PathConstraintData data = new PathConstraintData(constraintMap.getString("name")); + + for (JsonValue boneMap = constraintMap.getChild("bones"); boneMap != null; boneMap = boneMap.next) { + String boneName = boneMap.asString(); + BoneData bone = skeletonData.findBone(boneName); + if (bone == null) throw new SerializationException("Path bone not found: " + boneName); + data.bones.add(bone); + } + + String targetName = constraintMap.getString("target"); + data.target = skeletonData.findSlot(targetName); + if (data.target == null) throw new SerializationException("Target slot not found: " + targetName); + + data.positionMode = PositionMode.valueOf(constraintMap.getString("positionMode", "percent")); + data.spacingMode = SpacingMode.valueOf(constraintMap.getString("spacingMode", "length")); + data.rotateMode = RotateMode.valueOf(constraintMap.getString("rotateMode", "tangent")); + data.offsetRotation = constraintMap.getFloat("rotation", 0); + data.position = constraintMap.getFloat("position", 0); + if (data.positionMode == PositionMode.fixed) data.position *= scale; + data.spacing = constraintMap.getFloat("spacing", 0); + if (data.spacingMode == SpacingMode.length || data.spacingMode == SpacingMode.fixed) data.spacing *= scale; + data.rotateMix = constraintMap.getFloat("rotateMix", 1); + data.translateMix = constraintMap.getFloat("translateMix", 1); + + skeletonData.pathConstraints.add(data); } // Skins. @@ -201,7 +241,7 @@ public class SkeletonJson { int slotIndex = skeletonData.findSlotIndex(slotEntry.name); if (slotIndex == -1) throw new SerializationException("Slot not found: " + slotEntry.name); for (JsonValue entry = slotEntry.child; entry != null; entry = entry.next) { - Attachment attachment = readAttachment(skin, slotIndex, entry.name, entry); + Attachment attachment = readAttachment(entry, skin, slotIndex, entry.name); if (attachment != null) skin.addAttachment(slotIndex, entry.name, attachment); } } @@ -216,30 +256,23 @@ public class SkeletonJson { if (skin == null) throw new SerializationException("Skin not found: " + linkedMesh.skin); Attachment parent = skin.getAttachment(linkedMesh.slotIndex, linkedMesh.parent); if (parent == null) throw new SerializationException("Parent mesh not found: " + linkedMesh.parent); - if (linkedMesh.mesh instanceof MeshAttachment) { - MeshAttachment mesh = (MeshAttachment)linkedMesh.mesh; - mesh.setParentMesh((MeshAttachment)parent); - mesh.updateUVs(); - } else { - WeightedMeshAttachment mesh = (WeightedMeshAttachment)linkedMesh.mesh; - mesh.setParentMesh((WeightedMeshAttachment)parent); - mesh.updateUVs(); - } + linkedMesh.mesh.setParentMesh((MeshAttachment)parent); + linkedMesh.mesh.updateUVs(); } linkedMeshes.clear(); // Events. for (JsonValue eventMap = root.getChild("events"); eventMap != null; eventMap = eventMap.next) { - EventData eventData = new EventData(eventMap.name); - eventData.intValue = eventMap.getInt("int", 0); - eventData.floatValue = eventMap.getFloat("float", 0f); - eventData.stringValue = eventMap.getString("string", null); - skeletonData.events.add(eventData); + EventData data = new EventData(eventMap.name); + data.intValue = eventMap.getInt("int", 0); + data.floatValue = eventMap.getFloat("float", 0f); + data.stringValue = eventMap.getString("string", null); + skeletonData.events.add(data); } // Animations. for (JsonValue animationMap = root.getChild("animations"); animationMap != null; animationMap = animationMap.next) - readAnimation(animationMap.name, animationMap, skeletonData); + readAnimation(animationMap, animationMap.name, skeletonData); skeletonData.bones.shrink(); skeletonData.slots.shrink(); @@ -250,15 +283,20 @@ public class SkeletonJson { return skeletonData; } - private Attachment readAttachment (Skin skin, int slotIndex, String name, JsonValue map) { + private Attachment readAttachment (JsonValue map, Skin skin, int slotIndex, String name) { float scale = this.scale; name = map.getString("name", name); - String path = map.getString("path", name); String type = map.getString("type", AttachmentType.region.name()); + + // BOZO - Warning: These types are deprecated and will be removed in the near future. if (type.equals("skinnedmesh")) type = "weightedmesh"; + if (type.equals("weightedmesh")) type = "mesh"; + if (type.equals("weightedlinkedmesh")) type = "linkedmesh"; + switch (AttachmentType.valueOf(type)) { case region: { + String path = map.getString("path", name); RegionAttachment region = attachmentLoader.newRegionAttachment(skin, name, path); if (region == null) return null; region.setPath(path); @@ -279,16 +317,15 @@ public class SkeletonJson { case boundingbox: { BoundingBoxAttachment box = attachmentLoader.newBoundingBoxAttachment(skin, name); if (box == null) return null; - float[] vertices = map.require("vertices").asFloatArray(); - if (scale != 1) { - for (int i = 0, n = vertices.length; i < n; i++) - vertices[i] *= scale; - } - box.setVertices(vertices); + readVertices(map, box, map.getInt("vertexCount") << 1); + + String color = map.getString("color", null); + if (color != null) box.getColor().set(Color.valueOf(color)); return box; } case mesh: case linkedmesh: { + String path = map.getString("path", name); MeshAttachment mesh = attachmentLoader.newMeshAttachment(skin, name, path); if (mesh == null) return null; mesh.setPath(path); @@ -300,81 +337,73 @@ public class SkeletonJson { mesh.setHeight(map.getFloat("height", 0) * scale); String parent = map.getString("parent", null); - if (parent == null) { - float[] vertices = map.require("vertices").asFloatArray(); - if (scale != 1) { - for (int i = 0, n = vertices.length; i < n; i++) - vertices[i] *= scale; - } - mesh.setVertices(vertices); - mesh.setTriangles(map.require("triangles").asShortArray()); - mesh.setRegionUVs(map.require("uvs").asFloatArray()); - mesh.updateUVs(); - - if (map.has("hull")) mesh.setHullLength(map.require("hull").asInt() * 2); - if (map.has("edges")) mesh.setEdges(map.require("edges").asShortArray()); - } else { - mesh.setInheritFFD(map.getBoolean("ffd", true)); + if (parent != null) { + mesh.setInheritDeform(map.getBoolean("deform", true)); linkedMeshes.add(new LinkedMesh(mesh, map.getString("skin", null), slotIndex, parent)); + return mesh; } + + float[] uvs = map.require("uvs").asFloatArray(); + readVertices(map, mesh, uvs.length); + mesh.setTriangles(map.require("triangles").asShortArray()); + mesh.setRegionUVs(uvs); + mesh.updateUVs(); + + if (map.has("hull")) mesh.setHullLength(map.require("hull").asInt() * 2); + if (map.has("edges")) mesh.setEdges(map.require("edges").asShortArray()); return mesh; } - case weightedmesh: - case weightedlinkedmesh: { - WeightedMeshAttachment mesh = attachmentLoader.newWeightedMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.setPath(path); + case path: { + PathAttachment path = attachmentLoader.newPathAttachment(skin, name); + if (path == null) return null; + path.setClosed(map.getBoolean("closed", false)); + path.setConstantSpeed(map.getBoolean("constantSpeed", true)); + + int vertexCount = map.getInt("vertexCount"); + readVertices(map, path, vertexCount << 1); + + float[] lengths = new float[vertexCount / 3]; + int i = 0; + for (JsonValue curves = map.require("lengths").child; curves != null; curves = curves.next) + lengths[i++] = curves.asFloat() * scale; + path.setLengths(lengths); String color = map.getString("color", null); - if (color != null) mesh.getColor().set(Color.valueOf(color)); - - mesh.setWidth(map.getFloat("width", 0) * scale); - mesh.setHeight(map.getFloat("height", 0) * scale); - - String parent = map.getString("parent", null); - if (parent == null) { - float[] uvs = map.require("uvs").asFloatArray(); - float[] vertices = map.require("vertices").asFloatArray(); - FloatArray weights = new FloatArray(uvs.length * 3 * 3); - IntArray bones = new IntArray(uvs.length * 3); - for (int i = 0, n = vertices.length; i < n;) { - int boneCount = (int)vertices[i++]; - bones.add(boneCount); - for (int nn = i + boneCount * 4; i < nn; i += 4) { - bones.add((int)vertices[i]); - weights.add(vertices[i + 1] * scale); - weights.add(vertices[i + 2] * scale); - weights.add(vertices[i + 3]); - } - } - mesh.setBones(bones.toArray()); - mesh.setWeights(weights.toArray()); - mesh.setTriangles(map.require("triangles").asShortArray()); - mesh.setRegionUVs(uvs); - mesh.updateUVs(); - - if (map.has("hull")) mesh.setHullLength(map.require("hull").asInt() * 2); - if (map.has("edges")) mesh.setEdges(map.require("edges").asShortArray()); - } else { - mesh.setInheritFFD(map.getBoolean("ffd", true)); - linkedMeshes.add(new LinkedMesh(mesh, map.getString("skin", null), slotIndex, parent)); - } - return mesh; + if (color != null) path.getColor().set(Color.valueOf(color)); + return path; } } - - // RegionSequenceAttachment regionSequenceAttachment = (RegionSequenceAttachment)attachment; - // - // float fps = map.getFloat("fps"); - // regionSequenceAttachment.setFrameTime(fps); - // - // String modeString = map.getString("mode"); - // regionSequenceAttachment.setMode(modeString == null ? Mode.forward : Mode.valueOf(modeString)); - return null; } - private void readAnimation (String name, JsonValue map, SkeletonData skeletonData) { + private void readVertices (JsonValue map, VertexAttachment attachment, int verticesLength) { + attachment.setWorldVerticesLength(verticesLength); + float[] vertices = map.require("vertices").asFloatArray(); + if (verticesLength == vertices.length) { + if (scale != 1) { + for (int i = 0, n = vertices.length; i < n; i++) + vertices[i] *= scale; + } + attachment.setVertices(vertices); + return; + } + FloatArray weights = new FloatArray(verticesLength * 3 * 3); + IntArray bones = new IntArray(verticesLength * 3); + for (int i = 0, n = vertices.length; i < n;) { + int boneCount = (int)vertices[i++]; + bones.add(boneCount); + for (int nn = i + boneCount * 4; i < nn; i += 4) { + bones.add((int)vertices[i]); + weights.add(vertices[i + 1] * scale); + weights.add(vertices[i + 2] * scale); + weights.add(vertices[i + 3]); + } + } + attachment.setBones(bones.toArray()); + attachment.setVertices(weights.toArray()); + } + + private void readAnimation (JsonValue map, String name, SkeletonData skeletonData) { float scale = this.scale; Array timelines = new Array(); float duration = 0; @@ -383,7 +412,6 @@ public class SkeletonJson { for (JsonValue slotMap = map.getChild("slots"); slotMap != null; slotMap = slotMap.next) { int slotIndex = skeletonData.findSlotIndex(slotMap.name); if (slotIndex == -1) throw new SerializationException("Slot not found: " + slotMap.name); - for (JsonValue timelineMap = slotMap.child; timelineMap != null; timelineMap = timelineMap.next) { String timelineName = timelineMap.name; if (timelineName.equals("color")) { @@ -394,11 +422,11 @@ public class SkeletonJson { for (JsonValue valueMap = timelineMap.child; valueMap != null; valueMap = valueMap.next) { Color color = Color.valueOf(valueMap.getString("color")); timeline.setFrame(frameIndex, valueMap.getFloat("time"), color.r, color.g, color.b, color.a); - readCurve(timeline, frameIndex, valueMap); + readCurve(valueMap, timeline, frameIndex); frameIndex++; } timelines.add(timeline); - duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() * 5 - 5]); + duration = Math.max(duration, timeline.getFrames()[(timeline.getFrameCount() - 1) * ColorTimeline.ENTRIES]); } else if (timelineName.equals("attachment")) { AttachmentTimeline timeline = new AttachmentTimeline(timelineMap.size); @@ -418,7 +446,6 @@ public class SkeletonJson { for (JsonValue boneMap = map.getChild("bones"); boneMap != null; boneMap = boneMap.next) { int boneIndex = skeletonData.findBoneIndex(boneMap.name); if (boneIndex == -1) throw new SerializationException("Bone not found: " + boneMap.name); - for (JsonValue timelineMap = boneMap.child; timelineMap != null; timelineMap = timelineMap.next) { String timelineName = timelineMap.name; if (timelineName.equals("rotate")) { @@ -428,11 +455,11 @@ public class SkeletonJson { int frameIndex = 0; for (JsonValue valueMap = timelineMap.child; valueMap != null; valueMap = valueMap.next) { timeline.setFrame(frameIndex, valueMap.getFloat("time"), valueMap.getFloat("angle")); - readCurve(timeline, frameIndex, valueMap); + readCurve(valueMap, timeline, frameIndex); frameIndex++; } timelines.add(timeline); - duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() * 2 - 2]); + duration = Math.max(duration, timeline.getFrames()[(timeline.getFrameCount() - 1) * RotateTimeline.ENTRIES]); } else if (timelineName.equals("translate") || timelineName.equals("scale") || timelineName.equals("shear")) { TranslateTimeline timeline; @@ -451,11 +478,11 @@ public class SkeletonJson { for (JsonValue valueMap = timelineMap.child; valueMap != null; valueMap = valueMap.next) { float x = valueMap.getFloat("x", 0), y = valueMap.getFloat("y", 0); timeline.setFrame(frameIndex, valueMap.getFloat("time"), x * timelineScale, y * timelineScale); - readCurve(timeline, frameIndex, valueMap); + readCurve(valueMap, timeline, frameIndex); frameIndex++; } timelines.add(timeline); - duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() * 3 - 3]); + duration = Math.max(duration, timeline.getFrames()[(timeline.getFrameCount() - 1) * TranslateTimeline.ENTRIES]); } else throw new RuntimeException("Invalid timeline type for a bone: " + timelineName + " (" + boneMap.name + ")"); @@ -470,12 +497,12 @@ public class SkeletonJson { int frameIndex = 0; for (JsonValue valueMap = constraintMap.child; valueMap != null; valueMap = valueMap.next) { timeline.setFrame(frameIndex, valueMap.getFloat("time"), valueMap.getFloat("mix", 1), - valueMap.getBoolean("bendPositive") ? 1 : -1); - readCurve(timeline, frameIndex, valueMap); + valueMap.getBoolean("bendPositive", false) ? 1 : -1); + readCurve(valueMap, timeline, frameIndex); frameIndex++; } timelines.add(timeline); - duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() * 3 - 3]); + duration = Math.max(duration, timeline.getFrames()[(timeline.getFrameCount() - 1) * IkConstraintTimeline.ENTRIES]); } // Transform constraint timelines. @@ -487,59 +514,98 @@ public class SkeletonJson { for (JsonValue valueMap = constraintMap.child; valueMap != null; valueMap = valueMap.next) { timeline.setFrame(frameIndex, valueMap.getFloat("time"), valueMap.getFloat("rotateMix", 1), valueMap.getFloat("translateMix", 1), valueMap.getFloat("scaleMix", 1), valueMap.getFloat("shearMix", 1)); - readCurve(timeline, frameIndex, valueMap); + readCurve(valueMap, timeline, frameIndex); frameIndex++; } timelines.add(timeline); - duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() * 5 - 5]); + duration = Math.max(duration, + timeline.getFrames()[(timeline.getFrameCount() - 1) * TransformConstraintTimeline.ENTRIES]); } - // FFD timelines. - for (JsonValue ffdMap = map.getChild("ffd"); ffdMap != null; ffdMap = ffdMap.next) { - Skin skin = skeletonData.findSkin(ffdMap.name); - if (skin == null) throw new SerializationException("Skin not found: " + ffdMap.name); - for (JsonValue slotMap = ffdMap.child; slotMap != null; slotMap = slotMap.next) { + // Path constraint timelines. + for (JsonValue constraintMap = map.getChild("paths"); constraintMap != null; constraintMap = constraintMap.next) { + int index = skeletonData.findPathConstraintIndex(constraintMap.name); + if (index == -1) throw new SerializationException("Path constraint not found: " + constraintMap.name); + PathConstraintData data = skeletonData.getPathConstraints().get(index); + for (JsonValue timelineMap = constraintMap.child; timelineMap != null; timelineMap = timelineMap.next) { + String timelineName = timelineMap.name; + if (timelineName.equals("position") || timelineName.equals("spacing")) { + PathConstraintPositionTimeline timeline; + float timelineScale = 1; + if (timelineName.equals("spacing")) { + timeline = new PathConstraintSpacingTimeline(timelineMap.size); + if (data.spacingMode == SpacingMode.length || data.spacingMode == SpacingMode.fixed) timelineScale = scale; + } else { + timeline = new PathConstraintPositionTimeline(timelineMap.size); + if (data.positionMode == PositionMode.fixed) timelineScale = scale; + } + timeline.pathConstraintIndex = index; + int frameIndex = 0; + for (JsonValue valueMap = timelineMap.child; valueMap != null; valueMap = valueMap.next) { + timeline.setFrame(frameIndex, valueMap.getFloat("time"), valueMap.getFloat(timelineName, 0) * timelineScale); + readCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.add(timeline); + duration = Math.max(duration, + timeline.getFrames()[(timeline.getFrameCount() - 1) * PathConstraintPositionTimeline.ENTRIES]); + } else if (timelineName.equals("mix")) { + PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(timelineMap.size); + timeline.pathConstraintIndex = index; + int frameIndex = 0; + for (JsonValue valueMap = timelineMap.child; valueMap != null; valueMap = valueMap.next) { + timeline.setFrame(frameIndex, valueMap.getFloat("time"), valueMap.getFloat("rotateMix", 1), + valueMap.getFloat("translateMix", 1)); + readCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.add(timeline); + duration = Math.max(duration, + timeline.getFrames()[(timeline.getFrameCount() - 1) * PathConstraintMixTimeline.ENTRIES]); + } + } + } + + // Deform timelines. + for (JsonValue deformMap = map.getChild("deform"); deformMap != null; deformMap = deformMap.next) { + Skin skin = skeletonData.findSkin(deformMap.name); + if (skin == null) throw new SerializationException("Skin not found: " + deformMap.name); + for (JsonValue slotMap = deformMap.child; slotMap != null; slotMap = slotMap.next) { int slotIndex = skeletonData.findSlotIndex(slotMap.name); if (slotIndex == -1) throw new SerializationException("Slot not found: " + slotMap.name); - for (JsonValue meshMap = slotMap.child; meshMap != null; meshMap = meshMap.next) { - FfdTimeline timeline = new FfdTimeline(meshMap.size); - Attachment attachment = skin.getAttachment(slotIndex, meshMap.name); - if (attachment == null) throw new SerializationException("FFD attachment not found: " + meshMap.name); + for (JsonValue timelineMap = slotMap.child; timelineMap != null; timelineMap = timelineMap.next) { + VertexAttachment attachment = (VertexAttachment)skin.getAttachment(slotIndex, timelineMap.name); + if (attachment == null) throw new SerializationException("Deform attachment not found: " + timelineMap.name); + boolean weighted = attachment.getBones() != null; + float[] vertices = attachment.getVertices(); + int deformLength = weighted ? vertices.length / 3 * 2 : vertices.length; + + DeformTimeline timeline = new DeformTimeline(timelineMap.size); timeline.slotIndex = slotIndex; timeline.attachment = attachment; - int vertexCount; - if (attachment instanceof MeshAttachment) - vertexCount = ((MeshAttachment)attachment).getVertices().length; - else - vertexCount = ((WeightedMeshAttachment)attachment).getWeights().length / 3 * 2; - int frameIndex = 0; - for (JsonValue valueMap = meshMap.child; valueMap != null; valueMap = valueMap.next) { - float[] vertices; + for (JsonValue valueMap = timelineMap.child; valueMap != null; valueMap = valueMap.next) { + float[] deform; JsonValue verticesValue = valueMap.get("vertices"); - if (verticesValue == null) { - if (attachment instanceof MeshAttachment) - vertices = ((MeshAttachment)attachment).getVertices(); - else - vertices = new float[vertexCount]; - } else { - vertices = new float[vertexCount]; + if (verticesValue == null) + deform = weighted ? new float[deformLength] : vertices; + else { + deform = new float[deformLength]; int start = valueMap.getInt("offset", 0); - System.arraycopy(verticesValue.asFloatArray(), 0, vertices, start, verticesValue.size); + System.arraycopy(verticesValue.asFloatArray(), 0, deform, start, verticesValue.size); if (scale != 1) { for (int i = start, n = i + verticesValue.size; i < n; i++) - vertices[i] *= scale; + deform[i] *= scale; } - if (attachment instanceof MeshAttachment) { - float[] meshVertices = ((MeshAttachment)attachment).getVertices(); - for (int i = 0; i < vertexCount; i++) - vertices[i] += meshVertices[i]; + if (!weighted) { + for (int i = 0; i < deformLength; i++) + deform[i] += vertices[i]; } } - timeline.setFrame(frameIndex, valueMap.getFloat("time"), vertices); - readCurve(timeline, frameIndex, valueMap); + timeline.setFrame(frameIndex, valueMap.getFloat("time"), deform); + readCurve(valueMap, timeline, frameIndex); frameIndex++; } timelines.add(timeline); @@ -608,8 +674,8 @@ public class SkeletonJson { skeletonData.animations.add(new Animation(name, timelines, duration)); } - void readCurve (CurveTimeline timeline, int frameIndex, JsonValue valueMap) { - JsonValue curve = valueMap.get("curve"); + void readCurve (JsonValue map, CurveTimeline timeline, int frameIndex) { + JsonValue curve = map.get("curve"); if (curve == null) return; if (curve.isString() && curve.asString().equals("stepped")) timeline.setStepped(frameIndex); @@ -621,9 +687,9 @@ public class SkeletonJson { static class LinkedMesh { String parent, skin; int slotIndex; - Attachment mesh; + MeshAttachment mesh; - public LinkedMesh (Attachment mesh, String skin, int slotIndex, String parent) { + public LinkedMesh (MeshAttachment mesh, String skin, int slotIndex, String parent) { this.mesh = mesh; this.skin = skin; this.slotIndex = slotIndex; diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonMeshRenderer.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonMeshRenderer.java index 9033b90c8..e6db7b114 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonMeshRenderer.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonMeshRenderer.java @@ -35,10 +35,9 @@ import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.PolygonSpriteBatch; import com.badlogic.gdx.utils.Array; import com.esotericsoftware.spine.attachments.Attachment; -import com.esotericsoftware.spine.attachments.MeshAttachment; import com.esotericsoftware.spine.attachments.RegionAttachment; import com.esotericsoftware.spine.attachments.SkeletonAttachment; -import com.esotericsoftware.spine.attachments.WeightedMeshAttachment; +import com.esotericsoftware.spine.attachments.MeshAttachment; public class SkeletonMeshRenderer extends SkeletonRenderer { static private final short[] quadTriangles = {0, 1, 2, 2, 3, 0}; @@ -67,12 +66,6 @@ public class SkeletonMeshRenderer extends SkeletonRenderer { triangles = mesh.getTriangles(); texture = mesh.getRegion().getTexture(); - } else if (attachment instanceof WeightedMeshAttachment) { - WeightedMeshAttachment mesh = (WeightedMeshAttachment)attachment; - vertices = mesh.updateWorldVertices(slot, premultipliedAlpha); - triangles = mesh.getTriangles(); - texture = mesh.getRegion().getTexture(); - } else if (attachment instanceof SkeletonAttachment) { Skeleton attachmentSkeleton = ((SkeletonAttachment)attachment).getSkeleton(); if (attachmentSkeleton == null) continue; 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 eee9055b3..5a4321727 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java @@ -34,18 +34,13 @@ package com.esotericsoftware.spine; import com.badlogic.gdx.graphics.g2d.Batch; import com.badlogic.gdx.utils.Array; import com.esotericsoftware.spine.attachments.Attachment; -import com.esotericsoftware.spine.attachments.MeshAttachment; import com.esotericsoftware.spine.attachments.RegionAttachment; import com.esotericsoftware.spine.attachments.SkeletonAttachment; -import com.esotericsoftware.spine.attachments.WeightedMeshAttachment; +import com.esotericsoftware.spine.attachments.MeshAttachment; public class SkeletonRenderer { boolean premultipliedAlpha; - public SkeletonRenderer () { - super(); - } - public void draw (T batch, Skeleton skeleton) { boolean premultipliedAlpha = this.premultipliedAlpha; BlendMode blendMode = null; @@ -64,7 +59,7 @@ public class SkeletonRenderer { } batch.draw(regionAttachment.getRegion().getTexture(), vertices, 0, 20); - } else if (attachment instanceof MeshAttachment || attachment instanceof WeightedMeshAttachment) { + } else if (attachment instanceof MeshAttachment) { throw new RuntimeException("SkeletonMeshRenderer is required to render meshes."); } else if (attachment instanceof SkeletonAttachment) { 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 130a08efb..9aabd2662 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRendererDebug.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRendererDebug.java @@ -31,11 +31,6 @@ package com.esotericsoftware.spine; -import com.esotericsoftware.spine.attachments.Attachment; -import com.esotericsoftware.spine.attachments.MeshAttachment; -import com.esotericsoftware.spine.attachments.RegionAttachment; -import com.esotericsoftware.spine.attachments.WeightedMeshAttachment; - import static com.badlogic.gdx.graphics.g2d.Batch.*; import com.badlogic.gdx.Gdx; @@ -45,19 +40,24 @@ import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType; 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.MeshAttachment; +import com.esotericsoftware.spine.attachments.PathAttachment; +import com.esotericsoftware.spine.attachments.RegionAttachment; public class SkeletonRendererDebug { static private final Color boneLineColor = Color.RED; static private final Color boneOriginColor = Color.GREEN; 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 boundingBoxColor = new Color(0, 1, 0, 0.8f); static private final Color aabbColor = new Color(0, 1, 0, 0.5f); private final ShapeRenderer shapes; private boolean drawBones = true, drawRegionAttachments = true, drawBoundingBoxes = true; - private boolean drawMeshHull = true, drawMeshTriangles = true; + private boolean drawMeshHull = true, drawMeshTriangles = true, drawPaths = true; private final SkeletonBounds bounds = new SkeletonBounds(); + private final FloatArray temp = new FloatArray(); private float scale = 1; private float boneWidth = 2; private boolean premultipliedAlpha; @@ -119,23 +119,12 @@ public class SkeletonRendererDebug { for (int i = 0, n = slots.size; i < n; i++) { Slot slot = slots.get(i); Attachment attachment = slot.attachment; - float[] vertices = null; - short[] triangles = null; - int hullLength = 0; - if (attachment instanceof MeshAttachment) { - MeshAttachment mesh = (MeshAttachment)attachment; - mesh.updateWorldVertices(slot, false); - vertices = mesh.getWorldVertices(); - triangles = mesh.getTriangles(); - hullLength = mesh.getHullLength(); - } else if (attachment instanceof WeightedMeshAttachment) { - WeightedMeshAttachment mesh = (WeightedMeshAttachment)attachment; - mesh.updateWorldVertices(slot, false); - vertices = mesh.getWorldVertices(); - triangles = mesh.getTriangles(); - hullLength = mesh.getHullLength(); - } - if (vertices == null || triangles == null) continue; + if (!(attachment instanceof MeshAttachment)) continue; + MeshAttachment mesh = (MeshAttachment)attachment; + mesh.updateWorldVertices(slot, false); + float[] vertices = mesh.getWorldVertices(); + short[] triangles = mesh.getTriangles(); + int hullLength = mesh.getHullLength(); if (drawMeshTriangles) { shapes.setColor(triangleLineColor); for (int ii = 0, nn = triangles.length; ii < nn; ii += 3) { @@ -143,12 +132,12 @@ public class SkeletonRendererDebug { shapes.triangle(vertices[v1], vertices[v1 + 1], // vertices[v2], vertices[v2 + 1], // vertices[v3], vertices[v3 + 1] // - ); + ); } } if (drawMeshHull && hullLength > 0) { shapes.setColor(attachmentLineColor); - hullLength = hullLength / 2 * 5; + hullLength = (hullLength >> 1) * 5; float lastX = vertices[hullLength - 5], lastY = vertices[hullLength - 4]; for (int ii = 0, nn = hullLength; ii < nn; ii += 5) { float x = vertices[ii], y = vertices[ii + 1]; @@ -165,14 +154,53 @@ public class SkeletonRendererDebug { bounds.update(skeleton, true); shapes.setColor(aabbColor); shapes.rect(bounds.getMinX(), bounds.getMinY(), bounds.getWidth(), bounds.getHeight()); - shapes.setColor(boundingBoxColor); Array polygons = bounds.getPolygons(); + Array boxes = bounds.getBoundingBoxes(); for (int i = 0, n = polygons.size; i < n; i++) { FloatArray polygon = polygons.get(i); + shapes.setColor(boxes.get(i).getColor()); shapes.polygon(polygon.items, 0, polygon.size); } } + if (drawPaths) { + Array slots = skeleton.getSlots(); + for (int i = 0, n = slots.size; i < n; i++) { + Slot slot = slots.get(i); + Attachment attachment = slot.attachment; + if (!(attachment instanceof PathAttachment)) continue; + PathAttachment path = (PathAttachment)attachment; + int nn = path.getWorldVerticesLength(); + float[] world = temp.setSize(nn); + path.computeWorldVertices(slot, world); + Color color = path.getColor(); + float x1 = world[2], y1 = world[3], x2 = 0, y2 = 0; + if (path.getClosed()) { + shapes.setColor(color); + float cx1 = world[0], cy1 = world[1], cx2 = world[nn - 2], cy2 = world[nn - 1]; + x2 = world[nn - 4]; + y2 = world[nn - 3]; + shapes.curve(x1, y1, cx1, cy1, cx2, cy2, x2, y2, 32); + shapes.setColor(Color.LIGHT_GRAY); + shapes.line(x1, y1, cx1, cy1); + shapes.line(x2, y2, cx2, cy2); + } + nn -= 4; + for (int ii = 4; ii < nn; ii += 6) { + float cx1 = world[ii], cy1 = world[ii + 1], cx2 = world[ii + 2], cy2 = world[ii + 3]; + x2 = world[ii + 4]; + y2 = world[ii + 5]; + shapes.setColor(color); + shapes.curve(x1, y1, cx1, cy1, cx2, cy2, x2, y2, 32); + shapes.setColor(Color.LIGHT_GRAY); + shapes.line(x1, y1, cx1, cy1); + shapes.line(x2, y2, cx2, cy2); + x1 = x2; + y1 = y2; + } + } + } + shapes.end(); shapes.begin(ShapeType.Filled); @@ -186,6 +214,7 @@ public class SkeletonRendererDebug { } shapes.end(); + } public ShapeRenderer getShapeRenderer () { @@ -216,6 +245,10 @@ public class SkeletonRendererDebug { this.drawMeshTriangles = meshTriangles; } + public void setPaths (boolean paths) { + this.drawPaths = paths; + } + public void setPremultipliedAlpha (boolean premultipliedAlpha) { this.premultipliedAlpha = premultipliedAlpha; } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slot.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slot.java index c5f510051..f93478018 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slot.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slot.java @@ -44,12 +44,6 @@ public class Slot { private float attachmentTime; private FloatArray attachmentVertices = new FloatArray(); - Slot (SlotData data) { - this.data = data; - bone = null; - color = new Color(1, 1, 1, 1); - } - public Slot (SlotData data, Bone bone) { if (data == null) throw new IllegalArgumentException("data cannot be null."); if (bone == null) throw new IllegalArgumentException("bone cannot be null."); @@ -110,6 +104,7 @@ public class Slot { } public void setAttachmentVertices (FloatArray attachmentVertices) { + if (attachmentVertices == null) throw new IllegalArgumentException("attachmentVertices cannot be null."); this.attachmentVertices = attachmentVertices; } @@ -117,20 +112,16 @@ public class Slot { return attachmentVertices; } - void setToSetupPose (int slotIndex) { + public void setToSetupPose () { color.set(data.color); if (data.attachmentName == null) setAttachment(null); else { attachment = null; - setAttachment(bone.skeleton.getAttachment(slotIndex, data.attachmentName)); + setAttachment(bone.skeleton.getAttachment(data.index, data.attachmentName)); } } - public void setToSetupPose () { - setToSetupPose(bone.skeleton.data.slots.indexOf(data, true)); - } - public String toString () { return data.name; } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SlotData.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SlotData.java index 951e42d23..5625b1b7d 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SlotData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SlotData.java @@ -34,24 +34,26 @@ package com.esotericsoftware.spine; import com.badlogic.gdx.graphics.Color; public class SlotData { + final int index; final String name; final BoneData boneData; final Color color = new Color(1, 1, 1, 1); String attachmentName; BlendMode blendMode; - SlotData () { - name = null; - boneData = null; - } - - public SlotData (String name, BoneData boneData) { + public SlotData (int index, String name, BoneData boneData) { + if (index < 0) throw new IllegalArgumentException("index must be >= 0."); if (name == null) throw new IllegalArgumentException("name cannot be null."); if (boneData == null) throw new IllegalArgumentException("boneData cannot be null."); + this.index = index; this.name = name; this.boneData = boneData; } + public int getIndex () { + return index; + } + public String getName () { return name; } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraint.java index d95bdf301..2b6be66cd 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraint.java @@ -4,46 +4,42 @@ package com.esotericsoftware.spine; import static com.badlogic.gdx.math.MathUtils.*; import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.Array; public class TransformConstraint implements Updatable { final TransformConstraintData data; - Bone bone, target; + final Array bones; + Bone target; float rotateMix, translateMix, scaleMix, shearMix; - float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; final Vector2 temp = new Vector2(); public TransformConstraint (TransformConstraintData data, Skeleton skeleton) { + if (data == null) throw new IllegalArgumentException("data cannot be null."); + if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); this.data = data; - translateMix = data.translateMix; rotateMix = data.rotateMix; + translateMix = data.translateMix; scaleMix = data.scaleMix; shearMix = data.shearMix; - offsetX = data.offsetX; - offsetY = data.offsetY; - offsetScaleX = data.offsetScaleX; - offsetScaleY = data.offsetScaleY; - offsetShearY = data.offsetShearY; - - if (skeleton != null) { - bone = skeleton.findBone(data.bone.name); - target = skeleton.findBone(data.target.name); - } + bones = new Array(data.bones.size); + for (BoneData boneData : data.bones) + bones.add(skeleton.findBone(boneData.name)); + target = skeleton.findBone(data.target.name); } /** Copy constructor. */ public TransformConstraint (TransformConstraint constraint, Skeleton skeleton) { + if (constraint == null) throw new IllegalArgumentException("constraint cannot be null."); + if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); data = constraint.data; - bone = skeleton.bones.get(constraint.bone.skeleton.bones.indexOf(constraint.bone, true)); - target = skeleton.bones.get(constraint.target.skeleton.bones.indexOf(constraint.target, true)); - translateMix = constraint.translateMix; + bones = new Array(constraint.bones.size); + for (Bone bone : constraint.bones) + bones.add(skeleton.bones.get(bone.data.index)); + target = skeleton.bones.get(constraint.target.data.index); rotateMix = constraint.rotateMix; + translateMix = constraint.translateMix; scaleMix = constraint.scaleMix; shearMix = constraint.shearMix; - offsetX = constraint.offsetX; - offsetY = constraint.offsetY; - offsetScaleX = constraint.offsetScaleX; - offsetScaleY = constraint.offsetScaleY; - offsetShearY = constraint.offsetShearY; } public void apply () { @@ -51,64 +47,64 @@ public class TransformConstraint implements Updatable { } public void update () { - Bone bone = this.bone; + float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; Bone target = this.target; + float ta = target.a, tb = target.b, tc = target.c, td = target.d; + Array bones = this.bones; + for (int i = 0, n = bones.size; i < n; i++) { + Bone bone = bones.get(i); - if (rotateMix > 0) { - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float r = atan2(target.c, target.a) - atan2(c, a) + offsetRotation * degRad; - if (r > PI) - r -= PI2; - else if (r < -PI) r += PI2; - r *= rotateMix; - float cos = cos(r), sin = sin(r); - bone.a = cos * a - sin * c; - bone.b = cos * b - sin * d; - bone.c = sin * a + cos * c; - bone.d = sin * b + cos * d; - } + if (rotateMix > 0) { + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float r = atan2(tc, ta) - atan2(c, a) + data.offsetRotation * degRad; + if (r > PI) + r -= PI2; + else if (r < -PI) r += PI2; + r *= rotateMix; + float cos = cos(r), sin = sin(r); + bone.a = cos * a - sin * c; + bone.b = cos * b - sin * d; + bone.c = sin * a + cos * c; + bone.d = sin * b + cos * d; + } - if (scaleMix > 0) { - float bs = (float)Math.sqrt(bone.a * bone.a + bone.c * bone.c); - float ts = (float)Math.sqrt(target.a * target.a + target.c * target.c); - float s = bs > 0.00001f ? (bs + (ts - bs + offsetScaleX) * scaleMix) / bs : 0; - bone.a *= s; - bone.c *= s; - bs = (float)Math.sqrt(bone.b * bone.b + bone.d * bone.d); - ts = (float)Math.sqrt(target.b * target.b + target.d * target.d); - s = bs > 0.00001f ? (bs + (ts - bs + offsetScaleY) * scaleMix) / bs : 0; - bone.b *= s; - bone.d *= s; - } + if (translateMix > 0) { + Vector2 temp = this.temp; + target.localToWorld(temp.set(data.offsetX, data.offsetY)); + bone.worldX += (temp.x - bone.worldX) * translateMix; + bone.worldY += (temp.y - bone.worldY) * translateMix; + } - if (shearMix > 0) { - float b = bone.b, d = bone.d; - float by = atan2(d, b); - float r = atan2(target.d, target.b) - atan2(target.c, target.a) - (by - atan2(bone.c, bone.a)); - if (r > PI) - r -= PI2; - else if (r < -PI) r += PI2; - r = by + (r + offsetShearY * degRad) * shearMix; - float s = (float)Math.sqrt(b * b + d * d); - bone.b = cos(r) * s; - bone.d = sin(r) * s; - } + if (scaleMix > 0) { + float bs = (float)Math.sqrt(bone.a * bone.a + bone.c * bone.c); + float ts = (float)Math.sqrt(ta * ta + tc * tc); + float s = bs > 0.00001f ? (bs + (ts - bs + data.offsetScaleX) * scaleMix) / bs : 0; + bone.a *= s; + bone.c *= s; + bs = (float)Math.sqrt(bone.b * bone.b + bone.d * bone.d); + ts = (float)Math.sqrt(tb * tb + td * td); + s = bs > 0.00001f ? (bs + (ts - bs + data.offsetScaleY) * scaleMix) / bs : 0; + bone.b *= s; + bone.d *= s; + } - float translateMix = this.translateMix; - if (translateMix > 0) { - Vector2 temp = this.temp; - target.localToWorld(temp.set(offsetX, offsetY)); - bone.worldX += (temp.x - bone.worldX) * translateMix; - bone.worldY += (temp.y - bone.worldY) * translateMix; + if (shearMix > 0) { + float b = bone.b, d = bone.d; + float by = atan2(d, b); + float r = atan2(td, tb) - atan2(tc, ta) - (by - atan2(bone.c, bone.a)); + if (r > PI) + r -= PI2; + else if (r < -PI) r += PI2; + r = by + (r + data.offsetShearY * degRad) * shearMix; + float s = (float)Math.sqrt(b * b + d * d); + bone.b = cos(r) * s; + bone.d = sin(r) * s; + } } } - public Bone getBone () { - return bone; - } - - public void setBone (Bone bone) { - this.bone = bone; + public Array getBones () { + return bones; } public Bone getTarget () { @@ -151,54 +147,6 @@ public class TransformConstraint implements Updatable { this.shearMix = shearMix; } - public float getOffsetRotation () { - return offsetRotation; - } - - public void setOffsetRotation (float offsetRotation) { - this.offsetRotation = offsetRotation; - } - - public float getOffsetX () { - return offsetX; - } - - public void setOffsetX (float offsetX) { - this.offsetX = offsetX; - } - - public float getOffsetY () { - return offsetY; - } - - public void setOffsetY (float offsetY) { - this.offsetY = offsetY; - } - - public float getOffsetScaleX () { - return offsetScaleX; - } - - public void setOffsetScaleX (float offsetScaleX) { - this.offsetScaleX = offsetScaleX; - } - - public float getOffsetScaleY () { - return offsetScaleY; - } - - public void setOffsetScaleY (float offsetScaleY) { - this.offsetScaleY = offsetScaleY; - } - - public float getOffsetShearY () { - return offsetShearY; - } - - public void setOffsetShearY (float offsetShearY) { - this.offsetShearY = offsetShearY; - } - public TransformConstraintData getData () { return data; } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraintData.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraintData.java index 20d652603..e20700526 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraintData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraintData.java @@ -1,13 +1,17 @@ package com.esotericsoftware.spine; +import com.badlogic.gdx.utils.Array; + public class TransformConstraintData { final String name; - BoneData bone, target; + final Array bones = new Array(); + BoneData target; float rotateMix, translateMix, scaleMix, shearMix; float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; public TransformConstraintData (String name) { + if (name == null) throw new IllegalArgumentException("name cannot be null."); this.name = name; } @@ -15,12 +19,8 @@ public class TransformConstraintData { return name; } - public BoneData getBone () { - return bone; - } - - public void setBone (BoneData bone) { - this.bone = bone; + public Array getBones () { + return bones; } public BoneData getTarget () { @@ -28,6 +28,7 @@ public class TransformConstraintData { } public void setTarget (BoneData target) { + if (target == null) throw new IllegalArgumentException("target cannot be null."); this.target = target; } 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 496452311..a667c9b3d 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 @@ -46,8 +46,7 @@ public class AtlasAttachmentLoader implements AttachmentLoader { public RegionAttachment newRegionAttachment (Skin skin, String name, String path) { AtlasRegion region = atlas.findRegion(path); - if (region == null) - throw new RuntimeException("Region not found in atlas: " + path + " (region attachment: " + name + ")"); + if (region == null) throw new RuntimeException("Region not found in atlas: " + path + " (region attachment: " + name + ")"); RegionAttachment attachment = new RegionAttachment(name); attachment.setRegion(region); return attachment; @@ -61,16 +60,11 @@ public class AtlasAttachmentLoader implements AttachmentLoader { return attachment; } - public WeightedMeshAttachment newWeightedMeshAttachment (Skin skin, String name, String path) { - AtlasRegion region = atlas.findRegion(path); - if (region == null) - throw new RuntimeException("Region not found in atlas: " + path + " (weighted mesh attachment: " + name + ")"); - WeightedMeshAttachment attachment = new WeightedMeshAttachment(name); - attachment.setRegion(region); - return attachment; - } - public BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name) { return new BoundingBoxAttachment(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 1b7c8f0b8..6081e7d37 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 @@ -41,8 +41,8 @@ public interface AttachmentLoader { public MeshAttachment newMeshAttachment (Skin skin, String name, String path); /** @return May be null to not load any attachment. */ - public WeightedMeshAttachment newWeightedMeshAttachment (Skin skin, String name, String path); + public BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name); /** @return May be null to not load any attachment. */ - public BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name); + public PathAttachment newPathAttachment (Skin skin, String name); } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentType.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentType.java index bf8f446e6..6e8e5f303 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentType.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentType.java @@ -32,7 +32,7 @@ package com.esotericsoftware.spine.attachments; public enum AttachmentType { - region, boundingbox, mesh, weightedmesh, linkedmesh, weightedlinkedmesh; + region, boundingbox, mesh, linkedmesh, path; static public AttachmentType[] values = values(); } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/BoundingBoxAttachment.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/BoundingBoxAttachment.java index 4c11f1069..867b2906d 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/BoundingBoxAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/BoundingBoxAttachment.java @@ -31,37 +31,22 @@ package com.esotericsoftware.spine.attachments; -import com.esotericsoftware.spine.Bone; -import com.esotericsoftware.spine.Skeleton; +import com.badlogic.gdx.graphics.Color; +import com.esotericsoftware.spine.Slot; -public class BoundingBoxAttachment extends Attachment { - private float[] vertices; +public class BoundingBoxAttachment extends VertexAttachment { + // Nonessential. + final Color color = new Color(0.38f, 0.94f, 0, 1); public BoundingBoxAttachment (String name) { super(name); } - public void computeWorldVertices (Bone bone, float[] worldVertices) { - Skeleton skeleton = bone.getSkeleton(); - float x = skeleton.getX() + bone.getWorldX(), y = skeleton.getY() + bone.getWorldY(); - float m00 = bone.getA(); - float m01 = bone.getB(); - float m10 = bone.getC(); - float m11 = bone.getD(); - float[] vertices = this.vertices; - for (int i = 0, n = vertices.length; i < n; i += 2) { - float px = vertices[i]; - float py = vertices[i + 1]; - worldVertices[i] = px * m00 + py * m01 + x; - worldVertices[i + 1] = px * m10 + py * m11 + y; - } + public void computeWorldVertices (Slot slot, float[] worldVertices) { + computeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0); } - public float[] getVertices () { - return vertices; - } - - public void setVertices (float[] vertices) { - this.vertices = vertices; + public Color getColor () { + return color; } } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/FfdAttachment.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/FfdAttachment.java deleted file mode 100644 index 88e045dbb..000000000 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/FfdAttachment.java +++ /dev/null @@ -1,6 +0,0 @@ - -package com.esotericsoftware.spine.attachments; - -public interface FfdAttachment { - public boolean applyFFD (Attachment sourceAttachment); -} diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/MeshAttachment.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/MeshAttachment.java index b145d407a..23c94ea17 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/MeshAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/MeshAttachment.java @@ -41,16 +41,15 @@ import com.esotericsoftware.spine.Skeleton; import com.esotericsoftware.spine.Slot; /** Attachment that displays a texture region. */ -public class MeshAttachment extends Attachment implements FfdAttachment { +public class MeshAttachment extends VertexAttachment { private TextureRegion region; private String path; - private float[] vertices, regionUVs; + private float[] regionUVs, worldVertices; private short[] triangles; - private float[] worldVertices; private final Color color = new Color(1, 1, 1, 1); private int hullLength; private MeshAttachment parentMesh; - private boolean inheritFFD; + private boolean inheritDeform; // Nonessential. private short[] edges; @@ -71,8 +70,9 @@ public class MeshAttachment extends Attachment implements FfdAttachment { } public void updateUVs () { - int verticesLength = vertices.length; - int worldVerticesLength = verticesLength / 2 * 5; + float[] regionUVs = this.regionUVs; + int verticesLength = regionUVs.length; + int worldVerticesLength = (verticesLength >> 1) * 5; if (worldVertices == null || worldVertices.length != worldVerticesLength) worldVertices = new float[worldVerticesLength]; float u, v, width, height; @@ -85,7 +85,6 @@ public class MeshAttachment extends Attachment implements FfdAttachment { width = region.getU2() - u; height = region.getV2() - v; } - float[] regionUVs = this.regionUVs; if (region instanceof AtlasRegion && ((AtlasRegion)region).rotate) { for (int i = 0, w = 3; i < verticesLength; i += 2, w += 5) { worldVertices[w] = u + regionUVs[i + 1] * width; @@ -102,54 +101,81 @@ public class MeshAttachment extends Attachment implements FfdAttachment { /** @return The updated world vertices. */ public float[] updateWorldVertices (Slot slot, boolean premultipliedAlpha) { Skeleton skeleton = slot.getSkeleton(); - Color skeletonColor = skeleton.getColor(); - Color slotColor = slot.getColor(); - Color meshColor = color; - float a = skeletonColor.a * slotColor.a * meshColor.a * 255; - float multiplier = premultipliedAlpha ? a : 255; + Color skeletonColor = skeleton.getColor(), slotColor = slot.getColor(), meshColor = color; + float alpha = skeletonColor.a * slotColor.a * meshColor.a * 255; + float multiplier = premultipliedAlpha ? alpha : 255; float color = NumberUtils.intToFloatColor( // - ((int)a << 24) // + ((int)alpha << 24) // | ((int)(skeletonColor.b * slotColor.b * meshColor.b * multiplier) << 16) // | ((int)(skeletonColor.g * slotColor.g * meshColor.g * multiplier) << 8) // | (int)(skeletonColor.r * slotColor.r * meshColor.r * multiplier)); - float[] worldVertices = this.worldVertices; - FloatArray slotVertices = slot.getAttachmentVertices(); - float[] vertices = this.vertices; - if (slotVertices.size == vertices.length) vertices = slotVertices.items; - Bone bone = slot.getBone(); - float x = skeleton.getX() + bone.getWorldX(), y = skeleton.getY() + bone.getWorldY(); - float m00 = bone.getA(), m01 = bone.getB(), m10 = bone.getC(), m11 = bone.getD(); - for (int v = 0, w = 0, n = worldVertices.length; w < n; v += 2, w += 5) { - float vx = vertices[v]; - float vy = vertices[v + 1]; - worldVertices[w] = vx * m00 + vy * m01 + x; - worldVertices[w + 1] = vx * m10 + vy * m11 + y; - worldVertices[w + 2] = color; + float x = skeleton.getX(), y = skeleton.getY(); + FloatArray deformArray = slot.getAttachmentVertices(); + float[] vertices = this.vertices, worldVertices = this.worldVertices; + int[] bones = this.bones; + if (bones == null) { + int verticesLength = vertices.length; + if (deformArray.size > 0) vertices = deformArray.items; + Bone bone = slot.getBone(); + x += bone.getWorldX(); + y += bone.getWorldY(); + float a = bone.getA(), b = bone.getB(), c = bone.getC(), d = bone.getD(); + for (int v = 0, w = 0; v < verticesLength; v += 2, w += 5) { + float vx = vertices[v], vy = vertices[v + 1]; + worldVertices[w] = vx * a + vy * b + x; + worldVertices[w + 1] = vx * c + vy * d + y; + worldVertices[w + 2] = color; + } + return worldVertices; + } + Object[] skeletonBones = skeleton.getBones().items; + if (deformArray.size == 0) { + for (int w = 0, v = 0, b = 0, n = bones.length; v < n; w += 5) { + float wx = x, wy = y; + int nn = bones[v++] + v; + for (; v < nn; v++, b += 3) { + Bone bone = (Bone)skeletonBones[bones[v]]; + float vx = vertices[b], vy = vertices[b + 1], weight = vertices[b + 2]; + wx += (vx * bone.getA() + vy * bone.getB() + bone.getWorldX()) * weight; + wy += (vx * bone.getC() + vy * bone.getD() + bone.getWorldY()) * weight; + } + worldVertices[w] = wx; + worldVertices[w + 1] = wy; + worldVertices[w + 2] = color; + } + } else { + float[] deform = deformArray.items; + for (int w = 0, v = 0, b = 0, f = 0, n = bones.length; v < n; w += 5) { + float wx = x, wy = y; + int nn = bones[v++] + v; + for (; v < nn; v++, b += 3, f += 2) { + Bone bone = (Bone)skeletonBones[bones[v]]; + float vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], weight = vertices[b + 2]; + wx += (vx * bone.getA() + vy * bone.getB() + bone.getWorldX()) * weight; + wy += (vx * bone.getC() + vy * bone.getD() + bone.getWorldY()) * weight; + } + worldVertices[w] = wx; + worldVertices[w + 1] = wy; + worldVertices[w + 2] = color; + } } return worldVertices; } - public boolean applyFFD (Attachment sourceAttachment) { - return this == sourceAttachment || (inheritFFD && parentMesh == sourceAttachment); + public boolean applyDeform (VertexAttachment sourceAttachment) { + return this == sourceAttachment || (inheritDeform && parentMesh == sourceAttachment); } public float[] getWorldVertices () { return worldVertices; } - public float[] getVertices () { - return vertices; - } - - public void setVertices (float[] vertices) { - this.vertices = vertices; - } - public short[] getTriangles () { return triangles; } + /** Vertex number triplets which describe the mesh's triangulation. */ public void setTriangles (short[] triangles) { this.triangles = triangles; } @@ -158,6 +184,7 @@ public class MeshAttachment extends Attachment implements FfdAttachment { return regionUVs; } + /** Sets the texture coordinates for the region. The values are u,v pairs for each vertex. */ public void setRegionUVs (float[] regionUVs) { this.regionUVs = regionUVs; } @@ -182,14 +209,14 @@ public class MeshAttachment extends Attachment implements FfdAttachment { this.hullLength = hullLength; } - public short[] getEdges () { - return edges; - } - public void setEdges (short[] edges) { this.edges = edges; } + public short[] getEdges () { + return edges; + } + public float getWidth () { return width; } @@ -215,6 +242,7 @@ public class MeshAttachment extends Attachment implements FfdAttachment { public void setParentMesh (MeshAttachment parentMesh) { this.parentMesh = parentMesh; if (parentMesh != null) { + bones = parentMesh.bones; vertices = parentMesh.vertices; regionUVs = parentMesh.regionUVs; triangles = parentMesh.triangles; @@ -225,11 +253,11 @@ public class MeshAttachment extends Attachment implements FfdAttachment { } } - public boolean getInheritFFD () { - return inheritFFD; + public boolean getInheritDeform () { + return inheritDeform; } - public void setInheritFFD (boolean inheritFFD) { - this.inheritFFD = inheritFFD; + public void setInheritDeform (boolean inheritDeform) { + this.inheritDeform = inheritDeform; } } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java new file mode 100644 index 000000000..229be6d47 --- /dev/null +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java @@ -0,0 +1,84 @@ +/****************************************************************************** + * Spine Runtimes Software License + * Version 2.3 + * + * Copyright (c) 2013-2015, 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 (the "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 otherwise create derivative works, improvements of the + * Software or develop new applications using the Software 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; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) 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; + +public class PathAttachment extends VertexAttachment { + float[] lengths; + boolean closed, constantSpeed; + + // Nonessential. + final Color color = new Color(1, 0.5f, 0, 1); + + public PathAttachment (String name) { + super(name); + } + + public void computeWorldVertices (Slot slot, float[] worldVertices) { + super.computeWorldVertices(slot, worldVertices); + } + + public void computeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset) { + super.computeWorldVertices(slot, start, count, worldVertices, offset); + } + + public boolean getClosed () { + return closed; + } + + public void setClosed (boolean closed) { + this.closed = closed; + } + + public boolean getConstantSpeed () { + return constantSpeed; + } + + public void setConstantSpeed (boolean constantSpeed) { + this.constantSpeed = constantSpeed; + } + + /** Returns the length in the setup pose from the start of the path to the end of each curve. */ + public float[] getLengths () { + return lengths; + } + + public void setLengths (float[] lengths) { + this.lengths = lengths; + } + + public Color getColor () { + return color; + } +} diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionAttachment.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionAttachment.java index c645f58ce..cc5a36242 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionAttachment.java @@ -152,10 +152,10 @@ public class RegionAttachment extends Attachment { Color skeletonColor = skeleton.getColor(); Color slotColor = slot.getColor(); Color regionColor = color; - float a = skeletonColor.a * slotColor.a * regionColor.a * 255; - float multiplier = premultipliedAlpha ? a : 255; + float alpha = skeletonColor.a * slotColor.a * regionColor.a * 255; + float multiplier = premultipliedAlpha ? alpha : 255; float color = NumberUtils.intToFloatColor( // - ((int)a << 24) // + ((int)alpha << 24) // | ((int)(skeletonColor.b * slotColor.b * regionColor.b * multiplier) << 16) // | ((int)(skeletonColor.g * slotColor.g * regionColor.g * multiplier) << 8) // | (int)(skeletonColor.r * slotColor.r * regionColor.r * multiplier)); @@ -164,31 +164,31 @@ public class RegionAttachment extends Attachment { float[] offset = this.offset; Bone bone = slot.getBone(); float x = skeleton.getX() + bone.getWorldX(), y = skeleton.getY() + bone.getWorldY(); - float m00 = bone.getA(), m01 = bone.getB(), m10 = bone.getC(), m11 = bone.getD(); + float a = bone.getA(), b = bone.getB(), c = bone.getC(), d = bone.getD(); float offsetX, offsetY; offsetX = offset[BRX]; offsetY = offset[BRY]; - vertices[X1] = offsetX * m00 + offsetY * m01 + x; // br - vertices[Y1] = offsetX * m10 + offsetY * m11 + y; + vertices[X1] = offsetX * a + offsetY * b + x; // br + vertices[Y1] = offsetX * c + offsetY * d + y; vertices[C1] = color; offsetX = offset[BLX]; offsetY = offset[BLY]; - vertices[X2] = offsetX * m00 + offsetY * m01 + x; // bl - vertices[Y2] = offsetX * m10 + offsetY * m11 + y; + vertices[X2] = offsetX * a + offsetY * b + x; // bl + vertices[Y2] = offsetX * c + offsetY * d + y; vertices[C2] = color; offsetX = offset[ULX]; offsetY = offset[ULY]; - vertices[X3] = offsetX * m00 + offsetY * m01 + x; // ul - vertices[Y3] = offsetX * m10 + offsetY * m11 + y; + vertices[X3] = offsetX * a + offsetY * b + x; // ul + vertices[Y3] = offsetX * c + offsetY * d + y; vertices[C3] = color; offsetX = offset[URX]; offsetY = offset[URY]; - vertices[X4] = offsetX * m00 + offsetY * m01 + x; // ur - vertices[Y4] = offsetX * m10 + offsetY * m11 + y; + vertices[X4] = offsetX * a + offsetY * b + x; // ur + vertices[Y4] = offsetX * c + offsetY * d + y; vertices[C4] = color; return vertices; } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionSequenceAttachment.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionSequenceAttachment.java index 2e70db9bc..53f0f0060 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionSequenceAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionSequenceAttachment.java @@ -58,7 +58,7 @@ public class RegionSequenceAttachment extends RegionAttachment { frameIndex = frameIndex % regions.length; break; case pingPong: - frameIndex = frameIndex % (regions.length * 2); + frameIndex = frameIndex % (regions.length << 1); if (frameIndex >= regions.length) frameIndex = regions.length - 1 - (frameIndex - regions.length); break; case random: diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/VertexAttachment.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/VertexAttachment.java new file mode 100644 index 000000000..2d2fc478f --- /dev/null +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/VertexAttachment.java @@ -0,0 +1,146 @@ +/****************************************************************************** + * Spine Runtimes Software License + * Version 2.3 + * + * Copyright (c) 2013-2015, 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 (the "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 otherwise create derivative works, improvements of the + * Software or develop new applications using the Software 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; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) 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.utils.FloatArray; +import com.esotericsoftware.spine.Bone; +import com.esotericsoftware.spine.Skeleton; +import com.esotericsoftware.spine.Slot; + +/** An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's vertices. */ +public class VertexAttachment extends Attachment { + int[] bones; + float[] vertices; + int worldVerticesLength; + + public VertexAttachment (String name) { + super(name); + } + + protected void computeWorldVertices (Slot slot, float[] worldVertices) { + computeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0); + } + + /** Transforms local vertices to world coordinates. + * @param start The index of the first local vertex value to transform. Each vertex has 2 values, x and y. + * @param count The number of world vertex values to output. Must be <= {@link #getWorldVerticesLength()} - start. + * @param worldVertices The output world vertices. Must have a length >= offset + count. + * @param offset The worldVertices index to begin writing values. */ + protected void computeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset) { + count += offset; + Skeleton skeleton = slot.getSkeleton(); + float x = skeleton.getX(), y = skeleton.getY(); + FloatArray deformArray = slot.getAttachmentVertices(); + float[] vertices = this.vertices; + int[] bones = this.bones; + if (bones == null) { + if (deformArray.size > 0) vertices = deformArray.items; + Bone bone = slot.getBone(); + x += bone.getWorldX(); + y += bone.getWorldY(); + float a = bone.getA(), b = bone.getB(), c = bone.getC(), d = bone.getD(); + for (int v = start, w = offset; w < count; v += 2, w += 2) { + float vx = vertices[v], vy = vertices[v + 1]; + worldVertices[w] = vx * a + vy * b + x; + worldVertices[w + 1] = vx * c + vy * d + y; + } + return; + } + int v = 0, skip = 0; + for (int i = 0; i < start; i += 2) { + int n = bones[v]; + v += n + 1; + skip += n; + } + Object[] skeletonBones = skeleton.getBones().items; + if (deformArray.size == 0) { + for (int w = offset, b = skip * 3; w < count; w += 2) { + float wx = x, wy = y; + for (int n = bones[v++] + v; v < n; v++, b += 3) { + Bone bone = (Bone)skeletonBones[bones[v]]; + float vx = vertices[b], vy = vertices[b + 1], weight = vertices[b + 2]; + wx += (vx * bone.getA() + vy * bone.getB() + bone.getWorldX()) * weight; + wy += (vx * bone.getC() + vy * bone.getD() + bone.getWorldY()) * weight; + } + worldVertices[w] = wx; + worldVertices[w + 1] = wy; + } + } else { + float[] deform = deformArray.items; + for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += 2) { + float wx = x, wy = y; + for (int n = bones[v++] + v; v < n; v++, b += 3, f += 2) { + Bone bone = (Bone)skeletonBones[bones[v]]; + float vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], weight = vertices[b + 2]; + wx += (vx * bone.getA() + vy * bone.getB() + bone.getWorldX()) * weight; + wy += (vx * bone.getC() + vy * bone.getD() + bone.getWorldY()) * weight; + } + worldVertices[w] = wx; + worldVertices[w + 1] = wy; + } + } + } + + /** Returns true if a deform originally applied to the specified attachment should be applied to this attachment. */ + public boolean applyDeform (VertexAttachment sourceAttachment) { + return this == sourceAttachment; + } + + /** @return May be null if this attachment has no weights. */ + public int[] getBones () { + return bones; + } + + /** For each vertex, the number of bones affecting the vertex followed by that many bone indices. Ie: count, boneIndex, ... + * @param bones May be null if this attachment has no weights. */ + public void setBones (int[] bones) { + this.bones = bones; + } + + public float[] getVertices () { + return vertices; + } + + /** Sets the vertex position in the bone's coordinate system. For a non-weighted attachment, the values are x,y entries for + * each vertex. For a weighted attachment, the values are x,y,weight entries for each bone affecting each vertex. */ + public void setVertices (float[] vertices) { + this.vertices = vertices; + } + + public int getWorldVerticesLength () { + return worldVerticesLength; + } + + public void setWorldVerticesLength (int worldVerticesLength) { + this.worldVerticesLength = worldVerticesLength; + } +} diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/WeightedMeshAttachment.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/WeightedMeshAttachment.java deleted file mode 100644 index 032f4789f..000000000 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/WeightedMeshAttachment.java +++ /dev/null @@ -1,274 +0,0 @@ -/****************************************************************************** - * Spine Runtimes Software License - * Version 2.3 - * - * Copyright (c) 2013-2015, 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 (the "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 otherwise create derivative works, improvements of the - * Software or develop new applications using the Software 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; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) 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.esotericsoftware.spine.Bone; -import com.esotericsoftware.spine.Skeleton; -import com.esotericsoftware.spine.Slot; - -import com.badlogic.gdx.graphics.Color; -import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion; -import com.badlogic.gdx.graphics.g2d.TextureRegion; -import com.badlogic.gdx.utils.FloatArray; -import com.badlogic.gdx.utils.NumberUtils; - -/** Attachment that displays a texture region. */ -public class WeightedMeshAttachment extends Attachment implements FfdAttachment { - private TextureRegion region; - private String path; - private int[] bones; - private float[] weights, regionUVs; - private short[] triangles; - private float[] worldVertices; - private final Color color = new Color(1, 1, 1, 1); - private int hullLength; - private WeightedMeshAttachment parentMesh; - private boolean inheritFFD; - - // Nonessential. - private short[] edges; - private float width, height; - - public WeightedMeshAttachment (String name) { - super(name); - } - - public void setRegion (TextureRegion region) { - if (region == null) throw new IllegalArgumentException("region cannot be null."); - this.region = region; - } - - public TextureRegion getRegion () { - if (region == null) throw new IllegalStateException("Region has not been set: " + this); - return region; - } - - public void updateUVs () { - float[] regionUVs = this.regionUVs; - int verticesLength = regionUVs.length; - int worldVerticesLength = verticesLength / 2 * 5; - if (worldVertices == null || worldVertices.length != worldVerticesLength) worldVertices = new float[worldVerticesLength]; - - float u, v, width, height; - if (region == null) { - u = v = 0; - width = height = 1; - } else { - u = region.getU(); - v = region.getV(); - width = region.getU2() - u; - height = region.getV2() - v; - } - if (region instanceof AtlasRegion && ((AtlasRegion)region).rotate) { - for (int i = 0, w = 3; i < verticesLength; i += 2, w += 5) { - worldVertices[w] = u + regionUVs[i + 1] * width; - worldVertices[w + 1] = v + height - regionUVs[i] * height; - } - } else { - for (int i = 0, w = 3; i < verticesLength; i += 2, w += 5) { - worldVertices[w] = u + regionUVs[i] * width; - worldVertices[w + 1] = v + regionUVs[i + 1] * height; - } - } - } - - /** @return The updated world vertices. */ - public float[] updateWorldVertices (Slot slot, boolean premultipliedAlpha) { - Skeleton skeleton = slot.getSkeleton(); - Color skeletonColor = skeleton.getColor(); - Color meshColor = slot.getColor(); - Color regionColor = color; - float a = skeletonColor.a * meshColor.a * regionColor.a * 255; - float multiplier = premultipliedAlpha ? a : 255; - float color = NumberUtils.intToFloatColor( // - ((int)a << 24) // - | ((int)(skeletonColor.b * meshColor.b * regionColor.b * multiplier) << 16) // - | ((int)(skeletonColor.g * meshColor.g * regionColor.g * multiplier) << 8) // - | (int)(skeletonColor.r * meshColor.r * regionColor.r * multiplier)); - - float[] worldVertices = this.worldVertices; - float x = skeleton.getX(), y = skeleton.getY(); - Object[] skeletonBones = skeleton.getBones().items; - float[] weights = this.weights; - int[] bones = this.bones; - - FloatArray ffdArray = slot.getAttachmentVertices(); - if (ffdArray.size == 0) { - for (int w = 0, v = 0, b = 0, n = bones.length; v < n; w += 5) { - float wx = 0, wy = 0; - int nn = bones[v++] + v; - for (; v < nn; v++, b += 3) { - Bone bone = (Bone)skeletonBones[bones[v]]; - float vx = weights[b], vy = weights[b + 1], weight = weights[b + 2]; - wx += (vx * bone.getA() + vy * bone.getB() + bone.getWorldX()) * weight; - wy += (vx * bone.getC() + vy * bone.getD() + bone.getWorldY()) * weight; - } - worldVertices[w] = wx + x; - worldVertices[w + 1] = wy + y; - worldVertices[w + 2] = color; - } - } else { - float[] ffd = ffdArray.items; - for (int w = 0, v = 0, b = 0, f = 0, n = bones.length; v < n; w += 5) { - float wx = 0, wy = 0; - int nn = bones[v++] + v; - for (; v < nn; v++, b += 3, f += 2) { - Bone bone = (Bone)skeletonBones[bones[v]]; - float vx = weights[b] + ffd[f], vy = weights[b + 1] + ffd[f + 1], weight = weights[b + 2]; - wx += (vx * bone.getA() + vy * bone.getB() + bone.getWorldX()) * weight; - wy += (vx * bone.getC() + vy * bone.getD() + bone.getWorldY()) * weight; - } - worldVertices[w] = wx + x; - worldVertices[w + 1] = wy + y; - worldVertices[w + 2] = color; - } - } - return worldVertices; - } - - public boolean applyFFD (Attachment sourceAttachment) { - return this == sourceAttachment || (inheritFFD && parentMesh == sourceAttachment); - } - - public float[] getWorldVertices () { - return worldVertices; - } - - public int[] getBones () { - return bones; - } - - /** For each vertex, the number of bones affecting the vertex followed by that many bone indices. Ie: count, boneIndex, ... */ - public void setBones (int[] bones) { - this.bones = bones; - } - - public float[] getWeights () { - return weights; - } - - /** For each bone affecting the vertex, the vertex position in the bone's coordinate system and the weight for the bone's - * influence. Ie: x, y, weight, ... */ - public void setWeights (float[] weights) { - this.weights = weights; - } - - public short[] getTriangles () { - return triangles; - } - - /** Vertex number triplets which describe the mesh's triangulation. */ - public void setTriangles (short[] triangles) { - this.triangles = triangles; - } - - public float[] getRegionUVs () { - return regionUVs; - } - - /** For each vertex, a texure coordinate pair. Ie: u, v, ... */ - public void setRegionUVs (float[] regionUVs) { - this.regionUVs = regionUVs; - } - - public Color getColor () { - return color; - } - - public String getPath () { - return path; - } - - public void setPath (String path) { - this.path = path; - } - - public int getHullLength () { - return hullLength; - } - - public void setHullLength (int hullLength) { - this.hullLength = hullLength; - } - - public void setEdges (short[] edges) { - this.edges = edges; - } - - public short[] getEdges () { - return edges; - } - - public float getWidth () { - return width; - } - - public void setWidth (float width) { - this.width = width; - } - - public float getHeight () { - return height; - } - - public void setHeight (float height) { - this.height = height; - } - - /** Returns the source mesh if this is a linked mesh, else returns null. */ - public WeightedMeshAttachment getParentMesh () { - return parentMesh; - } - - /** @param parentMesh May be null. */ - public void setParentMesh (WeightedMeshAttachment parentMesh) { - this.parentMesh = parentMesh; - if (parentMesh != null) { - bones = parentMesh.bones; - weights = parentMesh.weights; - regionUVs = parentMesh.regionUVs; - triangles = parentMesh.triangles; - hullLength = parentMesh.hullLength; - edges = parentMesh.edges; - width = parentMesh.width; - height = parentMesh.height; - } - } - - public boolean getInheritFFD () { - return inheritFFD; - } - - public void setInheritFFD (boolean inheritFFD) { - this.inheritFFD = inheritFFD; - } -} diff --git a/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java b/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java index 96f4e40a3..bb3c62432 100644 --- a/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java +++ b/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java @@ -246,6 +246,7 @@ public class SkeletonViewer extends ApplicationAdapter { debugRenderer.setBoundingBoxes(ui.debugBoundingBoxesCheckbox.isChecked()); debugRenderer.setMeshHull(ui.debugMeshHullCheckbox.isChecked()); debugRenderer.setMeshTriangles(ui.debugMeshTrianglesCheckbox.isChecked()); + debugRenderer.setPaths(ui.debugPathsCheckbox.isChecked()); debugRenderer.draw(skeleton); } @@ -285,19 +286,20 @@ public class SkeletonViewer extends ApplicationAdapter { TextButton openButton = new TextButton("Open", skin); List animationList = new List(skin); List skinList = new List(skin); - CheckBox loopCheckbox = new CheckBox(" Loop", skin); - CheckBox premultipliedCheckbox = new CheckBox(" Premultiplied", skin); + CheckBox loopCheckbox = new CheckBox("Loop", skin); + CheckBox premultipliedCheckbox = new CheckBox("Premultiplied", skin); Slider mixSlider = new Slider(0f, 2, 0.01f, false, skin); Label mixLabel = new Label("0.3", skin); Slider speedSlider = new Slider(0.1f, 3, 0.01f, false, skin); Label speedLabel = new Label("1.0", skin); - CheckBox flipXCheckbox = new CheckBox(" X", skin); - CheckBox flipYCheckbox = new CheckBox(" Y", skin); - CheckBox debugBonesCheckbox = new CheckBox(" Bones", skin); - CheckBox debugRegionsCheckbox = new CheckBox(" Regions", skin); - CheckBox debugBoundingBoxesCheckbox = new CheckBox(" Bounds", skin); - CheckBox debugMeshHullCheckbox = new CheckBox(" Mesh Hull", skin); - CheckBox debugMeshTrianglesCheckbox = new CheckBox(" Mesh Triangles", skin); + CheckBox flipXCheckbox = new CheckBox("X", skin); + CheckBox flipYCheckbox = new CheckBox("Y", skin); + CheckBox debugBonesCheckbox = new CheckBox("Bones", skin); + CheckBox debugRegionsCheckbox = new CheckBox("Regions", skin); + CheckBox debugBoundingBoxesCheckbox = new CheckBox("Bounds", skin); + CheckBox debugMeshHullCheckbox = new CheckBox("Mesh hull", skin); + CheckBox debugMeshTrianglesCheckbox = new CheckBox("Triangles", skin); + CheckBox debugPathsCheckbox = new CheckBox("Paths", skin); Slider scaleSlider = new Slider(0.1f, 3, 0.01f, false, skin); Label scaleLabel = new Label("1.0", skin); TextButton pauseButton = new TextButton("Pause", skin, "toggle"); @@ -330,6 +332,7 @@ public class SkeletonViewer extends ApplicationAdapter { window.setX(-3); window.setY(-2); + window.getTitleLabel().setColor(new Color(0.76f, 1, 1, 1)); window.getTitleTable().add(openButton).space(3); window.getTitleTable().add(minimizeButton).width(20); @@ -356,12 +359,12 @@ public class SkeletonViewer extends ApplicationAdapter { root.add("Debug:"); root.add(table(debugBonesCheckbox, debugRegionsCheckbox, debugBoundingBoxesCheckbox)).row(); root.add(); - root.add(table(debugMeshHullCheckbox, debugMeshTrianglesCheckbox)).row(); + root.add(table(debugMeshHullCheckbox, debugMeshTrianglesCheckbox, debugPathsCheckbox)).row(); root.add("Alpha:"); root.add(premultipliedCheckbox).row(); root.add("Skin:"); root.add(skinScroll).expand().fill().minHeight(75).row(); - root.add("Setup Pose:"); + root.add("Setup pose:"); root.add(table(bonesSetupPoseButton, slotsSetupPoseButton, setupPoseButton)).row(); root.add("Animation:"); root.add(animationScroll).expand().fill().minHeight(75).row();