From ee29123dfaea5e33aedc89b79429cf5062d9f82d Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Sat, 21 May 2016 00:40:07 +0200 Subject: [PATCH 01/45] Spine v3.3 WIP --- .../spine/AnimationStateTest.java | 10 +- .../spine/AttachmentTimelineTests.java | 4 +- .../esotericsoftware/spine/BonePlotting.java | 12 +- .../com/esotericsoftware/spine/Animation.java | 93 ++++- .../src/com/esotericsoftware/spine/Bone.java | 12 +- .../com/esotericsoftware/spine/BoneData.java | 19 +- .../esotericsoftware/spine/IkConstraint.java | 4 +- .../spine/PathConstraint.java | 97 +++++ .../spine/PathConstraintData.java | 70 ++++ .../com/esotericsoftware/spine/Skeleton.java | 79 +++- .../spine/SkeletonBinary.java | 323 ++++++++------- .../spine/SkeletonBounds.java | 6 +- .../esotericsoftware/spine/SkeletonData.java | 18 + .../esotericsoftware/spine/SkeletonJson.java | 367 +++++++++--------- .../spine/SkeletonMeshRenderer.java | 9 +- .../spine/SkeletonRenderer.java | 9 +- .../spine/SkeletonRendererDebug.java | 71 ++-- .../src/com/esotericsoftware/spine/Slot.java | 14 +- .../com/esotericsoftware/spine/SlotData.java | 9 +- .../spine/TransformConstraint.java | 77 +--- .../attachments/AtlasAttachmentLoader.java | 16 +- .../spine/attachments/AttachmentLoader.java | 4 +- .../spine/attachments/AttachmentType.java | 2 +- .../attachments/BoundingBoxAttachment.java | 33 +- .../spine/attachments/FfdAttachment.java | 6 - .../spine/attachments/MeshAttachment.java | 114 ++++-- .../spine/attachments/PathAttachment.java | 165 ++++++++ .../spine/attachments/RegionAttachment.java | 24 +- .../spine/attachments/VertexAttachment.java | 133 +++++++ .../attachments/WeightedMeshAttachment.java | 274 ------------- .../spine/SkeletonViewer.java | 25 +- 31 files changed, 1186 insertions(+), 913 deletions(-) create mode 100644 spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java create mode 100644 spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java delete mode 100644 spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/FfdAttachment.java create mode 100644 spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java create mode 100644 spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/VertexAttachment.java delete mode 100644 spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/WeightedMeshAttachment.java 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..9d56ae280 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; @@ -654,13 +654,13 @@ 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][]; @@ -674,7 +674,7 @@ public class Animation { return slotIndex; } - public void setAttachment (Attachment attachment) { + public void setAttachment (VertexAttachment attachment) { this.attachment = attachment; } @@ -699,7 +699,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 +709,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]; @@ -760,8 +757,8 @@ public class Animation { frames = new float[frameCount * 3]; } - public void setIkConstraintIndex (int ikConstraint) { - this.ikConstraintIndex = ikConstraint; + public void setIkConstraintIndex (int index) { + this.ikConstraintIndex = index; } public int getIkConstraintIndex () { @@ -823,8 +820,8 @@ public class Animation { frames = new float[frameCount * 5]; } - public void setTransformConstraintIndex (int ikConstraint) { - this.transformConstraintIndex = ikConstraint; + public void setTransformConstraintIndex (int index) { + this.transformConstraintIndex = index; } public int getTransformConstraintIndex () { @@ -877,4 +874,72 @@ public class Animation { constraint.shearMix += (shear + (frames[frame + SHEAR_MIX] - shear) * percent - constraint.shearMix) * alpha; } } + + static public class PathConstraintTimeline extends CurveTimeline { + static private final int PREV_TIME = -4; + static private final int PREV_POSITION = -3; + static private final int PREV_ROTATE_MIX = -2; + static private final int PREV_TRANSLATE_MIX = -1; + static private final int POSITION = 1; + static private final int ROTATE_MIX = 2; + static private final int TRANSLATE_MIX = 3; + + int pathConstraintIndex; + private final float[] frames; // time, rotate mix, translate mix, scale mix, shear mix, ... + + public PathConstraintTimeline (int frameCount) { + super(frameCount); + frames = new float[frameCount * 4]; + } + + public void setPathConstraintIndex (int index) { + this.pathConstraintIndex = index; + } + + public int getPathConstraintIndex () { + return pathConstraintIndex; + } + + public float[] getFrames () { + return frames; + } + + /** Sets the time, position, and mixes of the specified keyframe. */ + public void setFrame (int frameIndex, float time, float position, float rotateMix, float translateMix) { + frameIndex *= 4; + frames[frameIndex] = time; + frames[frameIndex + 1] = position; + frames[frameIndex + 2] = rotateMix; + frames[frameIndex + 3] = 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 - 4]) { // Time is after last frame. + int i = frames.length - 1; + constraint.position += (frames[i - 2] - constraint.position) * alpha; + constraint.rotateMix += (frames[i - 1] - constraint.rotateMix) * alpha; + constraint.translateMix += (frames[i] - constraint.translateMix) * alpha; + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = binarySearch(frames, time, 4); + float frameTime = frames[frame]; + float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime), 0, 1); + percent = getCurvePercent(frame / 4 - 1, percent); + + float position = frames[frame + PREV_POSITION]; + float rotate = frames[frame + PREV_ROTATE_MIX]; + float translate = frames[frame + PREV_TRANSLATE_MIX]; + constraint.position += (position + (frames[frame + POSITION] - position) * percent - constraint.position) * alpha; + constraint.rotateMix += (rotate + (frames[frame + ROTATE_MIX] - rotate) * percent - constraint.rotateMix) * alpha; + constraint.translateMix += (translate + (frames[frame + TRANSLATE_MIX] - translate) * percent - constraint.translateMix) + * alpha; + } + } } 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..2b53a500e 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java @@ -36,11 +36,13 @@ 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; @@ -48,12 +50,6 @@ public class Bone implements Updatable { float c, d, worldY; float worldSignX, worldSignY; - Bone (BoneData data) { - this.data = data; - parent = null; - skeleton = null; - } - /** @param parent May be null. */ public Bone (BoneData data, Skeleton skeleton, Bone parent) { if (data == null) throw new IllegalArgumentException("data cannot be null."); @@ -230,6 +226,10 @@ public class Bone implements Updatable { return parent; } + public Array getChildren () { + return children; + } + public float getX () { return x; } 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..b1b6026a1 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/BoneData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/BoneData.java @@ -34,8 +34,9 @@ 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; @@ -44,8 +45,9 @@ public class BoneData { 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 (name == null) throw new IllegalArgumentException("name cannot be null."); + this.index = index; this.name = name; this.parent = parent; } @@ -54,8 +56,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 +69,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; } 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..8ed69047e 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java @@ -60,8 +60,8 @@ public class IkConstraint implements Updatable { 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)); + bones.add(skeleton.bones.get(bone.data.index)); + target = skeleton.bones.get(ikConstraint.target.data.index); mix = ikConstraint.mix; bendDirection = ikConstraint.bendDirection; } 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..3501a8ce7 --- /dev/null +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java @@ -0,0 +1,97 @@ + +package com.esotericsoftware.spine; + +import com.badlogic.gdx.math.Vector2; +import com.esotericsoftware.spine.attachments.Attachment; +import com.esotericsoftware.spine.attachments.PathAttachment; + +public class PathConstraint implements Updatable { + final PathConstraintData data; + Bone bone; + Slot target; + float position, rotateMix, translateMix; + + public PathConstraint (PathConstraintData data, Skeleton skeleton) { + this.data = data; + position = data.position; + rotateMix = data.rotateMix; + translateMix = data.translateMix; + bone = skeleton.findBone(data.bone.name); + target = skeleton.findSlot(data.target.name); + } + + /** Copy constructor. */ + public PathConstraint (PathConstraint constraint, Skeleton skeleton) { + data = constraint.data; + bone = skeleton.bones.get(constraint.bone.data.index); + target = skeleton.slots.get(constraint.target.data.index); + position = constraint.position; + rotateMix = constraint.rotateMix; + translateMix = constraint.translateMix; + } + + public void apply () { + update(); + } + + public void update () { + Attachment attachment = target.getAttachment(); + if (!(attachment instanceof PathAttachment)) return; + PathAttachment path = (PathAttachment)attachment; + + float translateMix = this.translateMix; + if (translateMix > 0) { + Vector2 temp = path.computeWorldPosition(target, position); + bone.worldX += (temp.x - bone.worldX) * translateMix; + bone.worldY += (temp.y - bone.worldY) * translateMix; + } + } + + public float getPosition () { + return position; + } + + public void setPosition (float position) { + this.position = position; + } + + 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 Bone getBone () { + return bone; + } + + public void setBone (Bone bone) { + this.bone = bone; + } + + 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..f3a276896 --- /dev/null +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java @@ -0,0 +1,70 @@ + +package com.esotericsoftware.spine; + +public class PathConstraintData { + final String name; + BoneData bone; + SlotData target; + float position, rotateMix, translateMix; + float offsetRotation; + + public PathConstraintData (String name) { + this.name = name; + } + + public BoneData getBone () { + return bone; + } + + public void setBone (BoneData bone) { + this.bone = bone; + } + + public SlotData getTarget () { + return target; + } + + public void setTarget (SlotData target) { + this.target = target; + } + + public float getPosition () { + return position; + } + + public void setPosition (float position) { + this.position = position; + } + + 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 float getOffsetRotation () { + return offsetRotation; + } + + public void setOffsetRotation (float offsetRotation) { + this.offsetRotation = offsetRotation; + } + + public String getName () { + return name; + } + + public String toString () { + return name; + } +} 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..b22a47c2e 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java @@ -37,7 +37,6 @@ 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.WeightedMeshAttachment; public class Skeleton { final SkeletonData data; @@ -46,6 +45,7 @@ public class Skeleton { Array drawOrder; final Array ikConstraints; final Array transformConstraints; + final Array pathConstraints; private final Array updateCache = new Array(); Skin skin; final Color color; @@ -59,14 +59,21 @@ 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); @@ -80,6 +87,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,19 +103,19 @@ 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); for (IkConstraint ikConstraint : skeleton.ikConstraints) @@ -114,6 +125,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; @@ -129,8 +144,8 @@ public class Skeleton { Array updateCache = this.updateCache; Array ikConstraints = this.ikConstraints; Array transformConstraints = this.transformConstraints; + Array pathConstraints = this.pathConstraints; int ikConstraintsCount = ikConstraints.size; - int transformConstraintsCount = transformConstraints.size; updateCache.clear(); for (int i = 0, n = bones.size; i < n; i++) { @@ -145,10 +160,22 @@ public class Skeleton { } } - for (int i = 0; i < transformConstraintsCount; i++) { - TransformConstraint transformConstraint = transformConstraints.get(i); + for (int i = 0, n = pathConstraints.size; i < n; i++) { + PathConstraint pathConstraint = pathConstraints.get(i); + Bone bone = pathConstraint.bone; for (int ii = updateCache.size - 1; ii >= 0; ii--) { - if (updateCache.get(ii) == transformConstraint.bone) { + if (updateCache.get(ii) == bone) { + updateCache.insert(ii + 1, pathConstraint); + break; + } + } + } + + for (int i = 0, n = transformConstraints.size; i < n; i++) { + TransformConstraint transformConstraint = transformConstraints.get(i); + Bone bone = transformConstraint.bone; + for (int ii = updateCache.size - 1; ii >= 0; ii--) { + if (updateCache.get(ii) == bone) { updateCache.insert(ii + 1, transformConstraint); break; } @@ -191,13 +218,22 @@ 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.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 () { @@ -370,6 +406,21 @@ public class Skeleton { return null; } + 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, mesh, and skinned 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. */ @@ -382,12 +433,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) { 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..8c6b4c3d7 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java @@ -44,10 +44,11 @@ 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.PathConstraintTimeline; import com.esotericsoftware.spine.Animation.RotateTimeline; import com.esotericsoftware.spine.Animation.ScaleTimeline; import com.esotericsoftware.spine.Animation.ShearTimeline; @@ -61,8 +62,9 @@ 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; @@ -161,59 +163,71 @@ 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); + 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); } // IK constraints. for (int i = 0, n = input.readInt(true); i < n; i++) { - IkConstraintData ikConstraintData = new IkConstraintData(input.readString()); + IkConstraintData data = 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); + 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 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); + TransformConstraintData data = new TransformConstraintData(input.readString()); + data.bone = 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()); + data.bone = skeletonData.bones.get(input.readInt(true)); + data.target = skeletonData.slots.get(input.readInt(true)); + data.offsetRotation = input.readFloat(); + data.position = input.readFloat(); + data.rotateMix = input.readFloat(); + data.translateMix = input.readFloat(); + skeletonData.pathConstraints.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); } // Default skin. @@ -234,25 +248,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 +335,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 +368,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 +387,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 +399,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 +407,47 @@ 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: { 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); + 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.setWorldVerticesLength(vertexCount << 1); + path.setVertices(vertices.vertices); + path.setBones(vertices.bones); + 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) { @@ -572,10 +553,10 @@ 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); @@ -586,10 +567,10 @@ public class SkeletonBinary { // 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()); @@ -599,52 +580,60 @@ public class SkeletonBinary { duration = Math.max(duration, timeline.getFrames()[frameCount * 5 - 5]); } - // FFD timelines. + // Path constraint timelines. + for (int i = 0, n = input.readInt(true); i < n; i++) { + int index = input.readInt(true); + int frameCount = input.readInt(true); + PathConstraintTimeline timeline = new PathConstraintTimeline(frameCount); + timeline.pathConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { + timeline.setFrame(frameIndex, 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 * 4 - 4]); + } + + // 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); @@ -702,12 +691,17 @@ public class SkeletonBinary { timelines.add(timeline); duration = Math.max(duration, timeline.getFrames()[eventCount - 1]); } - } catch (IOException ex) { + } catch ( + + IOException ex) + + { throw new SerializationException("Error reading skeleton file.", ex); } timelines.shrink(); skeletonData.animations.add(new Animation(name, timelines, duration)); + } private void readCurve (DataInput input, int frameIndex, CurveTimeline timeline) throws IOException { @@ -724,4 +718,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..07831f211 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBounds.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBounds.java @@ -67,11 +67,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())); } } 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..c48c63b5c 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; @@ -188,6 +189,23 @@ 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 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..0a42150da 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -43,10 +43,11 @@ 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.PathConstraintTimeline; import com.esotericsoftware.spine.Animation.RotateTimeline; import com.esotericsoftware.spine.Animation.ScaleTimeline; import com.esotericsoftware.spine.Animation.ShearTimeline; @@ -59,8 +60,9 @@ 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; @@ -112,70 +114,90 @@ 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.inheritScale = boneMap.getBoolean("inheritScale", true); + data.inheritRotation = boneMap.getBoolean("inheritRotation", 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); + skeletonData.bones.add(data); } // IK constraints. - for (JsonValue ikMap = root.getChild("ik"); ikMap != null; ikMap = ikMap.next) { - IkConstraintData ikConstraintData = new IkConstraintData(ikMap.getString("name")); + for (JsonValue constraintMap = root.getChild("ik"); constraintMap != null; constraintMap = constraintMap.next) { + IkConstraintData data = new IkConstraintData(constraintMap.getString("name")); - for (JsonValue boneMap = ikMap.getChild("bones"); boneMap != null; boneMap = boneMap.next) { + 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); - ikConstraintData.bones.add(bone); + data.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); + String targetName = constraintMap.getString("target"); + data.target = skeletonData.findBone(targetName); + if (data.target == null) throw new SerializationException("Target bone not found: " + targetName); - ikConstraintData.bendDirection = ikMap.getBoolean("bendPositive", true) ? 1 : -1; - ikConstraintData.mix = ikMap.getFloat("mix", 1); + data.bendDirection = constraintMap.getBoolean("bendPositive", true) ? 1 : -1; + data.mix = constraintMap.getFloat("mix", 1); - skeletonData.ikConstraints.add(ikConstraintData); + skeletonData.ikConstraints.add(data); } // Transform constraints. - for (JsonValue transformMap = root.getChild("transform"); transformMap != null; transformMap = transformMap.next) { - TransformConstraintData transformConstraintData = new TransformConstraintData(transformMap.getString("name")); + for (JsonValue constraintMap = root.getChild("transform"); constraintMap != null; constraintMap = constraintMap.next) { + TransformConstraintData data = new TransformConstraintData(constraintMap.getString("name")); - String boneName = transformMap.getString("bone"); - transformConstraintData.bone = skeletonData.findBone(boneName); - if (transformConstraintData.bone == null) throw new SerializationException("Bone not found: " + boneName); + String boneName = constraintMap.getString("bone"); + data.bone = skeletonData.findBone(boneName); + if (data.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); + String targetName = constraintMap.getString("target"); + data.target = skeletonData.findBone(targetName); + if (data.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; + 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; - transformConstraintData.rotateMix = transformMap.getFloat("rotateMix", 1); - transformConstraintData.translateMix = transformMap.getFloat("translateMix", 1); - transformConstraintData.scaleMix = transformMap.getFloat("scaleMix", 1); - transformConstraintData.shearMix = transformMap.getFloat("shearMix", 1); + 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(transformConstraintData); + skeletonData.transformConstraints.add(data); + } + + // Path constraints. + for (JsonValue constraintMap = root.getChild("path"); constraintMap != null; constraintMap = constraintMap.next) { + PathConstraintData data = new PathConstraintData(constraintMap.getString("name")); + + String boneName = constraintMap.getString("bone"); + data.bone = skeletonData.findBone(boneName); + if (data.bone == null) throw new SerializationException("Bone not found: " + boneName); + + String targetName = constraintMap.getString("target"); + data.target = skeletonData.findSlot(targetName); + if (data.target == null) throw new SerializationException("Target slot not found: " + targetName); + + data.offsetRotation = constraintMap.getFloat("rotation", 0); + data.position = constraintMap.getFloat("position", 0); + data.rotateMix = constraintMap.getFloat("rotateMix", 1); + data.translateMix = constraintMap.getFloat("translateMix", 1); + + skeletonData.pathConstraints.add(data); } // Slots. @@ -184,14 +206,14 @@ 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); } // Skins. @@ -201,7 +223,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 +238,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 +265,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()); + + // 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 +299,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 +319,63 @@ 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; + readVertices(map, path, map.getInt("vertexCount") << 1); 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; @@ -394,7 +395,7 @@ 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); @@ -428,7 +429,7 @@ 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); @@ -451,7 +452,7 @@ 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); @@ -471,7 +472,7 @@ public class SkeletonJson { 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); + readCurve(valueMap, timeline, frameIndex); frameIndex++; } timelines.add(timeline); @@ -487,59 +488,69 @@ 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]); } - // 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("path"); constraintMap != null; constraintMap = constraintMap.next) { + PathConstraintData constraint = skeletonData.findPathConstraint(constraintMap.name); + PathConstraintTimeline timeline = new PathConstraintTimeline(constraintMap.size); + timeline.pathConstraintIndex = skeletonData.getPathConstraints().indexOf(constraint, true); + int frameIndex = 0; + for (JsonValue valueMap = constraintMap.child; valueMap != null; valueMap = valueMap.next) { + timeline.setFrame(frameIndex, valueMap.getFloat("time"), valueMap.getFloat("scaleMix", 1), + valueMap.getFloat("rotateMix", 1), valueMap.getFloat("translateMix", 1)); + readCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.add(timeline); + duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() * 4 - 4]); + } + + // 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 +619,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 +632,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..253ad77f2 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,7 +132,7 @@ public class SkeletonRendererDebug { shapes.triangle(vertices[v1], vertices[v1 + 1], // vertices[v2], vertices[v2 + 1], // vertices[v3], vertices[v3 + 1] // - ); + ); } } if (drawMeshHull && hullLength > 0) { @@ -165,14 +154,39 @@ 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[] worldVertices = temp.setSize(nn); + path.computeWorldVertices(slot, worldVertices); + shapes.setColor(path.getColor()); + float x1 = worldVertices[0], y1 = worldVertices[1]; + float cx1 = x1 + (x1 - worldVertices[2]), cy1 = y1 + (y1 - worldVertices[3]); + for (int ii = 4; ii < nn; ii += 4) { + float x2 = worldVertices[ii], y2 = worldVertices[ii + 1], cx2 = worldVertices[ii + 2], cy2 = worldVertices[ii + 3]; + shapes.curve(x1, y1, cx1, cy1, cx2, cy2, x2, y2, 32); + x1 = x2; + y1 = y2; + cx1 = x2 + (x2 - cx2); + cy1 = y2 + (y2 - cy2); + } + } + } + shapes.end(); shapes.begin(ShapeType.Filled); @@ -186,6 +200,7 @@ public class SkeletonRendererDebug { } shapes.end(); + } public ShapeRenderer getShapeRenderer () { @@ -216,6 +231,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..cac1e1644 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."); @@ -117,20 +111,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..1958251b3 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SlotData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SlotData.java @@ -34,20 +34,17 @@ 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 (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; } 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..171227ade 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraint.java @@ -9,20 +9,14 @@ public class TransformConstraint implements Updatable { final TransformConstraintData data; Bone 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) { 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); @@ -33,17 +27,12 @@ public class TransformConstraint implements Updatable { /** Copy constructor. */ public TransformConstraint (TransformConstraint constraint, Skeleton skeleton) { 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; + bone = skeleton.bones.get(constraint.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 () { @@ -56,7 +45,7 @@ public class TransformConstraint implements Updatable { 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; + float r = atan2(target.c, target.a) - atan2(c, a) + data.offsetRotation * degRad; if (r > PI) r -= PI2; else if (r < -PI) r += PI2; @@ -71,12 +60,12 @@ public class TransformConstraint implements Updatable { 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; + 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(target.b * target.b + target.d * target.d); - s = bs > 0.00001f ? (bs + (ts - bs + offsetScaleY) * scaleMix) / bs : 0; + s = bs > 0.00001f ? (bs + (ts - bs + data.offsetScaleY) * scaleMix) / bs : 0; bone.b *= s; bone.d *= s; } @@ -88,7 +77,7 @@ public class TransformConstraint implements Updatable { if (r > PI) r -= PI2; else if (r < -PI) r += PI2; - r = by + (r + offsetShearY * degRad) * shearMix; + 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; @@ -97,7 +86,7 @@ public class TransformConstraint implements Updatable { float translateMix = this.translateMix; if (translateMix > 0) { Vector2 temp = this.temp; - target.localToWorld(temp.set(offsetX, offsetY)); + target.localToWorld(temp.set(data.offsetX, data.offsetY)); bone.worldX += (temp.x - bone.worldX) * translateMix; bone.worldY += (temp.y - bone.worldY) * translateMix; } @@ -151,54 +140,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/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..eb7397a48 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) { + super.computeWorldVertices(slot, worldVertices); } - 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..b9fdefa6b 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,7 +70,8 @@ public class MeshAttachment extends Attachment implements FfdAttachment { } public void updateUVs () { - int verticesLength = vertices.length; + float[] regionUVs = this.regionUVs; + int verticesLength = regionUVs.length; int worldVerticesLength = verticesLength / 2 * 5; if (worldVertices == null || worldVertices.length != worldVerticesLength) worldVertices = new float[worldVerticesLength]; @@ -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..3b6dba744 --- /dev/null +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java @@ -0,0 +1,165 @@ +/****************************************************************************** + * 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.badlogic.gdx.math.Vector2; +import com.esotericsoftware.spine.Slot; + +public class PathAttachment extends VertexAttachment { + // Nonessential. + final Color color = new Color(1, 0.5f, 0, 1); + final Vector2 temp = new Vector2(); + float[] worldVertices, lengths; + int totalLength; + boolean closed; + + public PathAttachment (String name) { + super(name); + } + + public void computeWorldVertices (Slot slot, float[] worldVertices) { + super.computeWorldVertices(slot, worldVertices); + } + + public Vector2 computeWorldPosition (Slot slot, float position) { + float[] worldVertices = this.worldVertices; + super.computeWorldVertices(slot, worldVertices); + + int curve = 0; + float t = 0; + if (closed) { + // BOZO - closed boolean used to turn off fancy calculations for now. + int curves = (worldVerticesLength >> 2) - 1; + curve = position < 1 ? (int)(curves * position) : curves - 1; + t = (position - curve / (float)curves) * curves; + } else { + // Compute lengths of all curves. + totalLength = 0; + float[] lengths = this.lengths; + float x1 = worldVertices[0], y1 = worldVertices[1]; + float cx1 = x1 + (x1 - worldVertices[2]), cy1 = y1 + (y1 - worldVertices[3]); + for (int i = 0, w = 4, n = worldVerticesLength; w < n; i += 6, w += 4) { + float x2 = worldVertices[w], y2 = worldVertices[w + 1]; + float cx2 = worldVertices[w + 2], cy2 = worldVertices[w + 3]; + addLengths(i, x1, y1, cx1, cy1, cx2, cy2, x2, y2); + x1 = x2; + y1 = y2; + cx1 = x2 + (x2 - cx2); + cy1 = y2 + (y2 - cy2); + } + + // Determine curve containing position. + float target = totalLength * position, distance = 0; + for (int i = 5;; i += 6) { + float curveLength = lengths[i]; + if (distance + curveLength > target) { + curve = i / 6; + t = (target - distance) / curveLength; + break; + } + distance += curveLength; + } + + // Adjust t for constant speed using lengths of curves as weights. + for (int i = curve * 6, n = i + 5; i < n; i++) { + float bezierPercent = lengths[i]; + if (t > bezierPercent) { + float linearPercent = 0.75f - 0.25f * (i - curve * 6 - 1); + float bezierPercentNext = lengths[i - 1]; + t = linearPercent + 0.25f * ((t - bezierPercent) / (bezierPercentNext - bezierPercent)); + break; + } + } + } + + // Calculate bezier point. + int i = curve << 2; + float x1 = worldVertices[i], y1 = worldVertices[i + 1]; + float cx1 = x1 + (x1 - worldVertices[i + 2]), cy1 = y1 + (y1 - worldVertices[i + 3]); + float x2 = worldVertices[i + 4], y2 = worldVertices[i + 5]; + float cx2 = worldVertices[i + 6], cy2 = worldVertices[i + 7]; + float tt = t * t, ttt = tt * t, t3 = t * 3; + float x = (x1 + t * (-x1 * 3 + t * (3 * x1 - x1 * t))) + t * (3 * cx1 + t * (-6 * cx1 + cx1 * t3)) + + tt * (cx2 * 3 - cx2 * t3) + x2 * ttt; + float y = (y1 + t * (-y1 * 3 + t * (3 * y1 - y1 * t))) + t * (3 * cy1 + t * (-6 * cy1 + cy1 * t3)) + + tt * (cy2 * 3 - cy2 * t3) + y2 * ttt; + return temp.set(x, y); + } + + private void addLengths (int index, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2) { + float tmp1x = x1 - cx1 * 2 + cx2, tmp1y = y1 - cy1 * 2 + cy2; + float tmp2x = (cx1 - cx2) * 3 - x1 + x2, tmp2y = (cy1 - cy2) * 3 - y1 + y2; + float dfx = (cx1 - x1) * 0.75f + tmp1x * 0.1875f + tmp2x * 0.015625f; + float dfy = (cy1 - y1) * 0.75f + tmp1y * 0.1875f + tmp2y * 0.015625f; + float ddfx = tmp1x * 0.375f + tmp2x * 0.09375f, ddfy = tmp1y * 0.375f + tmp2y * 0.09375f; + float dddfx = tmp2x * 0.09375f, dddfy = tmp2y * 0.09375f; + float length0 = (float)Math.sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + float length1 = length0 + (float)Math.sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + float length2 = length1 + (float)Math.sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + float total = length2 + (float)Math.sqrt(dfx * dfx + dfy * dfy); + totalLength += total; + float[] lengths = this.lengths; + lengths[index] = 1; + lengths[index + 1] = length2 / total; + lengths[index + 2] = length1 / total; + lengths[index + 3] = length0 / total; + lengths[index + 4] = 0; + lengths[index + 5] = total; + } + + public Color getColor () { + return color; + } + + public boolean getClosed () { + return closed; + } + + public void setClosed (boolean closed) { + this.closed = closed; + } + + public void setWorldVerticesLength (int worldVerticesLength) { + super.setWorldVerticesLength(worldVerticesLength); + worldVertices = new float[worldVerticesLength]; + lengths = new float[(worldVerticesLength >> 2) * 6]; + } +} 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/VertexAttachment.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/VertexAttachment.java new file mode 100644 index 000000000..913d6faf6 --- /dev/null +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/VertexAttachment.java @@ -0,0 +1,133 @@ +/****************************************************************************** + * 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) { + 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) { + 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; v < verticesLength; v += 2) { + float vx = vertices[v], vy = vertices[v + 1]; + worldVertices[v] = vx * a + vy * b + x; + worldVertices[v + 1] = vx * c + vy * d + y; + } + return; + } + Object[] skeletonBones = skeleton.getBones().items; + if (deformArray.size == 0) { + for (int w = 0, v = 0, b = 0, n = bones.length; v < n; w += 2) { + 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; + } + } else { + float[] deform = deformArray.items; + for (int w = 0, v = 0, b = 0, f = 0, n = bones.length; v < n; w += 2) { + 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; + } + } + } + + /** Returns true if a deform originally applied to the specified attachment should be applied to this attachment. */ + public boolean applyDeform (VertexAttachment sourceAttachment) { + return true; + } + + /** @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(); From a84e3379d813ef4abbcf4b9092c75399abc58ee6 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Mon, 23 May 2016 04:07:33 +0200 Subject: [PATCH 02/45] Much better paths. --- .../com/esotericsoftware/spine/Animation.java | 12 +- .../spine/PathConstraint.java | 3 +- .../spine/attachments/PathAttachment.java | 169 ++++++++++-------- .../spine/attachments/VertexAttachment.java | 2 +- 4 files changed, 101 insertions(+), 85 deletions(-) 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 9d56ae280..d0475c0f8 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -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; diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java index 3501a8ce7..d5e3418d3 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java @@ -10,6 +10,7 @@ public class PathConstraint implements Updatable { Bone bone; Slot target; float position, rotateMix, translateMix; + final Vector2 temp = new Vector2(); public PathConstraint (PathConstraintData data, Skeleton skeleton) { this.data = data; @@ -41,7 +42,7 @@ public class PathConstraint implements Updatable { float translateMix = this.translateMix; if (translateMix > 0) { - Vector2 temp = path.computeWorldPosition(target, position); + path.computeWorldPosition(target, position, temp); bone.worldX += (temp.x - bone.worldX) * translateMix; bone.worldY += (temp.y - bone.worldY) * translateMix; } 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 index 3b6dba744..8754896a2 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java @@ -38,9 +38,8 @@ import com.esotericsoftware.spine.Slot; public class PathAttachment extends VertexAttachment { // Nonessential. final Color color = new Color(1, 0.5f, 0, 1); - final Vector2 temp = new Vector2(); + float[] worldVertices, lengths; - int totalLength; boolean closed; public PathAttachment (String name) { @@ -51,98 +50,115 @@ public class PathAttachment extends VertexAttachment { super.computeWorldVertices(slot, worldVertices); } - public Vector2 computeWorldPosition (Slot slot, float position) { + public void computeWorldPosition (Slot slot, float position, Vector2 out) { + // BOZO - Remove check? + if (worldVerticesLength < 12) return; + float[] worldVertices = this.worldVertices; super.computeWorldVertices(slot, worldVertices); + // Determine curve containing position. + float pathLength = pathLengths(worldVertices); + float[] lengths = this.lengths; + float target = pathLength * position, distance = 0, t = 0; int curve = 0; - float t = 0; - if (closed) { - // BOZO - closed boolean used to turn off fancy calculations for now. - int curves = (worldVerticesLength >> 2) - 1; - curve = position < 1 ? (int)(curves * position) : curves - 1; - t = (position - curve / (float)curves) * curves; - } else { - // Compute lengths of all curves. - totalLength = 0; - float[] lengths = this.lengths; - float x1 = worldVertices[0], y1 = worldVertices[1]; - float cx1 = x1 + (x1 - worldVertices[2]), cy1 = y1 + (y1 - worldVertices[3]); - for (int i = 0, w = 4, n = worldVerticesLength; w < n; i += 6, w += 4) { - float x2 = worldVertices[w], y2 = worldVertices[w + 1]; - float cx2 = worldVertices[w + 2], cy2 = worldVertices[w + 3]; - addLengths(i, x1, y1, cx1, cy1, cx2, cy2, x2, y2); - x1 = x2; - y1 = y2; - cx1 = x2 + (x2 - cx2); - cy1 = y2 + (y2 - cy2); + for (;; curve++) { + float length = lengths[curve]; + float nextDistance = distance + length; + if (nextDistance >= target) { + t = (target - distance) / length; + break; } + distance = nextDistance; + } + curve *= 6; - // Determine curve containing position. - float target = totalLength * position, distance = 0; - for (int i = 5;; i += 6) { - float curveLength = lengths[i]; - if (distance + curveLength > target) { - curve = i / 6; - t = (target - distance) / curveLength; - break; - } - distance += curveLength; - } - - // Adjust t for constant speed using lengths of curves as weights. - for (int i = curve * 6, n = i + 5; i < n; i++) { - float bezierPercent = lengths[i]; - if (t > bezierPercent) { - float linearPercent = 0.75f - 0.25f * (i - curve * 6 - 1); - float bezierPercentNext = lengths[i - 1]; - t = linearPercent + 0.25f * ((t - bezierPercent) / (bezierPercentNext - bezierPercent)); - break; - } + // Adjust t for constant speed using lengths of curves as weights. + t *= curveLengths(curve, worldVertices); + for (int i = 1;; i++) { + float length = lengths[i]; + if (t >= length) { + t = 1 - 0.1f * i + 0.1f * (t - length) / (lengths[i - 1] - length); + break; } } - // Calculate bezier point. - int i = curve << 2; - float x1 = worldVertices[i], y1 = worldVertices[i + 1]; - float cx1 = x1 + (x1 - worldVertices[i + 2]), cy1 = y1 + (y1 - worldVertices[i + 3]); - float x2 = worldVertices[i + 4], y2 = worldVertices[i + 5]; - float cx2 = worldVertices[i + 6], cy2 = worldVertices[i + 7]; + // Calculate point. + float x1 = worldVertices[curve], y1 = worldVertices[curve + 1]; + float cx1 = worldVertices[curve + 4], cy1 = worldVertices[curve + 5]; + float x2 = worldVertices[curve + 6], y2 = worldVertices[curve + 7]; + float cx2 = worldVertices[curve + 8], cy2 = worldVertices[curve + 9]; float tt = t * t, ttt = tt * t, t3 = t * 3; float x = (x1 + t * (-x1 * 3 + t * (3 * x1 - x1 * t))) + t * (3 * cx1 + t * (-6 * cx1 + cx1 * t3)) + tt * (cx2 * 3 - cx2 * t3) + x2 * ttt; float y = (y1 + t * (-y1 * 3 + t * (3 * y1 - y1 * t))) + t * (3 * cy1 + t * (-6 * cy1 + cy1 * t3)) + tt * (cy2 * 3 - cy2 * t3) + y2 * ttt; - return temp.set(x, y); + out.set(x, y); } - private void addLengths (int index, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2) { - float tmp1x = x1 - cx1 * 2 + cx2, tmp1y = y1 - cy1 * 2 + cy2; - float tmp2x = (cx1 - cx2) * 3 - x1 + x2, tmp2y = (cy1 - cy2) * 3 - y1 + y2; - float dfx = (cx1 - x1) * 0.75f + tmp1x * 0.1875f + tmp2x * 0.015625f; - float dfy = (cy1 - y1) * 0.75f + tmp1y * 0.1875f + tmp2y * 0.015625f; - float ddfx = tmp1x * 0.375f + tmp2x * 0.09375f, ddfy = tmp1y * 0.375f + tmp2y * 0.09375f; - float dddfx = tmp2x * 0.09375f, dddfy = tmp2y * 0.09375f; - float length0 = (float)Math.sqrt(dfx * dfx + dfy * dfy); + private float pathLengths (float[] worldVertices) { + float[] lengths = this.lengths; + float total = 0; + float x1 = worldVertices[0], y1 = worldVertices[1]; + for (int i = 0, w = 4, n = 4 + worldVerticesLength - 6; w < n; i++, w += 6) { + float cx1 = worldVertices[w], cy1 = worldVertices[w + 1]; + float x2 = worldVertices[w + 2], y2 = worldVertices[w + 3]; + float cx2 = worldVertices[w + 4], cy2 = worldVertices[w + 5]; + float tmpx = (x1 - cx1 * 2 + cx2) * 0.1875f, tmpy = (y1 - cy1 * 2 + cy2) * 0.1875f; + float dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375f, dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.09375f; + float ddfx = tmpx * 2 + dddfx, ddfy = tmpy * 2 + dddfy; + float dfx = (cx1 - x1) * 0.75f + tmpx + dddfx * 0.16666667f, dfy = (cy1 - y1) * 0.75f + tmpy + dddfy * 0.16666667f; + float length = (float)Math.sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + length += (float)Math.sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + length += (float)Math.sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + length += (float)Math.sqrt(dfx * dfx + dfy * dfy); + total += length; + lengths[i] = length; + x1 = x2; + y1 = y2; + } + return total; + } + + private float curveLengths (int curve, float[] worldVertices) { + float x1 = worldVertices[curve], y1 = worldVertices[curve + 1]; + float cx1 = worldVertices[curve + 4], cy1 = worldVertices[curve + 5]; + float x2 = worldVertices[curve + 6], y2 = worldVertices[curve + 7]; + float cx2 = worldVertices[curve + 8], cy2 = worldVertices[curve + 9]; + float tmpx = (x1 - cx1 * 2 + cx2) * 0.03f, tmpy = (y1 - cy1 * 2 + cy2) * 0.03f; + float dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.006f, dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.006f; + float ddfx = tmpx * 2 + dddfx, ddfy = tmpy * 2 + dddfy; + float dfx = (cx1 - x1) * 0.3f + tmpx + dddfx * 0.16666667f, dfy = (cy1 - y1) * 0.3f + tmpy + dddfy * 0.16666667f; + float[] lengths = this.lengths; + lengths[10] = 0; + float total = 0; + for (int i = 9; i > 2; i--) { + total += (float)Math.sqrt(dfx * dfx + dfy * dfy); + lengths[i] = total; + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + } + total += (float)Math.sqrt(dfx * dfx + dfy * dfy); + lengths[2] = total; dfx += ddfx; dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - float length1 = length0 + (float)Math.sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx; - dfy += ddfy; - float length2 = length1 + (float)Math.sqrt(dfx * dfx + dfy * dfy); + total += (float)Math.sqrt(dfx * dfx + dfy * dfy); + lengths[1] = total; dfx += ddfx + dddfx; dfy += ddfy + dddfy; - float total = length2 + (float)Math.sqrt(dfx * dfx + dfy * dfy); - totalLength += total; - float[] lengths = this.lengths; - lengths[index] = 1; - lengths[index + 1] = length2 / total; - lengths[index + 2] = length1 / total; - lengths[index + 3] = length0 / total; - lengths[index + 4] = 0; - lengths[index + 5] = total; + total += (float)Math.sqrt(dfx * dfx + dfy * dfy); + lengths[0] = total; + return total; } public Color getColor () { @@ -159,7 +175,8 @@ public class PathAttachment extends VertexAttachment { public void setWorldVerticesLength (int worldVerticesLength) { super.setWorldVerticesLength(worldVerticesLength); - worldVertices = new float[worldVerticesLength]; - lengths = new float[(worldVerticesLength >> 2) * 6]; + // BOZO! - Don't reallocate for editor. + worldVertices = new float[Math.max(2, worldVerticesLength)]; + lengths = new float[Math.max(11, worldVerticesLength >> 1)]; } } 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 index 913d6faf6..529d8fa84 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/VertexAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/VertexAttachment.java @@ -99,7 +99,7 @@ public class VertexAttachment extends Attachment { /** Returns true if a deform originally applied to the specified attachment should be applied to this attachment. */ public boolean applyDeform (VertexAttachment sourceAttachment) { - return true; + return this == sourceAttachment; } /** @return May be null if this attachment has no weights. */ From c976f8038be05f6d2a5414e341b2641fde0997c5 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Wed, 25 May 2016 01:16:11 +0200 Subject: [PATCH 03/45] Fixed IK mix rotating the wrong direction. --- .../com/esotericsoftware/spine/IkConstraint.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) 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 8ed69047e..51d9644d4 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java @@ -243,18 +243,18 @@ 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.appliedScaleX, parent.appliedScaleY, 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.appliedScaleX, child.appliedScaleY, child.shearX, + child.shearY); } } From c406e4478530741141014ad1c62f5e18c388b9f4 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Wed, 25 May 2016 01:16:37 +0200 Subject: [PATCH 04/45] Improved vertex attachment, paths. --- .../com/esotericsoftware/spine/Skeleton.java | 2 +- .../attachments/BoundingBoxAttachment.java | 2 +- .../spine/attachments/PathAttachment.java | 244 +++++++++++------- .../spine/attachments/VertexAttachment.java | 33 ++- 4 files changed, 169 insertions(+), 112 deletions(-) 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 b22a47c2e..2aac860a1 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java @@ -46,7 +46,7 @@ public class Skeleton { final Array ikConstraints; final Array transformConstraints; final Array pathConstraints; - private final Array updateCache = new Array(); + final Array updateCache = new Array(); Skin skin; final Color color; float time; 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 eb7397a48..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 @@ -43,7 +43,7 @@ public class BoundingBoxAttachment extends VertexAttachment { } public void computeWorldVertices (Slot slot, float[] worldVertices) { - super.computeWorldVertices(slot, worldVertices); + computeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0); } public Color getColor () { 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 index 8754896a2..787e85f75 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java @@ -40,7 +40,7 @@ public class PathAttachment extends VertexAttachment { final Color color = new Color(1, 0.5f, 0, 1); float[] worldVertices, lengths; - boolean closed; + boolean closed, constantSpeed; public PathAttachment (String name) { super(name); @@ -54,111 +54,147 @@ public class PathAttachment extends VertexAttachment { // BOZO - Remove check? if (worldVerticesLength < 12) return; - float[] worldVertices = this.worldVertices; - super.computeWorldVertices(slot, worldVertices); + // BOZO - Path constraint rotation. + // BOZO - Closed paths. + // BOZO - Before/after open paths. - // Determine curve containing position. - float pathLength = pathLengths(worldVertices); - float[] lengths = this.lengths; - float target = pathLength * position, distance = 0, t = 0; - int curve = 0; - for (;; curve++) { - float length = lengths[curve]; - float nextDistance = distance + length; - if (nextDistance >= target) { - t = (target - distance) / length; - break; + float x1, y1, cx1, cy1, cx2, cy2, x2, y2; + if (!constantSpeed) { + int curves = worldVerticesLength / 6 - 1; + int curve = position < 1 ? (int)(curves * position) : curves - 1; + position = (position - curve / (float)curves) * curves; + + float[] worldVertices = this.worldVertices; + super.computeWorldVertices(slot, curve * 6 + 2, 8, worldVertices, 0); + + x1 = worldVertices[0]; + y1 = worldVertices[1]; + cx1 = worldVertices[2]; + cy1 = worldVertices[3]; + cx2 = worldVertices[4]; + cy2 = worldVertices[5]; + x2 = worldVertices[6]; + y2 = worldVertices[7]; + } else { + // BOZO - Use offset and count for all attachment compute methods. + float[] worldVertices = this.worldVertices; + int verticesLength = worldVerticesLength - 4; + super.computeWorldVertices(slot, 2, verticesLength, worldVertices, 0); + + // Curve lengths. + float[] lengths = this.lengths; + float length = 0; + x1 = worldVertices[0]; + y1 = worldVertices[1]; + float tmpx, tmpy, dddfx, dddfy, ddfx, ddfy, dfx, dfy; + for (int i = 0, w = 2; w < verticesLength; i++, w += 6) { + cx1 = worldVertices[w]; + cy1 = worldVertices[w + 1]; + cx2 = worldVertices[w + 2]; + cy2 = worldVertices[w + 3]; + x2 = worldVertices[w + 4]; + y2 = worldVertices[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; + length += (float)Math.sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + length += (float)Math.sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + length += (float)Math.sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + length += (float)Math.sqrt(dfx * dfx + dfy * dfy); + lengths[i] = length; + x1 = x2; + y1 = y2; } - distance = nextDistance; - } - curve *= 6; - // Adjust t for constant speed using lengths of curves as weights. - t *= curveLengths(curve, worldVertices); - for (int i = 1;; i++) { - float length = lengths[i]; - if (t >= length) { - t = 1 - 0.1f * i + 0.1f * (t - length) / (lengths[i - 1] - length); - break; + // Determine curve containing position. + int curve; + position *= length; + length = lengths[0]; + if (position <= length) { + curve = 0; + position /= length; + } else { + for (curve = 1;; curve++) { + length = lengths[curve]; + if (position <= length) { + float prev = lengths[curve - 1]; + position = (position - prev) / (length - prev); + break; + } + } + curve *= 6; + } + + // Curve segment lengths. + x1 = worldVertices[curve]; + y1 = worldVertices[curve + 1]; + cx1 = worldVertices[curve + 2]; + cy1 = worldVertices[curve + 3]; + cx2 = worldVertices[curve + 4]; + cy2 = worldVertices[curve + 5]; + x2 = worldVertices[curve + 6]; + y2 = worldVertices[curve + 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; + length = (float)Math.sqrt(dfx * dfx + dfy * dfy); + lengths[0] = length; + for (int i = 1; i < 8; i++) { + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + length += (float)Math.sqrt(dfx * dfx + dfy * dfy); + lengths[i] = length; + } + dfx += ddfx; + dfy += ddfy; + length += (float)Math.sqrt(dfx * dfx + dfy * dfy); + lengths[8] = length; + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + length += (float)Math.sqrt(dfx * dfx + dfy * dfy); + lengths[9] = length; + + // Weight by segment length. + position *= length; + length = lengths[0]; + if (position <= length) + position = 0.1f * position / length; + else { + for (int i = 1;; i++) { + length = lengths[i]; + if (position <= length) { + float prev = lengths[i - 1]; + position = 0.1f * (i + (position - prev) / (length - prev)); + break; + } + } } } // Calculate point. - float x1 = worldVertices[curve], y1 = worldVertices[curve + 1]; - float cx1 = worldVertices[curve + 4], cy1 = worldVertices[curve + 5]; - float x2 = worldVertices[curve + 6], y2 = worldVertices[curve + 7]; - float cx2 = worldVertices[curve + 8], cy2 = worldVertices[curve + 9]; - float tt = t * t, ttt = tt * t, t3 = t * 3; - float x = (x1 + t * (-x1 * 3 + t * (3 * x1 - x1 * t))) + t * (3 * cx1 + t * (-6 * cx1 + cx1 * t3)) - + tt * (cx2 * 3 - cx2 * t3) + x2 * ttt; - float y = (y1 + t * (-y1 * 3 + t * (3 * y1 - y1 * t))) + t * (3 * cy1 + t * (-6 * cy1 + cy1 * t3)) - + tt * (cy2 * 3 - cy2 * t3) + y2 * ttt; - out.set(x, y); - } - - private float pathLengths (float[] worldVertices) { - float[] lengths = this.lengths; - float total = 0; - float x1 = worldVertices[0], y1 = worldVertices[1]; - for (int i = 0, w = 4, n = 4 + worldVerticesLength - 6; w < n; i++, w += 6) { - float cx1 = worldVertices[w], cy1 = worldVertices[w + 1]; - float x2 = worldVertices[w + 2], y2 = worldVertices[w + 3]; - float cx2 = worldVertices[w + 4], cy2 = worldVertices[w + 5]; - float tmpx = (x1 - cx1 * 2 + cx2) * 0.1875f, tmpy = (y1 - cy1 * 2 + cy2) * 0.1875f; - float dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375f, dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.09375f; - float ddfx = tmpx * 2 + dddfx, ddfy = tmpy * 2 + dddfy; - float dfx = (cx1 - x1) * 0.75f + tmpx + dddfx * 0.16666667f, dfy = (cy1 - y1) * 0.75f + tmpy + dddfy * 0.16666667f; - float length = (float)Math.sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - length += (float)Math.sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx; - dfy += ddfy; - length += (float)Math.sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx + dddfx; - dfy += ddfy + dddfy; - length += (float)Math.sqrt(dfx * dfx + dfy * dfy); - total += length; - lengths[i] = length; - x1 = x2; - y1 = y2; - } - return total; - } - - private float curveLengths (int curve, float[] worldVertices) { - float x1 = worldVertices[curve], y1 = worldVertices[curve + 1]; - float cx1 = worldVertices[curve + 4], cy1 = worldVertices[curve + 5]; - float x2 = worldVertices[curve + 6], y2 = worldVertices[curve + 7]; - float cx2 = worldVertices[curve + 8], cy2 = worldVertices[curve + 9]; - float tmpx = (x1 - cx1 * 2 + cx2) * 0.03f, tmpy = (y1 - cy1 * 2 + cy2) * 0.03f; - float dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.006f, dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.006f; - float ddfx = tmpx * 2 + dddfx, ddfy = tmpy * 2 + dddfy; - float dfx = (cx1 - x1) * 0.3f + tmpx + dddfx * 0.16666667f, dfy = (cy1 - y1) * 0.3f + tmpy + dddfy * 0.16666667f; - float[] lengths = this.lengths; - lengths[10] = 0; - float total = 0; - for (int i = 9; i > 2; i--) { - total += (float)Math.sqrt(dfx * dfx + dfy * dfy); - lengths[i] = total; - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - } - total += (float)Math.sqrt(dfx * dfx + dfy * dfy); - lengths[2] = total; - dfx += ddfx; - dfy += ddfy; - total += (float)Math.sqrt(dfx * dfx + dfy * dfy); - lengths[1] = total; - dfx += ddfx + dddfx; - dfy += ddfy + dddfy; - total += (float)Math.sqrt(dfx * dfx + dfy * dfy); - lengths[0] = total; - return total; + float ttt = position * position * position, u = 1 - position; + float uuu = u * u * u, ut3 = u * position * 3, uut3 = u * ut3, utt3 = position * ut3; + out.set(x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt); } public Color getColor () { @@ -173,10 +209,18 @@ public class PathAttachment extends VertexAttachment { this.closed = closed; } + public boolean getConstantSpeed () { + return constantSpeed; + } + + public void setConstantSpeed (boolean constantSpeed) { + this.constantSpeed = constantSpeed; + } + public void setWorldVerticesLength (int worldVerticesLength) { super.setWorldVerticesLength(worldVerticesLength); // BOZO! - Don't reallocate for editor. worldVertices = new float[Math.max(2, worldVerticesLength)]; - lengths = new float[Math.max(11, worldVerticesLength >> 1)]; + lengths = new float[Math.max(10, worldVerticesLength / 6 - 1)]; } } 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 index 529d8fa84..2d2fc478f 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/VertexAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/VertexAttachment.java @@ -47,31 +47,45 @@ public class VertexAttachment extends Attachment { } 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) { - 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; v < verticesLength; v += 2) { + for (int v = start, w = offset; w < count; v += 2, w += 2) { float vx = vertices[v], vy = vertices[v + 1]; - worldVertices[v] = vx * a + vy * b + x; - worldVertices[v + 1] = vx * c + vy * d + y; + 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 = 0, v = 0, b = 0, n = bones.length; v < n; w += 2) { + for (int w = offset, b = skip * 3; w < count; w += 2) { float wx = x, wy = y; - int nn = bones[v++] + v; - for (; v < nn; v++, b += 3) { + 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; @@ -82,10 +96,9 @@ public class VertexAttachment extends Attachment { } } else { float[] deform = deformArray.items; - for (int w = 0, v = 0, b = 0, f = 0, n = bones.length; v < n; w += 2) { + for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += 2) { float wx = x, wy = y; - int nn = bones[v++] + v; - for (; v < nn; v++, b += 3, f += 2) { + 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; From 00c5313bd177ee54f1481411e3e1b703cfdb4a99 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Wed, 25 May 2016 17:19:35 +0200 Subject: [PATCH 05/45] Support for path constraint rotation, closed paths, outside curve position. --- .../spine/PathConstraint.java | 22 +++++- .../spine/attachments/PathAttachment.java | 77 +++++++++++++++---- 2 files changed, 78 insertions(+), 21 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java index d5e3418d3..315e852f5 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java @@ -1,6 +1,8 @@ package com.esotericsoftware.spine; +import static com.badlogic.gdx.math.MathUtils.*; + import com.badlogic.gdx.math.Vector2; import com.esotericsoftware.spine.attachments.Attachment; import com.esotericsoftware.spine.attachments.PathAttachment; @@ -10,7 +12,7 @@ public class PathConstraint implements Updatable { Bone bone; Slot target; float position, rotateMix, translateMix; - final Vector2 temp = new Vector2(); + final Vector2 worldPosition = new Vector2(), tangent = new Vector2(); public PathConstraint (PathConstraintData data, Skeleton skeleton) { this.data = data; @@ -42,9 +44,21 @@ public class PathConstraint implements Updatable { float translateMix = this.translateMix; if (translateMix > 0) { - path.computeWorldPosition(target, position, temp); - bone.worldX += (temp.x - bone.worldX) * translateMix; - bone.worldY += (temp.y - bone.worldY) * translateMix; + path.computeWorldPosition(target, position, worldPosition, null); + bone.worldX += (worldPosition.x - bone.worldX) * translateMix; + bone.worldY += (worldPosition.y - bone.worldY) * translateMix; + } + + float rotateMix = this.rotateMix; + if (rotateMix > 0) { + path.computeWorldPosition(target, position, worldPosition, tangent); + float r = worldPosition.sub(tangent).angle() - bone.getWorldRotationX(); + float cos = cosDeg(r), sin = sinDeg(r); + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + bone.a = cos * a - sin * c; + bone.b = cos * b - sin * d; + bone.c = sin * a + cos * c; + bone.d = sin * b + cos * d; } } 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 index 787e85f75..0dd458fc4 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java @@ -32,6 +32,7 @@ package com.esotericsoftware.spine.attachments; import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Vector2; import com.esotericsoftware.spine.Slot; @@ -50,22 +51,24 @@ public class PathAttachment extends VertexAttachment { super.computeWorldVertices(slot, worldVertices); } - public void computeWorldPosition (Slot slot, float position, Vector2 out) { + public void computeWorldPosition (Slot slot, float position, Vector2 out, Vector2 tangent) { // BOZO - Remove check? if (worldVerticesLength < 12) return; - // BOZO - Path constraint rotation. - // BOZO - Closed paths. - // BOZO - Before/after open paths. - float x1, y1, cx1, cy1, cx2, cy2, x2, y2; if (!constantSpeed) { - int curves = worldVerticesLength / 6 - 1; + int curves = worldVerticesLength / 6; + if (!closed) curves--; + position = MathUtils.clamp(position, 0, 1); int curve = position < 1 ? (int)(curves * position) : curves - 1; position = (position - curve / (float)curves) * curves; float[] worldVertices = this.worldVertices; - super.computeWorldVertices(slot, curve * 6 + 2, 8, worldVertices, 0); + if (closed && curve == curves - 1) { + super.computeWorldVertices(slot, curves * 6 - 4, 4, worldVertices, 0); + super.computeWorldVertices(slot, 0, 4, worldVertices, 4); + } else + super.computeWorldVertices(slot, curve * 6 + 2, 8, worldVertices, 0); x1 = worldVertices[0]; y1 = worldVertices[1]; @@ -76,10 +79,19 @@ public class PathAttachment extends VertexAttachment { x2 = worldVertices[6]; y2 = worldVertices[7]; } else { - // BOZO - Use offset and count for all attachment compute methods. float[] worldVertices = this.worldVertices; - int verticesLength = worldVerticesLength - 4; - super.computeWorldVertices(slot, 2, verticesLength, worldVertices, 0); + int verticesLength; + if (closed) { + verticesLength = worldVerticesLength; + super.computeWorldVertices(slot, 2, verticesLength - 2, worldVertices, 0); + super.computeWorldVertices(slot, 0, 2, worldVertices, verticesLength - 2); + worldVertices[verticesLength] = worldVertices[0]; + worldVertices[verticesLength + 1] = worldVertices[1]; + verticesLength += 2; + } else { + verticesLength = worldVerticesLength - 4; + super.computeWorldVertices(slot, 2, verticesLength, worldVertices, 0); + } // Curve lengths. float[] lengths = this.lengths; @@ -118,10 +130,35 @@ public class PathAttachment extends VertexAttachment { x1 = x2; y1 = y2; } + position *= length; + + // Outside curve. + if (!closed && (position < 0 || position > length)) { + if (position < 0) { + x1 = worldVertices[0]; + y1 = worldVertices[1]; + cx1 = worldVertices[2] - x1; + cy1 = worldVertices[3] - y1; + } else { + x1 = worldVertices[verticesLength - 2]; + y1 = worldVertices[verticesLength - 1]; + cx1 = x1 - worldVertices[verticesLength - 4]; + cy1 = y1 - worldVertices[verticesLength - 3]; + position -= length; + } + float r = MathUtils.atan2(cy1, cx1); + float cos = MathUtils.cos(r), sin = MathUtils.sin(r); + out.x = x1 + cos * position; + out.y = y1 + sin * position; + if (tangent != null) { + tangent.x = out.x - cos; + tangent.y = out.y - sin; + } + return; + } // Determine curve containing position. int curve; - position *= length; length = lengths[0]; if (position <= length) { curve = 0; @@ -191,10 +228,16 @@ public class PathAttachment extends VertexAttachment { } } - // Calculate point. - float ttt = position * position * position, u = 1 - position; - float uuu = u * u * u, ut3 = u * position * 3, uut3 = u * ut3, utt3 = position * ut3; - out.set(x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt); + // Calculate point and tangent. + position += 0.0001f; + float tt = position * position, ttt = tt * position, u = 1 - position, uu = u * u, uuu = uu * u; + float ut = u * position, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * position; + out.x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt; + out.y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt; + if (tangent != null) { + tangent.x = x1 * uu + cx1 * ut * 2 + cx2 * tt; + tangent.y = y1 * uu + cy1 * ut * 2 + cy2 * tt; + } } public Color getColor () { @@ -220,7 +263,7 @@ public class PathAttachment extends VertexAttachment { public void setWorldVerticesLength (int worldVerticesLength) { super.setWorldVerticesLength(worldVerticesLength); // BOZO! - Don't reallocate for editor. - worldVertices = new float[Math.max(2, worldVerticesLength)]; - lengths = new float[Math.max(10, worldVerticesLength / 6 - 1)]; + worldVertices = new float[Math.max(2, worldVerticesLength + 4)]; + lengths = new float[Math.max(10, worldVerticesLength / 6)]; } } From 903fbf1340689b3a3068058cd67250aa1a09876d Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Thu, 26 May 2016 02:18:53 +0200 Subject: [PATCH 06/45] Clean up. --- .../spine/PathConstraint.java | 18 ++++++---- .../spine/attachments/PathAttachment.java | 33 ++++++++++--------- 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java index 315e852f5..da60beb3a 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java @@ -42,19 +42,25 @@ public class PathConstraint implements Updatable { if (!(attachment instanceof PathAttachment)) return; PathAttachment path = (PathAttachment)attachment; - float translateMix = this.translateMix; + Vector2 worldPosition = this.worldPosition; + Bone bone = this.bone; + + float translateMix = this.translateMix, rotateMix = this.rotateMix; if (translateMix > 0) { - path.computeWorldPosition(target, position, worldPosition, null); + path.computeWorldPosition(target, position, worldPosition, rotateMix > 0 ? tangent : null); bone.worldX += (worldPosition.x - bone.worldX) * translateMix; bone.worldY += (worldPosition.y - bone.worldY) * translateMix; } - float rotateMix = this.rotateMix; if (rotateMix > 0) { - path.computeWorldPosition(target, position, worldPosition, tangent); - float r = worldPosition.sub(tangent).angle() - bone.getWorldRotationX(); - float cos = cosDeg(r), sin = sinDeg(r); + if (translateMix == 0) path.computeWorldPosition(target, position, worldPosition, tangent); float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float r = atan2(worldPosition.y - tangent.y, worldPosition.x - tangent.x) - 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; 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 index 0dd458fc4..abc3c6741 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java @@ -51,15 +51,17 @@ public class PathAttachment extends VertexAttachment { super.computeWorldVertices(slot, worldVertices); } - public void computeWorldPosition (Slot slot, float position, Vector2 out, Vector2 tangent) { - // BOZO - Remove check? - if (worldVerticesLength < 12) return; - + public void computeWorldPosition (Slot slot, float position, Vector2 worldPosition, Vector2 tangent) { float x1, y1, cx1, cy1, cx2, cy2, x2, y2; if (!constantSpeed) { int curves = worldVerticesLength / 6; - if (!closed) curves--; - position = MathUtils.clamp(position, 0, 1); + if (closed) { + position = position % 1; + if (position < 0) position += 1; + } else { + position = MathUtils.clamp(position, 0, 1); + curves--; + } int curve = position < 1 ? (int)(curves * position) : curves - 1; position = (position - curve / (float)curves) * curves; @@ -132,8 +134,10 @@ public class PathAttachment extends VertexAttachment { } position *= length; - // Outside curve. - if (!closed && (position < 0 || position > length)) { + if (closed) + position = position % length; + else if (position < 0 || position > length) { + // Outside curve. if (position < 0) { x1 = worldVertices[0]; y1 = worldVertices[1]; @@ -148,11 +152,11 @@ public class PathAttachment extends VertexAttachment { } float r = MathUtils.atan2(cy1, cx1); float cos = MathUtils.cos(r), sin = MathUtils.sin(r); - out.x = x1 + cos * position; - out.y = y1 + sin * position; + worldPosition.x = x1 + position * cos; + worldPosition.y = y1 + position * sin; if (tangent != null) { - tangent.x = out.x - cos; - tangent.y = out.y - sin; + tangent.x = worldPosition.x - cos; + tangent.y = worldPosition.y - sin; } return; } @@ -232,8 +236,8 @@ public class PathAttachment extends VertexAttachment { position += 0.0001f; float tt = position * position, ttt = tt * position, u = 1 - position, uu = u * u, uuu = uu * u; float ut = u * position, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * position; - out.x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt; - out.y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt; + worldPosition.x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt; + worldPosition.y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt; if (tangent != null) { tangent.x = x1 * uu + cx1 * ut * 2 + cx2 * tt; tangent.y = y1 * uu + cy1 * ut * 2 + cy2 * tt; @@ -262,7 +266,6 @@ public class PathAttachment extends VertexAttachment { public void setWorldVerticesLength (int worldVerticesLength) { super.setWorldVerticesLength(worldVerticesLength); - // BOZO! - Don't reallocate for editor. worldVertices = new float[Math.max(2, worldVerticesLength + 4)]; lengths = new float[Math.max(10, worldVerticesLength / 6)]; } From 4748b5bbea22eb1c50a42af91602e0f40c0d5419 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Thu, 26 May 2016 13:36:50 +0200 Subject: [PATCH 07/45] Fixed close path looping. --- .../esotericsoftware/spine/attachments/PathAttachment.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 index abc3c6741..c50f5f7d3 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java @@ -134,9 +134,10 @@ public class PathAttachment extends VertexAttachment { } position *= length; - if (closed) + if (closed) { position = position % length; - else if (position < 0 || position > length) { + if (position < 0) position += length; + } else if (position < 0 || position > length) { // Outside curve. if (position < 0) { x1 = worldVertices[0]; From ceb86b597b32fe09251cb4505476e9f02fba05ac Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Fri, 27 May 2016 17:57:29 +0200 Subject: [PATCH 08/45] Fixed bendPositive in IK timelines. Should be optional, false if omitted. --- .../src/com/esotericsoftware/spine/SkeletonJson.java | 1 + 1 file changed, 1 insertion(+) 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 0a42150da..e130d3f87 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -123,6 +123,7 @@ public class SkeletonJson { 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); data.inheritRotation = boneMap.getBoolean("inheritRotation", true); From 463020e254c30615b31ec16dc31e23765b4622ae Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Fri, 27 May 2016 17:58:31 +0200 Subject: [PATCH 09/45] Fixed bendPositive in IK timelines. Should be optional, false if omitted. --- .../src/com/esotericsoftware/spine/SkeletonJson.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 e130d3f87..e018b10d2 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -123,7 +123,6 @@ public class SkeletonJson { 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); data.inheritRotation = boneMap.getBoolean("inheritRotation", true); @@ -472,7 +471,7 @@ 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); + valueMap.getBoolean("bendPositive", false) ? 1 : -1); readCurve(valueMap, timeline, frameIndex); frameIndex++; } From 1fa2b35e096c40809de8bf16193e3a0f8c013806 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Sat, 28 May 2016 01:33:45 +0200 Subject: [PATCH 10/45] Clean up. --- .../com/esotericsoftware/spine/BoneData.java | 18 +++++++++--------- .../esotericsoftware/spine/SkeletonJson.java | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) 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 b1b6026a1..ac8151445 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/BoneData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/BoneData.java @@ -39,7 +39,7 @@ public class BoneData { 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); @@ -156,14 +156,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; } @@ -172,6 +164,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/SkeletonJson.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java index e018b10d2..72afbc720 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -123,8 +123,8 @@ public class SkeletonJson { data.scaleY = boneMap.getFloat("scaleY", 1); data.shearX = boneMap.getFloat("shearX", 0); data.shearY = boneMap.getFloat("shearY", 0); - data.inheritScale = boneMap.getBoolean("inheritScale", true); data.inheritRotation = boneMap.getBoolean("inheritRotation", true); + data.inheritScale = boneMap.getBoolean("inheritScale", true); String color = boneMap.getString("color", null); if (color != null) data.getColor().set(Color.valueOf(color)); From e50b59155864e33957aa2b819e1a38f8d6305a9a Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Sat, 28 May 2016 01:34:02 +0200 Subject: [PATCH 11/45] Added updateLocalTransform. --- .../src/com/esotericsoftware/spine/Bone.java | 42 ++++++++++++++++--- 1 file changed, 36 insertions(+), 6 deletions(-) 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 2b53a500e..67e505a4f 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java @@ -81,12 +81,12 @@ 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; @@ -169,9 +169,7 @@ public class Bone implements Updatable { pd = pc * zb + pd * zd; 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; pa = temp; @@ -203,6 +201,38 @@ public class Bone implements Updatable { } } + /** 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) { + float a = parent.a, b = parent.b, c = parent.c, d = parent.d; + float invDet = 1 / (a * d - b * c); + float x = worldX - parent.worldX, y = worldY - parent.worldY; + x = (x * d * invDet - y * b * invDet); + y = (y * a * invDet - x * c * invDet); + } + shearX = 0; + scaleX = (float)Math.sqrt(a * a + c * c); + if (scaleX > 0.00001f) { + float det = a * d - b * c; + shearY = atan2(a * b + c * d, det) * radDeg; + scaleY = det / scaleX; + rotation = atan2(c, a) * radDeg; + } else { + scaleX = 0; + shearY = 0; + scaleY = (float)Math.sqrt(b * b + d * d); + rotation = 90 - atan2(d, b) * radDeg; + } + appliedRotation = rotation; + appliedScaleX = scaleX; + appliedScaleY = scaleY; + } + public void setToSetupPose () { BoneData data = this.data; x = data.x; @@ -365,9 +395,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; From 05b3e66dc03452c2d04518f8ffcf0ba318b36393 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Sat, 28 May 2016 20:30:12 +0200 Subject: [PATCH 12/45] Start of multi bone path constraints. --- .../src/com/esotericsoftware/spine/Bone.java | 9 + .../esotericsoftware/spine/IkConstraint.java | 14 +- .../spine/PathConstraint.java | 118 +++++-- .../spine/PathConstraintData.java | 12 +- .../com/esotericsoftware/spine/Skeleton.java | 3 +- .../spine/SkeletonBinary.java | 3 +- .../esotericsoftware/spine/SkeletonJson.java | 9 +- .../spine/attachments/PathAttachment.java | 299 +++++++++--------- 8 files changed, 268 insertions(+), 199 deletions(-) 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 67e505a4f..c454e1177 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java @@ -233,6 +233,15 @@ public class Bone implements Updatable { appliedScaleY = scaleY; } + 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; + } + public void setToSetupPose () { BoneData data = this.data; x = data.x; 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 51d9644d4..d3710d08f 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java @@ -56,14 +56,14 @@ public class IkConstraint implements Updatable { } /** Copy constructor. */ - public IkConstraint (IkConstraint ikConstraint, Skeleton skeleton) { - data = ikConstraint.data; - bones = new Array(ikConstraint.bones.size); - for (Bone bone : ikConstraint.bones) + public IkConstraint (IkConstraint constraint, Skeleton skeleton) { + 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(ikConstraint.target.data.index); - mix = ikConstraint.mix; - bendDirection = ikConstraint.bendDirection; + target = skeleton.bones.get(constraint.target.data.index); + mix = constraint.mix; + bendDirection = constraint.bendDirection; } public void apply () { diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java index da60beb3a..6eecc75f0 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java @@ -4,29 +4,38 @@ package com.esotericsoftware.spine; import static com.badlogic.gdx.math.MathUtils.*; import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.FloatArray; import com.esotericsoftware.spine.attachments.Attachment; import com.esotericsoftware.spine.attachments.PathAttachment; public class PathConstraint implements Updatable { final PathConstraintData data; - Bone bone; + final Array bones; Slot target; float position, rotateMix, translateMix; - final Vector2 worldPosition = new Vector2(), tangent = new Vector2(); + final FloatArray lengths = new FloatArray(), positions = new FloatArray(); + final Vector2 temp = new Vector2(); public PathConstraint (PathConstraintData data, Skeleton skeleton) { this.data = data; position = data.position; rotateMix = data.rotateMix; translateMix = data.translateMix; - bone = skeleton.findBone(data.bone.name); + + bones = new Array(data.bones.size); + for (BoneData boneData : data.bones) + bones.add(skeleton.findBone(boneData.name)); + target = skeleton.findSlot(data.target.name); } /** Copy constructor. */ public PathConstraint (PathConstraint constraint, Skeleton skeleton) { data = constraint.data; - bone = skeleton.bones.get(constraint.bone.data.index); + 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; rotateMix = constraint.rotateMix; @@ -40,31 +49,82 @@ public class PathConstraint implements Updatable { public void update () { Attachment attachment = target.getAttachment(); if (!(attachment instanceof PathAttachment)) return; - PathAttachment path = (PathAttachment)attachment; - - Vector2 worldPosition = this.worldPosition; - Bone bone = this.bone; float translateMix = this.translateMix, rotateMix = this.rotateMix; - if (translateMix > 0) { - path.computeWorldPosition(target, position, worldPosition, rotateMix > 0 ? tangent : null); - bone.worldX += (worldPosition.x - bone.worldX) * translateMix; - bone.worldY += (worldPosition.y - bone.worldY) * translateMix; + boolean translate = translateMix > 0, rotate = rotateMix > 0; + if (!translate && !rotate) return; + + PathAttachment path = (PathAttachment)attachment; + FloatArray lengths = this.lengths, positions = this.positions; + lengths.clear(); + lengths.add(0); + positions.clear(); + + Array bones = this.bones; + int boneCount = bones.size; + if (boneCount == 1) { + path.computeWorldPositions(target, position, lengths, positions, rotate); + Bone bone = bones.first(); + bone.worldX += (positions.first() - bone.worldX) * translateMix; + bone.worldY += (positions.get(1) - bone.worldY) * translateMix; + if (rotate) { + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float r = positions.get(2) - 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; + } + return; } - if (rotateMix > 0) { - if (translateMix == 0) path.computeWorldPosition(target, position, worldPosition, tangent); - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float r = atan2(worldPosition.y - tangent.y, worldPosition.x - tangent.x) - 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; + for (int i = 0; i < boneCount; i++) + lengths.add(bones.get(i).data.length); + path.computeWorldPositions(target, position, lengths, positions, false); + + Vector2 temp = this.temp; + float boneX = positions.first(), boneY = positions.get(1); + for (int i = 0, p = 2; i < boneCount; i++, p += 2) { + Bone bone = bones.get(i); + bone.worldX += (boneX - bone.worldX) * translateMix; + bone.worldY += (boneY - bone.worldY) * translateMix; + + float x = positions.get(p), y = positions.get(p + 1); + if (rotate) { + // Scale. + // float dist = (float)Math.sqrt((boneX - x) * (boneX - x) + (boneY - y) * (boneY - y)); + // bone.scaleX = dist / bone.data.length; + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float r = atan2(y - boneY, x - boneX) - 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 (data.offsetRotation == 0 && rotateMix == 1) { + // Place at tip. + bone.localToWorld(temp.set(bone.data.length, 0)); + boneX = temp.x; + boneY = temp.y; + } else { + // Place on path. + boneX = x; + boneY = y; + } + } else { + // Place on path. + boneX = x; + boneY = y; + } } } @@ -92,12 +152,8 @@ public class PathConstraint implements Updatable { this.translateMix = translateMix; } - public Bone getBone () { - return bone; - } - - public void setBone (Bone bone) { - this.bone = bone; + public Array getBones () { + return bones; } public Slot getTarget () { diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java index f3a276896..4df2952ea 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java @@ -1,9 +1,11 @@ package com.esotericsoftware.spine; +import com.badlogic.gdx.utils.Array; + public class PathConstraintData { final String name; - BoneData bone; + final Array bones = new Array(); SlotData target; float position, rotateMix, translateMix; float offsetRotation; @@ -12,12 +14,8 @@ public class PathConstraintData { this.name = name; } - public BoneData getBone () { - return bone; - } - - public void setBone (BoneData bone) { - this.bone = bone; + public Array getBones () { + return bones; } public SlotData getTarget () { 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 2aac860a1..823da165d 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java @@ -162,7 +162,8 @@ public class Skeleton { for (int i = 0, n = pathConstraints.size; i < n; i++) { PathConstraint pathConstraint = pathConstraints.get(i); - Bone bone = pathConstraint.bone; + // BOZO! - Fix update order for multiple bones. + Bone bone = pathConstraint.bones.peek(); for (int ii = updateCache.size - 1; ii >= 0; ii--) { if (updateCache.get(ii) == bone) { updateCache.insert(ii + 1, pathConstraint); 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 8c6b4c3d7..becad6226 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java @@ -210,7 +210,8 @@ public class SkeletonBinary { // Path constraints. for (int i = 0, n = input.readInt(true); i < n; i++) { PathConstraintData data = new PathConstraintData(input.readString()); - data.bone = skeletonData.bones.get(input.readInt(true)); + 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.offsetRotation = input.readFloat(); data.position = input.readFloat(); 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 72afbc720..ed6d1f0b9 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -184,9 +184,12 @@ public class SkeletonJson { for (JsonValue constraintMap = root.getChild("path"); constraintMap != null; constraintMap = constraintMap.next) { PathConstraintData data = new PathConstraintData(constraintMap.getString("name")); - String boneName = constraintMap.getString("bone"); - data.bone = skeletonData.findBone(boneName); - if (data.bone == null) throw new SerializationException("Bone not found: " + boneName); + 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); 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 index c50f5f7d3..4c4ef8786 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java @@ -31,16 +31,17 @@ package com.esotericsoftware.spine.attachments; +import static com.badlogic.gdx.math.MathUtils.*; + import com.badlogic.gdx.graphics.Color; -import com.badlogic.gdx.math.MathUtils; -import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.FloatArray; import com.esotericsoftware.spine.Slot; public class PathAttachment extends VertexAttachment { // Nonessential. final Color color = new Color(1, 0.5f, 0, 1); - float[] worldVertices, lengths; + float[] worldVertices, temp; boolean closed, constantSpeed; public PathAttachment (String name) { @@ -51,144 +52,140 @@ public class PathAttachment extends VertexAttachment { super.computeWorldVertices(slot, worldVertices); } - public void computeWorldPosition (Slot slot, float position, Vector2 worldPosition, Vector2 tangent) { - float x1, y1, cx1, cy1, cx2, cy2, x2, y2; + public void computeWorldPositions (Slot slot, float position, FloatArray lengths, FloatArray out, boolean tangents) { + float[] vertices = this.worldVertices; + int verticesLength = worldVerticesLength; + int curves = verticesLength / 6; + if (!constantSpeed) { - int curves = worldVerticesLength / 6; - if (closed) { - position = position % 1; - if (position < 0) position += 1; - } else { - position = MathUtils.clamp(position, 0, 1); - curves--; - } - int curve = position < 1 ? (int)(curves * position) : curves - 1; - position = (position - curve / (float)curves) * curves; - - float[] worldVertices = this.worldVertices; - if (closed && curve == curves - 1) { - super.computeWorldVertices(slot, curves * 6 - 4, 4, worldVertices, 0); - super.computeWorldVertices(slot, 0, 4, worldVertices, 4); - } else - super.computeWorldVertices(slot, curve * 6 + 2, 8, worldVertices, 0); - - x1 = worldVertices[0]; - y1 = worldVertices[1]; - cx1 = worldVertices[2]; - cy1 = worldVertices[3]; - cx2 = worldVertices[4]; - cy2 = worldVertices[5]; - x2 = worldVertices[6]; - y2 = worldVertices[7]; - } else { - float[] worldVertices = this.worldVertices; - int verticesLength; - if (closed) { - verticesLength = worldVerticesLength; - super.computeWorldVertices(slot, 2, verticesLength - 2, worldVertices, 0); - super.computeWorldVertices(slot, 0, 2, worldVertices, verticesLength - 2); - worldVertices[verticesLength] = worldVertices[0]; - worldVertices[verticesLength + 1] = worldVertices[1]; - verticesLength += 2; - } else { - verticesLength = worldVerticesLength - 4; - super.computeWorldVertices(slot, 2, verticesLength, worldVertices, 0); - } - - // Curve lengths. - float[] lengths = this.lengths; - float length = 0; - x1 = worldVertices[0]; - y1 = worldVertices[1]; - float tmpx, tmpy, dddfx, dddfy, ddfx, ddfy, dfx, dfy; - for (int i = 0, w = 2; w < verticesLength; i++, w += 6) { - cx1 = worldVertices[w]; - cy1 = worldVertices[w + 1]; - cx2 = worldVertices[w + 2]; - cy2 = worldVertices[w + 3]; - x2 = worldVertices[w + 4]; - y2 = worldVertices[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; - length += (float)Math.sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - length += (float)Math.sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx; - dfy += ddfy; - length += (float)Math.sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx + dddfx; - dfy += ddfy + dddfy; - length += (float)Math.sqrt(dfx * dfx + dfy * dfy); - lengths[i] = length; - x1 = x2; - y1 = y2; - } - position *= length; - - if (closed) { - position = position % length; - if (position < 0) position += length; - } else if (position < 0 || position > length) { - // Outside curve. - if (position < 0) { - x1 = worldVertices[0]; - y1 = worldVertices[1]; - cx1 = worldVertices[2] - x1; - cy1 = worldVertices[3] - y1; + for (int i = 0, n = lengths.size; i < n; i++) { + // BOZO - Wrong. Use path length property to give !constantSpeed paths support for oob and multiple bones? + position += lengths.get(i); + if (closed) { + position %= 1; + if (position < 0) position += 1; } else { - x1 = worldVertices[verticesLength - 2]; - y1 = worldVertices[verticesLength - 1]; - cx1 = x1 - worldVertices[verticesLength - 4]; - cy1 = y1 - worldVertices[verticesLength - 3]; - position -= length; + position = clamp(position, 0, 1); + curves--; } - float r = MathUtils.atan2(cy1, cx1); - float cos = MathUtils.cos(r), sin = MathUtils.sin(r); - worldPosition.x = x1 + position * cos; - worldPosition.y = y1 + position * sin; - if (tangent != null) { - tangent.x = worldPosition.x - cos; - tangent.y = worldPosition.y - sin; + int curve = position < 1 ? (int)(curves * position) : curves - 1; + position = (position - curve / (float)curves) * curves; + + if (closed && curve == curves - 1) { + super.computeWorldVertices(slot, curves * 6 - 4, 4, vertices, 0); + super.computeWorldVertices(slot, 0, 4, vertices, 4); + } else + super.computeWorldVertices(slot, curve * 6 + 2, 8, vertices, 0); + + addPoint(vertices[0], vertices[1], vertices[2], vertices[3], vertices[4], vertices[5], vertices[6], vertices[7], + position, tangents, out); + } + return; + } + + if (closed) { + super.computeWorldVertices(slot, 2, verticesLength - 2, vertices, 0); + super.computeWorldVertices(slot, 0, 2, vertices, verticesLength - 2); + vertices[verticesLength] = vertices[0]; + vertices[verticesLength + 1] = vertices[1]; + verticesLength += 2; + } else { + verticesLength -= 4; + super.computeWorldVertices(slot, 2, verticesLength, vertices, 0); + } + + // Curve lengths. + float[] temp = this.temp; + float pathLength = 0, x1 = vertices[0], y1 = vertices[1], cx1, cy1, cx2, cy2, x2, y2; + float tmpx, tmpy, dddfx, dddfy, ddfx, ddfy, dfx, dfy; + for (int i = 10, w = 2; w < verticesLength; i++, w += 6) { + cx1 = vertices[w]; + cy1 = vertices[w + 1]; + cx2 = vertices[w + 2]; + cy2 = vertices[w + 3]; + x2 = vertices[w + 4]; + y2 = vertices[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); + temp[i] = pathLength; + x1 = x2; + y1 = y2; + } + position *= pathLength; + + for (int i = 0, n = lengths.size; i < n; i++) { + position += lengths.get(i); + float p = position; + + if (closed) { + p %= pathLength; + if (p < 0) p += pathLength; + } else if (p < 0 || p > pathLength) { + // Outside path. + if (p < 0) { + x1 = vertices[0]; + y1 = vertices[1]; + cx1 = vertices[2] - x1; + cy1 = vertices[3] - y1; + } else { + x1 = vertices[verticesLength - 2]; + y1 = vertices[verticesLength - 1]; + cx1 = x1 - vertices[verticesLength - 4]; + cy1 = y1 - vertices[verticesLength - 3]; + p -= pathLength; } - return; + float r = atan2(cy1, cx1); + out.add(x1 + p * cos(r)); + out.add(y1 + p * sin(r)); + if (tangents) out.add(r + PI); + continue; } // Determine curve containing position. int curve; - length = lengths[0]; - if (position <= length) { + float length = temp[10]; + if (p <= length) { curve = 0; - position /= length; + p /= length; } else { - for (curve = 1;; curve++) { - length = lengths[curve]; - if (position <= length) { - float prev = lengths[curve - 1]; - position = (position - prev) / (length - prev); + for (curve = 11;; curve++) { + length = temp[curve]; + if (p <= length) { + float prev = temp[curve - 1]; + p = (p - prev) / (length - prev); break; } } - curve *= 6; + curve = (curve - 10) * 6; } // Curve segment lengths. - x1 = worldVertices[curve]; - y1 = worldVertices[curve + 1]; - cx1 = worldVertices[curve + 2]; - cy1 = worldVertices[curve + 3]; - cx2 = worldVertices[curve + 4]; - cy2 = worldVertices[curve + 5]; - x2 = worldVertices[curve + 6]; - y2 = worldVertices[curve + 7]; + x1 = vertices[curve]; + y1 = vertices[curve + 1]; + cx1 = vertices[curve + 2]; + cy1 = vertices[curve + 3]; + cx2 = vertices[curve + 4]; + cy2 = vertices[curve + 5]; + x2 = vertices[curve + 6]; + y2 = vertices[curve + 7]; tmpx = (x1 - cx1 * 2 + cx2) * 0.03f; tmpy = (y1 - cy1 * 2 + cy2) * 0.03f; dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.006f; @@ -198,51 +195,54 @@ public class PathAttachment extends VertexAttachment { dfx = (cx1 - x1) * 0.3f + tmpx + dddfx * 0.16666667f; dfy = (cy1 - y1) * 0.3f + tmpy + dddfy * 0.16666667f; length = (float)Math.sqrt(dfx * dfx + dfy * dfy); - lengths[0] = length; - for (int i = 1; i < 8; i++) { + // BOZO! - Don't overwrite curve lengths with segment lengths. + temp[0] = length; + for (int ii = 1; ii < 8; ii++) { dfx += ddfx; dfy += ddfy; ddfx += dddfx; ddfy += dddfy; length += (float)Math.sqrt(dfx * dfx + dfy * dfy); - lengths[i] = length; + temp[ii] = length; } dfx += ddfx; dfy += ddfy; length += (float)Math.sqrt(dfx * dfx + dfy * dfy); - lengths[8] = length; + temp[8] = length; dfx += ddfx + dddfx; dfy += ddfy + dddfy; length += (float)Math.sqrt(dfx * dfx + dfy * dfy); - lengths[9] = length; + temp[9] = length; // Weight by segment length. - position *= length; - length = lengths[0]; - if (position <= length) - position = 0.1f * position / length; + p *= length; + length = temp[0]; + if (p <= length) + p = 0.1f * p / length; else { - for (int i = 1;; i++) { - length = lengths[i]; - if (position <= length) { - float prev = lengths[i - 1]; - position = 0.1f * (i + (position - prev) / (length - prev)); + for (int ii = 1;; ii++) { + length = temp[ii]; + if (p <= length) { + float prev = temp[ii - 1]; + p = 0.1f * (ii + (p - prev) / (length - prev)); break; } } } - } - // Calculate point and tangent. - position += 0.0001f; + addPoint(x1, y1, cx1, cy1, cx2, cy2, x2, y2, p, tangents, out); + } + } + + private void addPoint (float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, float position, + boolean tangents, FloatArray out) { + if (position == 0) position = 0.0001f; float tt = position * position, ttt = tt * position, u = 1 - position, uu = u * u, uuu = uu * u; float ut = u * position, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * position; - worldPosition.x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt; - worldPosition.y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt; - if (tangent != null) { - tangent.x = x1 * uu + cx1 * ut * 2 + cx2 * tt; - tangent.y = y1 * uu + cy1 * ut * 2 + cy2 * tt; - } + float x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt; + out.add(x); + out.add(y); + if (tangents) out.add(atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt))); } public Color getColor () { @@ -267,7 +267,8 @@ public class PathAttachment extends VertexAttachment { public void setWorldVerticesLength (int worldVerticesLength) { super.setWorldVerticesLength(worldVerticesLength); + // BOZO! - Share working memory? worldVertices = new float[Math.max(2, worldVerticesLength + 4)]; - lengths = new float[Math.max(10, worldVerticesLength / 6)]; + temp = new float[10 + worldVerticesLength / 6]; } } From cdbc878f317990500589b17ce6ee1889393a7c2e Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Sun, 29 May 2016 02:33:14 +0200 Subject: [PATCH 13/45] Scale mix for path constraints. --- .../com/esotericsoftware/spine/Animation.java | 33 ++- .../spine/PathConstraint.java | 276 ++++++++++++++++-- .../spine/PathConstraintData.java | 10 +- .../com/esotericsoftware/spine/Skeleton.java | 1 + .../spine/SkeletonBinary.java | 6 +- .../esotericsoftware/spine/SkeletonJson.java | 5 +- .../spine/attachments/PathAttachment.java | 221 +------------- 7 files changed, 299 insertions(+), 253 deletions(-) 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 d0475c0f8..4fc2aa7ad 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -874,20 +874,23 @@ public class Animation { } static public class PathConstraintTimeline extends CurveTimeline { - static private final int PREV_TIME = -4; - static private final int PREV_POSITION = -3; - static private final int PREV_ROTATE_MIX = -2; - static private final int PREV_TRANSLATE_MIX = -1; + static private final int PREV_TIME = -5; + static private final int PREV_POSITION = -4; + static private final int PREV_ROTATE_MIX = -3; + static private final int PREV_TRANSLATE_MIX = -2; + static private final int PREV_SCALE_MIX = -1; static private final int POSITION = 1; static private final int ROTATE_MIX = 2; static private final int TRANSLATE_MIX = 3; + static private final int SCALE_MIX = 4; int pathConstraintIndex; + private final float[] frames; // time, rotate mix, translate mix, scale mix, shear mix, ... public PathConstraintTimeline (int frameCount) { super(frameCount); - frames = new float[frameCount * 4]; + frames = new float[frameCount * 5]; } public void setPathConstraintIndex (int index) { @@ -903,12 +906,13 @@ public class Animation { } /** Sets the time, position, and mixes of the specified keyframe. */ - public void setFrame (int frameIndex, float time, float position, float rotateMix, float translateMix) { - frameIndex *= 4; + public void setFrame (int frameIndex, float time, float position, float rotateMix, float translateMix, float scaleMix) { + frameIndex *= 5; frames[frameIndex] = time; frames[frameIndex + 1] = position; frames[frameIndex + 2] = rotateMix; frames[frameIndex + 3] = translateMix; + frames[frameIndex + 4] = scaleMix; } public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha) { @@ -917,27 +921,30 @@ public class Animation { PathConstraint constraint = skeleton.pathConstraints.get(pathConstraintIndex); - if (time >= frames[frames.length - 4]) { // Time is after last frame. + if (time >= frames[frames.length - 5]) { // Time is after last frame. int i = frames.length - 1; - constraint.position += (frames[i - 2] - constraint.position) * alpha; - constraint.rotateMix += (frames[i - 1] - constraint.rotateMix) * alpha; - constraint.translateMix += (frames[i] - constraint.translateMix) * alpha; + constraint.position += (frames[i - 3] - constraint.position) * alpha; + constraint.rotateMix += (frames[i - 2] - constraint.rotateMix) * alpha; + constraint.translateMix += (frames[i - 1] - constraint.translateMix) * alpha; + constraint.scaleMix += (frames[i] - constraint.scaleMix) * alpha; return; } // Interpolate between the previous frame and the current frame. - int frame = binarySearch(frames, time, 4); + int frame = binarySearch(frames, time, 5); float frameTime = frames[frame]; float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime), 0, 1); - percent = getCurvePercent(frame / 4 - 1, percent); + percent = getCurvePercent(frame / 5 - 1, percent); float position = frames[frame + PREV_POSITION]; float rotate = frames[frame + PREV_ROTATE_MIX]; float translate = frames[frame + PREV_TRANSLATE_MIX]; + float scale = frames[frame + PREV_SCALE_MIX]; constraint.position += (position + (frames[frame + POSITION] - position) * percent - constraint.position) * alpha; constraint.rotateMix += (rotate + (frames[frame + ROTATE_MIX] - rotate) * percent - constraint.rotateMix) * alpha; constraint.translateMix += (translate + (frames[frame + TRANSLATE_MIX] - translate) * percent - constraint.translateMix) * alpha; + constraint.scaleMix += (scale + (frames[frame + SCALE_MIX] - scale) * percent - constraint.scaleMix) * alpha; } } } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java index 6eecc75f0..459148127 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java @@ -3,7 +3,6 @@ package com.esotericsoftware.spine; import static com.badlogic.gdx.math.MathUtils.*; -import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.FloatArray; import com.esotericsoftware.spine.attachments.Attachment; @@ -13,15 +12,16 @@ public class PathConstraint implements Updatable { final PathConstraintData data; final Array bones; Slot target; - float position, rotateMix, translateMix; + float position, rotateMix, translateMix, scaleMix; final FloatArray lengths = new FloatArray(), positions = new FloatArray(); - final Vector2 temp = new Vector2(); + final FloatArray worldVertices = new FloatArray(), temp = new FloatArray(); public PathConstraint (PathConstraintData data, Skeleton skeleton) { this.data = data; position = data.position; rotateMix = data.rotateMix; translateMix = data.translateMix; + scaleMix = data.scaleMix; bones = new Array(data.bones.size); for (BoneData boneData : data.bones) @@ -40,6 +40,7 @@ public class PathConstraint implements Updatable { position = constraint.position; rotateMix = constraint.rotateMix; translateMix = constraint.translateMix; + scaleMix = constraint.scaleMix; } public void apply () { @@ -50,12 +51,12 @@ public class PathConstraint implements Updatable { Attachment attachment = target.getAttachment(); if (!(attachment instanceof PathAttachment)) return; - float translateMix = this.translateMix, rotateMix = this.rotateMix; - boolean translate = translateMix > 0, rotate = rotateMix > 0; + float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix; + boolean translate = translateMix > 0, rotate = rotateMix > 0, scale = scaleMix > 0; if (!translate && !rotate) return; PathAttachment path = (PathAttachment)attachment; - FloatArray lengths = this.lengths, positions = this.positions; + FloatArray lengths = this.lengths; lengths.clear(); lengths.add(0); positions.clear(); @@ -63,7 +64,7 @@ public class PathConstraint implements Updatable { Array bones = this.bones; int boneCount = bones.size; if (boneCount == 1) { - path.computeWorldPositions(target, position, lengths, positions, rotate); + computeWorldPositions(path, rotate); Bone bone = bones.first(); bone.worldX += (positions.first() - bone.worldX) * translateMix; bone.worldY += (positions.get(1) - bone.worldY) * translateMix; @@ -85,22 +86,39 @@ public class PathConstraint implements Updatable { for (int i = 0; i < boneCount; i++) lengths.add(bones.get(i).data.length); - path.computeWorldPositions(target, position, lengths, positions, false); + computeWorldPositions(path, false); - Vector2 temp = this.temp; - float boneX = positions.first(), boneY = positions.get(1); + float[] positions = this.positions.items; + float boneX = positions[0], boneY = positions[1]; for (int i = 0, p = 2; i < boneCount; i++, p += 2) { Bone bone = bones.get(i); + float x = positions[p], y = positions[p + 1]; + + if (scale) { + float dx = boneX - x, dy = boneY - y, d = (float)Math.sqrt(dx * dx + dy * dy); + // BOZO - Length not transformed by bone matrix. + float sx = bone.scaleX + (d / bone.data.length - bone.scaleX) * scaleMix; + bone.a *= sx; + bone.c *= sx; + } + bone.worldX += (boneX - bone.worldX) * translateMix; bone.worldY += (boneY - bone.worldY) * translateMix; - float x = positions.get(p), y = positions.get(p + 1); + float r = atan2(y - boneY, x - boneX) + data.offsetRotation * degRad; + if (data.offsetRotation != 0) { + boneX = x; + boneY = y; + } else { + // BOZO - Doesn't transform by bone matrix. + float cos = cos(r), sin = sin(r); + float length = bone.data.length, mix = rotateMix * (1 - scaleMix); + boneX = x + (length * cos + boneX - x) * mix; + boneY = y + (length * sin + boneY - y) * mix; + } if (rotate) { - // Scale. - // float dist = (float)Math.sqrt((boneX - x) * (boneX - x) + (boneY - y) * (boneY - y)); - // bone.scaleX = dist / bone.data.length; float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float r = atan2(y - boneY, x - boneX) - atan2(c, a) + data.offsetRotation * degRad; + r -= atan2(c, a); if (r > PI) r -= PI2; else if (r < -PI) r += PI2; @@ -110,24 +128,216 @@ public class PathConstraint implements Updatable { bone.b = cos * b - sin * d; bone.c = sin * a + cos * c; bone.d = sin * b + cos * d; - if (data.offsetRotation == 0 && rotateMix == 1) { - // Place at tip. - bone.localToWorld(temp.set(bone.data.length, 0)); - boneX = temp.x; - boneY = temp.y; - } else { - // Place on path. - boneX = x; - boneY = y; - } - } else { - // Place on path. - boneX = x; - boneY = y; } } } + private void computeWorldPositions (PathAttachment path, boolean tangents) { + Slot slot = target; + float position = this.position; + FloatArray out = positions; + FloatArray lengths = this.lengths; + + int verticesLength = path.getWorldVerticesLength(), curves = verticesLength / 6; + boolean closed = path.getClosed(); + float[] vertices; + + if (!path.getConstantSpeed()) { + if (!closed) curves--; + float pathLength = path.getLength(); + vertices = worldVertices.setSize(8); + for (int i = 0, n = lengths.size; i < n; i++) { + position += lengths.get(i) / pathLength; + if (closed) { + position %= 1; + if (position < 0) position += 1; + } else if (position < 0 || position > 1) { + path.computeWorldVertices(slot, 0, 4, vertices, 0); + path.computeWorldVertices(slot, verticesLength - 4, 4, vertices, 4); + addOutsidePoint(position * pathLength, vertices, 8, pathLength, out, tangents); + continue; + } + int curve = position < 1 ? (int)(curves * position) : curves - 1; + if (closed && curve == curves - 1) { + path.computeWorldVertices(slot, verticesLength - 4, 4, vertices, 0); + path.computeWorldVertices(slot, 0, 4, vertices, 4); + } else + path.computeWorldVertices(slot, curve * 6 + 2, 8, vertices, 0); + addCurvePoint(vertices[0], vertices[1], vertices[2], vertices[3], vertices[4], vertices[5], vertices[6], vertices[7], + (position - curve / (float)curves) * curves, tangents, out); + } + return; + } + + if (closed) { + verticesLength += 2; + vertices = worldVertices.setSize(verticesLength); + path.computeWorldVertices(slot, 2, verticesLength - 4, vertices, 0); + path.computeWorldVertices(slot, 0, 2, vertices, verticesLength - 4); + vertices[verticesLength - 2] = vertices[0]; + vertices[verticesLength - 1] = vertices[1]; + } else { + verticesLength -= 4; + vertices = worldVertices.setSize(verticesLength); + path.computeWorldVertices(slot, 2, verticesLength, vertices, 0); + } + + // Curve lengths. + temp.setSize(10 + curves); // BOZO - Combine with worldVertices? + float[] temp = this.temp.items; + float pathLength = 0, x1 = vertices[0], y1 = vertices[1], cx1, cy1, cx2, cy2, x2, y2; + float tmpx, tmpy, dddfx, dddfy, ddfx, ddfy, dfx, dfy; + for (int i = 10, w = 2; w < verticesLength; i++, w += 6) { + cx1 = vertices[w]; + cy1 = vertices[w + 1]; + cx2 = vertices[w + 2]; + cy2 = vertices[w + 3]; + x2 = vertices[w + 4]; + y2 = vertices[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); + temp[i] = pathLength; + x1 = x2; + y1 = y2; + } + position *= pathLength; + + for (int i = 0, n = lengths.size; i < n; i++) { + position += lengths.get(i); + float p = position; + + if (closed) { + p %= pathLength; + if (p < 0) p += pathLength; + } else if (p < 0 || p > pathLength) { + addOutsidePoint(p, vertices, verticesLength, pathLength, out, tangents); + continue; + } + + // Determine curve containing position. + int curve; + float length = temp[10]; + if (p <= length) { + curve = 0; + p /= length; + } else { + for (curve = 11;; curve++) { + length = temp[curve]; + if (p <= length) { + float prev = temp[curve - 1]; + p = (p - prev) / (length - prev); + break; + } + } + curve = (curve - 10) * 6; + } + + // Curve segment lengths. + x1 = vertices[curve]; + y1 = vertices[curve + 1]; + cx1 = vertices[curve + 2]; + cy1 = vertices[curve + 3]; + cx2 = vertices[curve + 4]; + cy2 = vertices[curve + 5]; + x2 = vertices[curve + 6]; + y2 = vertices[curve + 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; + length = (float)Math.sqrt(dfx * dfx + dfy * dfy); + temp[0] = length; + for (int ii = 1; ii < 8; ii++) { + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + length += (float)Math.sqrt(dfx * dfx + dfy * dfy); + temp[ii] = length; + } + dfx += ddfx; + dfy += ddfy; + length += (float)Math.sqrt(dfx * dfx + dfy * dfy); + temp[8] = length; + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + length += (float)Math.sqrt(dfx * dfx + dfy * dfy); + temp[9] = length; + + // Weight by segment length. + p *= length; + length = temp[0]; + if (p <= length) + p = 0.1f * p / length; + else { + for (int ii = 1;; ii++) { + length = temp[ii]; + if (p <= length) { + float prev = temp[ii - 1]; + p = 0.1f * (ii + (p - prev) / (length - prev)); + break; + } + } + } + + addCurvePoint(x1, y1, cx1, cy1, cx2, cy2, x2, y2, p, tangents, out); + } + } + + private void addOutsidePoint (float position, float[] vertices, int verticesLength, float pathLength, FloatArray out, + boolean tangents) { + float x1, y1, x2, y2; + if (position < 0) { + x1 = vertices[0]; + y1 = vertices[1]; + x2 = vertices[2] - x1; + y2 = vertices[3] - y1; + } else { + x1 = vertices[verticesLength - 2]; + y1 = vertices[verticesLength - 1]; + x2 = x1 - vertices[verticesLength - 4]; + y2 = y1 - vertices[verticesLength - 3]; + position -= pathLength; + } + float r = atan2(y2, x2); + out.add(x1 + position * cos(r)); + out.add(y1 + position * sin(r)); + if (tangents) out.add(r + PI); + } + + private void addCurvePoint (float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, float position, + boolean tangents, FloatArray out) { + if (position == 0) position = 0.0001f; + float tt = position * position, ttt = tt * position, u = 1 - position, uu = u * u, uuu = uu * u; + float ut = u * position, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * position; + float x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt; + out.add(x); + out.add(y); + if (tangents) out.add(atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt))); + } + public float getPosition () { return position; } @@ -152,6 +362,14 @@ public class PathConstraint implements Updatable { this.translateMix = translateMix; } + public float getScaleMix () { + return scaleMix; + } + + public void setScaleMix (float scaleMix) { + this.scaleMix = scaleMix; + } + public Array getBones () { return bones; } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java index 4df2952ea..c4e4d4f03 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java @@ -7,7 +7,7 @@ public class PathConstraintData { final String name; final Array bones = new Array(); SlotData target; - float position, rotateMix, translateMix; + float position, rotateMix, translateMix, scaleMix; float offsetRotation; public PathConstraintData (String name) { @@ -50,6 +50,14 @@ public class PathConstraintData { this.translateMix = translateMix; } + public float getScaleMix () { + return scaleMix; + } + + public void setScaleMix (float scaleMix) { + this.scaleMix = scaleMix; + } + public float getOffsetRotation () { return offsetRotation; } 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 823da165d..32ff7a6da 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java @@ -227,6 +227,7 @@ public class Skeleton { constraint.position = data.position; constraint.rotateMix = data.rotateMix; constraint.translateMix = data.translateMix; + constraint.scaleMix = data.scaleMix; } } 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 becad6226..1bddff0d3 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java @@ -217,6 +217,7 @@ public class SkeletonBinary { data.position = input.readFloat(); data.rotateMix = input.readFloat(); data.translateMix = input.readFloat(); + data.scaleMix = input.readFloat(); skeletonData.pathConstraints.add(data); } @@ -588,11 +589,12 @@ public class SkeletonBinary { PathConstraintTimeline timeline = new PathConstraintTimeline(frameCount); timeline.pathConstraintIndex = index; for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.setFrame(frameIndex, input.readFloat(), input.readFloat(), input.readFloat(), input.readFloat()); + 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 * 4 - 4]); + duration = Math.max(duration, timeline.getFrames()[frameCount * 5 - 5]); } // Deform timelines. 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 ed6d1f0b9..4c7ecf0c3 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -199,6 +199,7 @@ public class SkeletonJson { data.position = constraintMap.getFloat("position", 0); data.rotateMix = constraintMap.getFloat("rotateMix", 1); data.translateMix = constraintMap.getFloat("translateMix", 1); + data.scaleMix = constraintMap.getFloat("scaleMix", 1); skeletonData.pathConstraints.add(data); } @@ -506,12 +507,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("scaleMix", 1), - valueMap.getFloat("rotateMix", 1), valueMap.getFloat("translateMix", 1)); + valueMap.getFloat("rotateMix", 1), valueMap.getFloat("translateMix", 1), valueMap.getFloat("scaleMix", 1)); readCurve(valueMap, timeline, frameIndex); frameIndex++; } timelines.add(timeline); - duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() * 4 - 4]); + duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() * 5 - 5]); } // Deform timelines. 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 index 4c4ef8786..4683221b4 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java @@ -31,19 +31,16 @@ package com.esotericsoftware.spine.attachments; -import static com.badlogic.gdx.math.MathUtils.*; - import com.badlogic.gdx.graphics.Color; -import com.badlogic.gdx.utils.FloatArray; import com.esotericsoftware.spine.Slot; public class PathAttachment extends VertexAttachment { + float length; + boolean closed, constantSpeed; + // Nonessential. final Color color = new Color(1, 0.5f, 0, 1); - float[] worldVertices, temp; - boolean closed, constantSpeed; - public PathAttachment (String name) { super(name); } @@ -52,201 +49,8 @@ public class PathAttachment extends VertexAttachment { super.computeWorldVertices(slot, worldVertices); } - public void computeWorldPositions (Slot slot, float position, FloatArray lengths, FloatArray out, boolean tangents) { - float[] vertices = this.worldVertices; - int verticesLength = worldVerticesLength; - int curves = verticesLength / 6; - - if (!constantSpeed) { - for (int i = 0, n = lengths.size; i < n; i++) { - // BOZO - Wrong. Use path length property to give !constantSpeed paths support for oob and multiple bones? - position += lengths.get(i); - if (closed) { - position %= 1; - if (position < 0) position += 1; - } else { - position = clamp(position, 0, 1); - curves--; - } - int curve = position < 1 ? (int)(curves * position) : curves - 1; - position = (position - curve / (float)curves) * curves; - - if (closed && curve == curves - 1) { - super.computeWorldVertices(slot, curves * 6 - 4, 4, vertices, 0); - super.computeWorldVertices(slot, 0, 4, vertices, 4); - } else - super.computeWorldVertices(slot, curve * 6 + 2, 8, vertices, 0); - - addPoint(vertices[0], vertices[1], vertices[2], vertices[3], vertices[4], vertices[5], vertices[6], vertices[7], - position, tangents, out); - } - return; - } - - if (closed) { - super.computeWorldVertices(slot, 2, verticesLength - 2, vertices, 0); - super.computeWorldVertices(slot, 0, 2, vertices, verticesLength - 2); - vertices[verticesLength] = vertices[0]; - vertices[verticesLength + 1] = vertices[1]; - verticesLength += 2; - } else { - verticesLength -= 4; - super.computeWorldVertices(slot, 2, verticesLength, vertices, 0); - } - - // Curve lengths. - float[] temp = this.temp; - float pathLength = 0, x1 = vertices[0], y1 = vertices[1], cx1, cy1, cx2, cy2, x2, y2; - float tmpx, tmpy, dddfx, dddfy, ddfx, ddfy, dfx, dfy; - for (int i = 10, w = 2; w < verticesLength; i++, w += 6) { - cx1 = vertices[w]; - cy1 = vertices[w + 1]; - cx2 = vertices[w + 2]; - cy2 = vertices[w + 3]; - x2 = vertices[w + 4]; - y2 = vertices[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); - temp[i] = pathLength; - x1 = x2; - y1 = y2; - } - position *= pathLength; - - for (int i = 0, n = lengths.size; i < n; i++) { - position += lengths.get(i); - float p = position; - - if (closed) { - p %= pathLength; - if (p < 0) p += pathLength; - } else if (p < 0 || p > pathLength) { - // Outside path. - if (p < 0) { - x1 = vertices[0]; - y1 = vertices[1]; - cx1 = vertices[2] - x1; - cy1 = vertices[3] - y1; - } else { - x1 = vertices[verticesLength - 2]; - y1 = vertices[verticesLength - 1]; - cx1 = x1 - vertices[verticesLength - 4]; - cy1 = y1 - vertices[verticesLength - 3]; - p -= pathLength; - } - float r = atan2(cy1, cx1); - out.add(x1 + p * cos(r)); - out.add(y1 + p * sin(r)); - if (tangents) out.add(r + PI); - continue; - } - - // Determine curve containing position. - int curve; - float length = temp[10]; - if (p <= length) { - curve = 0; - p /= length; - } else { - for (curve = 11;; curve++) { - length = temp[curve]; - if (p <= length) { - float prev = temp[curve - 1]; - p = (p - prev) / (length - prev); - break; - } - } - curve = (curve - 10) * 6; - } - - // Curve segment lengths. - x1 = vertices[curve]; - y1 = vertices[curve + 1]; - cx1 = vertices[curve + 2]; - cy1 = vertices[curve + 3]; - cx2 = vertices[curve + 4]; - cy2 = vertices[curve + 5]; - x2 = vertices[curve + 6]; - y2 = vertices[curve + 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; - length = (float)Math.sqrt(dfx * dfx + dfy * dfy); - // BOZO! - Don't overwrite curve lengths with segment lengths. - temp[0] = length; - for (int ii = 1; ii < 8; ii++) { - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - length += (float)Math.sqrt(dfx * dfx + dfy * dfy); - temp[ii] = length; - } - dfx += ddfx; - dfy += ddfy; - length += (float)Math.sqrt(dfx * dfx + dfy * dfy); - temp[8] = length; - dfx += ddfx + dddfx; - dfy += ddfy + dddfy; - length += (float)Math.sqrt(dfx * dfx + dfy * dfy); - temp[9] = length; - - // Weight by segment length. - p *= length; - length = temp[0]; - if (p <= length) - p = 0.1f * p / length; - else { - for (int ii = 1;; ii++) { - length = temp[ii]; - if (p <= length) { - float prev = temp[ii - 1]; - p = 0.1f * (ii + (p - prev) / (length - prev)); - break; - } - } - } - - addPoint(x1, y1, cx1, cy1, cx2, cy2, x2, y2, p, tangents, out); - } - } - - private void addPoint (float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, float position, - boolean tangents, FloatArray out) { - if (position == 0) position = 0.0001f; - float tt = position * position, ttt = tt * position, u = 1 - position, uu = u * u, uuu = uu * u; - float ut = u * position, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * position; - float x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt; - out.add(x); - out.add(y); - if (tangents) out.add(atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt))); - } - - public Color getColor () { - return color; + public void computeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset) { + super.computeWorldVertices(slot, start, count, worldVertices, offset); } public boolean getClosed () { @@ -265,10 +69,15 @@ public class PathAttachment extends VertexAttachment { this.constantSpeed = constantSpeed; } - public void setWorldVerticesLength (int worldVerticesLength) { - super.setWorldVerticesLength(worldVerticesLength); - // BOZO! - Share working memory? - worldVertices = new float[Math.max(2, worldVerticesLength + 4)]; - temp = new float[10 + worldVerticesLength / 6]; + public float getLength () { + return length; + } + + public void setLength (float length) { + this.length = length; + } + + public Color getColor () { + return color; } } From c4810e23d68177a3e2eb736d70e01c2f6131ae3f Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Sun, 29 May 2016 05:58:23 +0200 Subject: [PATCH 14/45] Path constraint optimization. --- .../spine/PathConstraint.java | 273 +++++++++--------- 1 file changed, 142 insertions(+), 131 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java index 459148127..83a3c2601 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java @@ -13,8 +13,7 @@ public class PathConstraint implements Updatable { final Array bones; Slot target; float position, rotateMix, translateMix, scaleMix; - final FloatArray lengths = new FloatArray(), positions = new FloatArray(); - final FloatArray worldVertices = new FloatArray(), temp = new FloatArray(); + final FloatArray lengths = new FloatArray(), positions = new FloatArray(), temp = new FloatArray(); public PathConstraint (PathConstraintData data, Skeleton skeleton) { this.data = data; @@ -53,24 +52,23 @@ public class PathConstraint implements Updatable { float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix; boolean translate = translateMix > 0, rotate = rotateMix > 0, scale = scaleMix > 0; - if (!translate && !rotate) return; + if (!translate && !rotate && !scale) return; PathAttachment path = (PathAttachment)attachment; FloatArray lengths = this.lengths; lengths.clear(); lengths.add(0); - positions.clear(); Array bones = this.bones; int boneCount = bones.size; if (boneCount == 1) { - computeWorldPositions(path, rotate); + float[] positions = computeWorldPositions(path, rotate); Bone bone = bones.first(); - bone.worldX += (positions.first() - bone.worldX) * translateMix; - bone.worldY += (positions.get(1) - bone.worldY) * translateMix; + bone.worldX += (positions[0] - bone.worldX) * translateMix; + bone.worldY += (positions[1] - bone.worldY) * translateMix; if (rotate) { float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float r = positions.get(2) - atan2(c, a) + data.offsetRotation * degRad; + float r = positions[2] - atan2(c, a) + data.offsetRotation * degRad; if (r > PI) r -= PI2; else if (r < -PI) r += PI2; @@ -84,46 +82,48 @@ public class PathConstraint implements Updatable { return; } - for (int i = 0; i < boneCount; i++) - lengths.add(bones.get(i).data.length); - computeWorldPositions(path, false); + for (int i = 0; i < boneCount; i++) { + Bone bone = bones.get(i); + float length = bone.data.length; + float x = length * bone.a, y = length * bone.c; + lengths.add((float)Math.sqrt(x * x + y * y)); + } + float[] positions = computeWorldPositions(path, false); - float[] positions = this.positions.items; - float boneX = positions[0], boneY = positions[1]; + float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation; for (int i = 0, p = 2; i < boneCount; i++, p += 2) { Bone bone = bones.get(i); - float x = positions[p], y = positions[p + 1]; - - if (scale) { - float dx = boneX - x, dy = boneY - y, d = (float)Math.sqrt(dx * dx + dy * dy); - // BOZO - Length not transformed by bone matrix. - float sx = bone.scaleX + (d / bone.data.length - bone.scaleX) * scaleMix; - bone.a *= sx; - bone.c *= sx; - } - bone.worldX += (boneX - bone.worldX) * translateMix; bone.worldY += (boneY - bone.worldY) * translateMix; - - float r = atan2(y - boneY, x - boneX) + data.offsetRotation * degRad; - if (data.offsetRotation != 0) { + float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; + if (scale) { + float s = ((float)Math.sqrt(dx * dx + dy * dy) / lengths.get(i + 1) - 1) * scaleMix + 1; + bone.a *= s; + bone.c *= s; + } + if (!rotate) { boneX = x; boneY = y; } else { - // BOZO - Doesn't transform by bone matrix. - float cos = cos(r), sin = sin(r); - float length = bone.data.length, mix = rotateMix * (1 - scaleMix); - boneX = x + (length * cos + boneX - x) * mix; - boneY = y + (length * sin + boneY - y) * mix; - } - if (rotate) { float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - r -= atan2(c, a); + float r = atan2(dy, dx) - atan2(c, a) + offsetRotation * degRad, cos, sin; + if (offsetRotation != 0) { + boneX = x; + boneY = y; + } else { // Mix between on path and at tip. + cos = cos(r); + sin = sin(r); + float length = bone.data.length; + boneX = x + (length * (cos * a - sin * c) - dx) * rotateMix; + boneY = y + (length * (sin * a + cos * c) - dy) * rotateMix; + } if (r > PI) r -= PI2; - else if (r < -PI) r += PI2; + else if (r < -PI) // + r += PI2; r *= rotateMix; - float cos = cos(r), sin = sin(r); + cos = cos(r); + sin = sin(r); bone.a = cos * a - sin * c; bone.b = cos * b - sin * d; bone.c = sin * a + cos * c; @@ -132,68 +132,71 @@ public class PathConstraint implements Updatable { } } - private void computeWorldPositions (PathAttachment path, boolean tangents) { - Slot slot = target; + private float[] computeWorldPositions (PathAttachment path, boolean tangents) { + Slot target = this.target; float position = this.position; - FloatArray out = positions; - FloatArray lengths = this.lengths; - - int verticesLength = path.getWorldVerticesLength(), curves = verticesLength / 6; + int lengthCount = lengths.size; + float[] lengths = this.lengths.items; + FloatArray positions = this.positions; + positions.clear(); boolean closed = path.getClosed(); - float[] vertices; + int verticesLength = path.getWorldVerticesLength(), curves = verticesLength / 6; + float[] temp; if (!path.getConstantSpeed()) { if (!closed) curves--; float pathLength = path.getLength(); - vertices = worldVertices.setSize(8); - for (int i = 0, n = lengths.size; i < n; i++) { - position += lengths.get(i) / pathLength; + temp = this.temp.setSize(8); + for (int i = 0; i < lengthCount; i++) { + position += lengths[i] / pathLength; if (closed) { position %= 1; if (position < 0) position += 1; } else if (position < 0 || position > 1) { - path.computeWorldVertices(slot, 0, 4, vertices, 0); - path.computeWorldVertices(slot, verticesLength - 4, 4, vertices, 4); - addOutsidePoint(position * pathLength, vertices, 8, pathLength, out, tangents); + path.computeWorldVertices(target, 0, 4, temp, 0); + path.computeWorldVertices(target, verticesLength - 4, 4, temp, 4); + addOutsidePosition(position * pathLength, temp, 0, 8, pathLength, positions, tangents); continue; } int curve = position < 1 ? (int)(curves * position) : curves - 1; if (closed && curve == curves - 1) { - path.computeWorldVertices(slot, verticesLength - 4, 4, vertices, 0); - path.computeWorldVertices(slot, 0, 4, vertices, 4); + path.computeWorldVertices(target, verticesLength - 4, 4, temp, 0); + path.computeWorldVertices(target, 0, 4, temp, 4); } else - path.computeWorldVertices(slot, curve * 6 + 2, 8, vertices, 0); - addCurvePoint(vertices[0], vertices[1], vertices[2], vertices[3], vertices[4], vertices[5], vertices[6], vertices[7], - (position - curve / (float)curves) * curves, tangents, out); + path.computeWorldVertices(target, curve * 6 + 2, 8, temp, 0); + addCurvePosition((position - curve / (float)curves) * curves, temp[0], temp[1], temp[2], temp[3], temp[4], temp[5], + temp[6], temp[7], positions, tangents); } - return; + return positions.items; } + // World vertices, verticesStart to verticesStart + verticesLength. + int verticesStart = 10 + curves; + temp = this.temp.setSize(verticesStart + verticesLength + 2); if (closed) { verticesLength += 2; - vertices = worldVertices.setSize(verticesLength); - path.computeWorldVertices(slot, 2, verticesLength - 4, vertices, 0); - path.computeWorldVertices(slot, 0, 2, vertices, verticesLength - 4); - vertices[verticesLength - 2] = vertices[0]; - vertices[verticesLength - 1] = vertices[1]; + int verticesEnd = verticesStart + verticesLength; + path.computeWorldVertices(target, 2, verticesLength - 4, temp, verticesStart); + path.computeWorldVertices(target, 0, 2, temp, verticesEnd - 4); + temp[verticesEnd - 2] = temp[verticesStart]; + temp[verticesEnd - 1] = temp[verticesStart + 1]; } else { + verticesStart--; verticesLength -= 4; - vertices = worldVertices.setSize(verticesLength); - path.computeWorldVertices(slot, 2, verticesLength, vertices, 0); + path.computeWorldVertices(target, 2, verticesLength, temp, verticesStart); } - // Curve lengths. - temp.setSize(10 + curves); // BOZO - Combine with worldVertices? - float[] temp = this.temp.items; - float pathLength = 0, x1 = vertices[0], y1 = vertices[1], cx1, cy1, cx2, cy2, x2, y2; + // Curve lengths, 10 to verticesStart. + float pathLength = 0; + float x1 = temp[verticesStart], y1 = temp[verticesStart + 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 = 10, w = 2; w < verticesLength; i++, w += 6) { - cx1 = vertices[w]; - cy1 = vertices[w + 1]; - cx2 = vertices[w + 2]; - cy2 = vertices[w + 3]; - x2 = vertices[w + 4]; - y2 = vertices[w + 5]; + for (int i = 10, v = verticesStart + 2; i < verticesStart; i++, v += 6) { + cx1 = temp[v]; + cy1 = temp[v + 1]; + cx2 = temp[v + 2]; + cy2 = temp[v + 3]; + x2 = temp[v + 4]; + y2 = temp[v + 5]; tmpx = (x1 - cx1 * 2 + cx2) * 0.1875f; tmpy = (y1 - cy1 * 2 + cy2) * 0.1875f; dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375f; @@ -220,15 +223,17 @@ public class PathConstraint implements Updatable { } position *= pathLength; - for (int i = 0, n = lengths.size; i < n; i++) { - position += lengths.get(i); + int lastCurve = 0; + float curveLength = 0; + for (int i = 0; i < lengthCount; i++) { + position += lengths[i]; float p = position; if (closed) { p %= pathLength; if (p < 0) p += pathLength; } else if (p < 0 || p > pathLength) { - addOutsidePoint(p, vertices, verticesLength, pathLength, out, tangents); + addOutsidePosition(p, temp, verticesStart, verticesLength, pathLength, positions, tangents); continue; } @@ -236,7 +241,7 @@ public class PathConstraint implements Updatable { int curve; float length = temp[10]; if (p <= length) { - curve = 0; + curve = verticesStart; p /= length; } else { for (curve = 11;; curve++) { @@ -247,47 +252,50 @@ public class PathConstraint implements Updatable { break; } } - curve = (curve - 10) * 6; + curve = verticesStart + (curve - 10) * 6; } - // Curve segment lengths. - x1 = vertices[curve]; - y1 = vertices[curve + 1]; - cx1 = vertices[curve + 2]; - cy1 = vertices[curve + 3]; - cx2 = vertices[curve + 4]; - cy2 = vertices[curve + 5]; - x2 = vertices[curve + 6]; - y2 = vertices[curve + 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; - length = (float)Math.sqrt(dfx * dfx + dfy * dfy); - temp[0] = length; - for (int ii = 1; ii < 8; ii++) { + // Curve segment lengths, 0 to 10. + if (curve != lastCurve) { + lastCurve = curve; + x1 = temp[curve]; + y1 = temp[curve + 1]; + cx1 = temp[curve + 2]; + cy1 = temp[curve + 3]; + cx2 = temp[curve + 4]; + cy2 = temp[curve + 5]; + x2 = temp[curve + 6]; + y2 = temp[curve + 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); + temp[0] = curveLength; + for (int ii = 1; ii < 8; ii++) { + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + curveLength += (float)Math.sqrt(dfx * dfx + dfy * dfy); + temp[ii] = curveLength; + } dfx += ddfx; dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - length += (float)Math.sqrt(dfx * dfx + dfy * dfy); - temp[ii] = length; + curveLength += (float)Math.sqrt(dfx * dfx + dfy * dfy); + temp[8] = curveLength; + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + curveLength += (float)Math.sqrt(dfx * dfx + dfy * dfy); + temp[9] = curveLength; } - dfx += ddfx; - dfy += ddfy; - length += (float)Math.sqrt(dfx * dfx + dfy * dfy); - temp[8] = length; - dfx += ddfx + dddfx; - dfy += ddfy + dddfy; - length += (float)Math.sqrt(dfx * dfx + dfy * dfy); - temp[9] = length; // Weight by segment length. - p *= length; + p *= curveLength; length = temp[0]; if (p <= length) p = 0.1f * p / length; @@ -302,36 +310,39 @@ public class PathConstraint implements Updatable { } } - addCurvePoint(x1, y1, cx1, cy1, cx2, cy2, x2, y2, p, tangents, out); + addCurvePosition(p, x1, y1, cx1, cy1, cx2, cy2, x2, y2, positions, tangents); } + + return positions.items; } - private void addOutsidePoint (float position, float[] vertices, int verticesLength, float pathLength, FloatArray out, - boolean tangents) { + private void addOutsidePosition (float p, float[] temp, int verticesStart, int verticesLength, float pathLength, + FloatArray out, boolean tangents) { float x1, y1, x2, y2; - if (position < 0) { - x1 = vertices[0]; - y1 = vertices[1]; - x2 = vertices[2] - x1; - y2 = vertices[3] - y1; + if (p < 0) { + x1 = temp[verticesStart]; + y1 = temp[verticesStart + 1]; + x2 = temp[verticesStart + 2] - x1; + y2 = temp[verticesStart + 3] - y1; } else { - x1 = vertices[verticesLength - 2]; - y1 = vertices[verticesLength - 1]; - x2 = x1 - vertices[verticesLength - 4]; - y2 = y1 - vertices[verticesLength - 3]; - position -= pathLength; + verticesStart += verticesLength; + x1 = temp[verticesStart - 2]; + y1 = temp[verticesStart - 1]; + x2 = x1 - temp[verticesStart - 4]; + y2 = y1 - temp[verticesStart - 3]; + p -= pathLength; } float r = atan2(y2, x2); - out.add(x1 + position * cos(r)); - out.add(y1 + position * sin(r)); + out.add(x1 + p * cos(r)); + out.add(y1 + p * sin(r)); if (tangents) out.add(r + PI); } - private void addCurvePoint (float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, float position, - boolean tangents, FloatArray out) { - if (position == 0) position = 0.0001f; - float tt = position * position, ttt = tt * position, u = 1 - position, uu = u * u, uuu = uu * u; - float ut = u * position, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * position; + private void addCurvePosition (float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, + FloatArray out, 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.add(x); out.add(y); From 62ae3c83b5daab3d9f65202c301e50fd117d856f Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Tue, 31 May 2016 01:46:38 +0200 Subject: [PATCH 15/45] Optimizations. --- .../spine/PathConstraint.java | 65 ++++++++++--------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java index 83a3c2601..f12f3c50c 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java @@ -142,6 +142,7 @@ public class PathConstraint implements Updatable { boolean closed = path.getClosed(); int verticesLength = path.getWorldVerticesLength(), curves = verticesLength / 6; float[] temp; + int lastCurve = -1; if (!path.getConstantSpeed()) { if (!closed) curves--; @@ -152,18 +153,30 @@ public class PathConstraint implements Updatable { if (closed) { position %= 1; if (position < 0) position += 1; - } else if (position < 0 || position > 1) { - path.computeWorldVertices(target, 0, 4, temp, 0); - path.computeWorldVertices(target, verticesLength - 4, 4, temp, 4); - addOutsidePosition(position * pathLength, temp, 0, 8, pathLength, positions, tangents); + } else if (position < 0) { + if (lastCurve != -2) { + lastCurve = -2; + path.computeWorldVertices(target, 2, 4, temp, 0); + } + addBeforePosition(position * pathLength, temp, 0, positions, tangents); + continue; + } else if (position > 1) { + if (lastCurve != -3) { + lastCurve = -3; + path.computeWorldVertices(target, verticesLength - 6, 4, temp, 0); + } + addAfterPosition((position - 1) * pathLength, temp, 0, positions, tangents); continue; } int curve = position < 1 ? (int)(curves * position) : curves - 1; - if (closed && curve == curves - 1) { - path.computeWorldVertices(target, verticesLength - 4, 4, temp, 0); - path.computeWorldVertices(target, 0, 4, temp, 4); - } else - path.computeWorldVertices(target, curve * 6 + 2, 8, temp, 0); + if (curve != lastCurve) { + lastCurve = curve; + if (closed && curve == curves - 1) { + path.computeWorldVertices(target, verticesLength - 4, 4, temp, 0); + path.computeWorldVertices(target, 0, 4, temp, 4); + } else + path.computeWorldVertices(target, curve * 6 + 2, 8, temp, 0); + } addCurvePosition((position - curve / (float)curves) * curves, temp[0], temp[1], temp[2], temp[3], temp[4], temp[5], temp[6], temp[7], positions, tangents); } @@ -223,7 +236,6 @@ public class PathConstraint implements Updatable { } position *= pathLength; - int lastCurve = 0; float curveLength = 0; for (int i = 0; i < lengthCount; i++) { position += lengths[i]; @@ -232,8 +244,11 @@ public class PathConstraint implements Updatable { if (closed) { p %= pathLength; if (p < 0) p += pathLength; - } else if (p < 0 || p > pathLength) { - addOutsidePosition(p, temp, verticesStart, verticesLength, pathLength, positions, tangents); + } else if (p < 0) { + addBeforePosition(p, temp, verticesStart, positions, tangents); + continue; + } else if (p > pathLength) { + addAfterPosition(p - pathLength, temp, verticesStart + verticesLength - 4, positions, tangents); continue; } @@ -316,23 +331,15 @@ public class PathConstraint implements Updatable { return positions.items; } - private void addOutsidePosition (float p, float[] temp, int verticesStart, int verticesLength, float pathLength, - FloatArray out, boolean tangents) { - float x1, y1, x2, y2; - if (p < 0) { - x1 = temp[verticesStart]; - y1 = temp[verticesStart + 1]; - x2 = temp[verticesStart + 2] - x1; - y2 = temp[verticesStart + 3] - y1; - } else { - verticesStart += verticesLength; - x1 = temp[verticesStart - 2]; - y1 = temp[verticesStart - 1]; - x2 = x1 - temp[verticesStart - 4]; - y2 = y1 - temp[verticesStart - 3]; - p -= pathLength; - } - float r = atan2(y2, x2); + private void addBeforePosition (float p, float[] temp, int i, FloatArray out, boolean tangents) { + float x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = atan2(dy, dx); + out.add(x1 + p * cos(r)); + out.add(y1 + p * sin(r)); + if (tangents) out.add(r + PI); + } + + private void addAfterPosition (float p, float[] temp, int i, FloatArray out, boolean tangents) { + float x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = atan2(dy, dx); out.add(x1 + p * cos(r)); out.add(y1 + p * sin(r)); if (tangents) out.add(r + PI); From de2b7674c46ee768f8cce477b443b5c4544d69ba Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Tue, 31 May 2016 17:52:43 +0200 Subject: [PATCH 16/45] Better use of constants, formatting. --- .../com/esotericsoftware/spine/Animation.java | 289 ++++++++---------- .../spine/SkeletonBinary.java | 12 +- .../esotericsoftware/spine/SkeletonJson.java | 13 +- 3 files changed, 147 insertions(+), 167 deletions(-) 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 4fc2aa7ad..4ae99a0b0 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -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."); @@ -211,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]; @@ -238,11 +239,12 @@ 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); @@ -262,10 +264,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) { @@ -274,8 +276,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) @@ -285,18 +287,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) @@ -306,16 +307,16 @@ 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) { @@ -332,10 +333,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) { @@ -344,22 +345,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; } } @@ -373,22 +373,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; } } @@ -402,38 +401,35 @@ 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) { @@ -450,12 +446,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) { @@ -463,23 +459,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; @@ -721,12 +717,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]; @@ -742,17 +737,16 @@ 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 index) { @@ -769,10 +763,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) { @@ -781,41 +775,34 @@ 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 index) { @@ -832,7 +819,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; @@ -846,43 +833,36 @@ 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_MIX] - scale) * percent - constraint.scaleMix) * alpha; - constraint.shearMix += (shear + (frames[frame + SHEAR_MIX] - shear) * percent - constraint.shearMix) * 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 PathConstraintTimeline extends CurveTimeline { - static private final int PREV_TIME = -5; - static private final int PREV_POSITION = -4; - static private final int PREV_ROTATE_MIX = -3; - static private final int PREV_TRANSLATE_MIX = -2; - static private final int PREV_SCALE_MIX = -1; - static private final int POSITION = 1; - static private final int ROTATE_MIX = 2; - static private final int TRANSLATE_MIX = 3; - static private final int SCALE_MIX = 4; + static public final int ENTRIES = 5; + static private final int PREV_TIME = -5, PREV_POSITION = -4, PREV_ROTATE = -3, PREV_TRANSLATE = -2, PREV_SCALE = -1; + static private final int POSITION = 1, ROTATE = 2, TRANSLATE = 3, SCALE = 4; int pathConstraintIndex; @@ -890,7 +870,7 @@ public class Animation { public PathConstraintTimeline (int frameCount) { super(frameCount); - frames = new float[frameCount * 5]; + frames = new float[frameCount * ENTRIES]; } public void setPathConstraintIndex (int index) { @@ -907,12 +887,12 @@ public class Animation { /** Sets the time, position, and mixes of the specified keyframe. */ public void setFrame (int frameIndex, float time, float position, float rotateMix, float translateMix, float scaleMix) { - frameIndex *= 5; + frameIndex *= ENTRIES; frames[frameIndex] = time; - frames[frameIndex + 1] = position; - frames[frameIndex + 2] = rotateMix; - frames[frameIndex + 3] = translateMix; - frames[frameIndex + 4] = scaleMix; + frames[frameIndex + POSITION] = position; + frames[frameIndex + ROTATE] = rotateMix; + frames[frameIndex + TRANSLATE] = translateMix; + frames[frameIndex + SCALE] = scaleMix; } public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha) { @@ -921,30 +901,29 @@ public class Animation { PathConstraint constraint = skeleton.pathConstraints.get(pathConstraintIndex); - if (time >= frames[frames.length - 5]) { // Time is after last frame. - int i = frames.length - 1; - constraint.position += (frames[i - 3] - constraint.position) * alpha; - constraint.rotateMix += (frames[i - 2] - constraint.rotateMix) * alpha; - constraint.translateMix += (frames[i - 1] - constraint.translateMix) * alpha; - constraint.scaleMix += (frames[i] - constraint.scaleMix) * alpha; + if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. + int i = frames.length; + constraint.position += (frames[i + PREV_POSITION] - constraint.position) * alpha; + 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; return; } // Interpolate between the previous frame and the current frame. - int frame = binarySearch(frames, time, 5); - float frameTime = frames[frame]; - float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime), 0, 1); - percent = getCurvePercent(frame / 5 - 1, percent); - + int frame = binarySearch(frames, time, ENTRIES); float position = frames[frame + PREV_POSITION]; - float rotate = frames[frame + PREV_ROTATE_MIX]; - float translate = frames[frame + PREV_TRANSLATE_MIX]; - float scale = frames[frame + PREV_SCALE_MIX]; + float rotate = frames[frame + PREV_ROTATE]; + float translate = frames[frame + PREV_TRANSLATE]; + float scale = frames[frame + PREV_SCALE]; + float frameTime = frames[frame]; + float percent = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + constraint.position += (position + (frames[frame + POSITION] - position) * percent - constraint.position) * alpha; - constraint.rotateMix += (rotate + (frames[frame + ROTATE_MIX] - rotate) * percent - constraint.rotateMix) * alpha; - constraint.translateMix += (translate + (frames[frame + TRANSLATE_MIX] - translate) * percent - constraint.translateMix) + 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.scaleMix += (scale + (frames[frame + SCALE] - scale) * percent - constraint.scaleMix) * alpha; } } } 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 1bddff0d3..a85630f9f 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java @@ -493,7 +493,7 @@ 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: @@ -523,7 +523,7 @@ 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: @@ -546,7 +546,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; } } @@ -564,7 +564,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) * IkConstraintTimeline.ENTRIES]); } // Transform constraint timelines. @@ -579,7 +579,7 @@ 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) * TransformConstraintTimeline.ENTRIES]); } // Path constraint timelines. @@ -594,7 +594,7 @@ 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) * PathConstraintTimeline.ENTRIES]); } // Deform timelines. 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 4c7ecf0c3..9e5f106a1 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -403,7 +403,7 @@ public class SkeletonJson { 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); @@ -437,7 +437,7 @@ public class SkeletonJson { 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; @@ -460,7 +460,7 @@ public class SkeletonJson { 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 + ")"); @@ -480,7 +480,7 @@ public class SkeletonJson { 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. @@ -496,7 +496,8 @@ public class SkeletonJson { 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]); } // Path constraint timelines. @@ -512,7 +513,7 @@ public class SkeletonJson { frameIndex++; } timelines.add(timeline); - duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() * 5 - 5]); + duration = Math.max(duration, timeline.getFrames()[(timeline.getFrameCount() - 1) * PathConstraintTimeline.ENTRIES]); } // Deform timelines. From bcee9ede8d6cb6b471f78ab835803e387b7b152a Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Tue, 31 May 2016 18:08:48 +0200 Subject: [PATCH 17/45] Improved parameter checking. --- .../com/esotericsoftware/spine/Animation.java | 32 ++++++++++++------- .../spine/AnimationStateData.java | 1 + .../src/com/esotericsoftware/spine/Bone.java | 1 + .../com/esotericsoftware/spine/BoneData.java | 1 + .../src/com/esotericsoftware/spine/Event.java | 1 + .../esotericsoftware/spine/IkConstraint.java | 12 ++++--- .../spine/IkConstraintData.java | 2 ++ .../spine/PathConstraint.java | 7 ++-- .../spine/PathConstraintData.java | 1 + .../com/esotericsoftware/spine/Skeleton.java | 6 +++- .../spine/SkeletonBinary.java | 1 + .../spine/SkeletonBounds.java | 2 ++ .../esotericsoftware/spine/SkeletonJson.java | 1 + .../src/com/esotericsoftware/spine/Slot.java | 1 + .../com/esotericsoftware/spine/SlotData.java | 1 + .../spine/TransformConstraint.java | 10 +++--- .../spine/TransformConstraintData.java | 3 ++ 17 files changed, 59 insertions(+), 24 deletions(-) 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 4ae99a0b0..2def4f12f 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -251,8 +251,9 @@ public class Animation { 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 () { @@ -319,8 +320,9 @@ public class Animation { 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 () { @@ -432,8 +434,9 @@ public class Animation { 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 () { @@ -503,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 () { @@ -660,8 +664,9 @@ public class Animation { 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 () { @@ -750,6 +755,7 @@ public class Animation { } public void setIkConstraintIndex (int index) { + if (index < 0) throw new IllegalArgumentException("index must be >= 0."); this.ikConstraintIndex = index; } @@ -806,6 +812,7 @@ public class Animation { } public void setTransformConstraintIndex (int index) { + if (index < 0) throw new IllegalArgumentException("index must be >= 0."); this.transformConstraintIndex = index; } @@ -874,6 +881,7 @@ public class Animation { } public void setPathConstraintIndex (int index) { + if (index < 0) throw new IllegalArgumentException("index must be >= 0."); this.pathConstraintIndex = index; } 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 c454e1177..a8ae92eae 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java @@ -64,6 +64,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; 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 ac8151445..821403b95 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/BoneData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/BoneData.java @@ -46,6 +46,7 @@ public class BoneData { /** @param parent May be null. */ 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; 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 d3710d08f..2c126492e 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java @@ -43,20 +43,22 @@ public class IkConstraint implements Updatable { int bendDirection; 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 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) 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 index f12f3c50c..7a59dab2c 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java @@ -16,6 +16,8 @@ public class PathConstraint implements Updatable { final FloatArray lengths = new FloatArray(), positions = new FloatArray(), temp = new FloatArray(); 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; position = data.position; rotateMix = data.rotateMix; @@ -31,6 +33,8 @@ public class PathConstraint implements Updatable { /** 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) @@ -84,8 +88,7 @@ public class PathConstraint implements Updatable { for (int i = 0; i < boneCount; i++) { Bone bone = bones.get(i); - float length = bone.data.length; - float x = length * bone.a, y = length * bone.c; + float length = bone.data.length, x = length * bone.a, y = length * bone.c; lengths.add((float)Math.sqrt(x * x + y * y)); } float[] positions = computeWorldPositions(path, false); diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java index c4e4d4f03..6957c3716 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java @@ -11,6 +11,7 @@ public class PathConstraintData { float offsetRotation; public PathConstraintData (String name) { + if (name == null) throw new IllegalArgumentException("name cannot be null."); this.name = name; } 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 32ff7a6da..7d17cc563 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java @@ -303,6 +303,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; } @@ -423,10 +424,12 @@ public class Skeleton { return null; } - /** Returns the axis aligned bounding box (AABB) of the region, mesh, and skinned mesh attachments for the current pose. + /** 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++) { @@ -458,6 +461,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 a85630f9f..dfaf9ee32 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java @@ -89,6 +89,7 @@ public class SkeletonBinary { } public SkeletonBinary (AttachmentLoader attachmentLoader) { + if (attachmentLoader == null) throw new IllegalArgumentException("attachmentLoader cannot be null."); this.attachmentLoader = attachmentLoader; } 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 07831f211..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; @@ -221,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/SkeletonJson.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java index 9e5f106a1..54dd11178 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -74,6 +74,7 @@ public class SkeletonJson { } public SkeletonJson (AttachmentLoader attachmentLoader) { + if (attachmentLoader == null) throw new IllegalArgumentException("attachmentLoader cannot be null."); this.attachmentLoader = attachmentLoader; } 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 cac1e1644..f93478018 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slot.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slot.java @@ -104,6 +104,7 @@ public class Slot { } public void setAttachmentVertices (FloatArray attachmentVertices) { + if (attachmentVertices == null) throw new IllegalArgumentException("attachmentVertices cannot be null."); this.attachmentVertices = attachmentVertices; } 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 1958251b3..eea1c6620 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SlotData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SlotData.java @@ -42,6 +42,7 @@ public class SlotData { BlendMode blendMode; 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; 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 171227ade..67858ab5e 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraint.java @@ -12,20 +12,22 @@ public class TransformConstraint implements Updatable { 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; rotateMix = data.rotateMix; translateMix = data.translateMix; scaleMix = data.scaleMix; shearMix = data.shearMix; - if (skeleton != null) { - bone = skeleton.findBone(data.bone.name); - target = skeleton.findBone(data.target.name); - } + bone = skeleton.findBone(data.bone.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.data.index); target = skeleton.bones.get(constraint.target.data.index); 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..4a20acd5b 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraintData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraintData.java @@ -8,6 +8,7 @@ public class TransformConstraintData { float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; public TransformConstraintData (String name) { + if (name == null) throw new IllegalArgumentException("name cannot be null."); this.name = name; } @@ -20,6 +21,7 @@ public class TransformConstraintData { } public void setBone (BoneData bone) { + if (bone == null) throw new IllegalArgumentException("bone cannot be null."); this.bone = bone; } @@ -28,6 +30,7 @@ public class TransformConstraintData { } public void setTarget (BoneData target) { + if (target == null) throw new IllegalArgumentException("target cannot be null."); this.target = target; } From a990d6dfddfe8eb1f92f41f660912d54add42460 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Wed, 1 Jun 2016 02:36:44 +0200 Subject: [PATCH 18/45] Clean up. --- .../src/com/esotericsoftware/spine/Animation.java | 2 +- .../src/com/esotericsoftware/spine/PathConstraint.java | 2 -- .../com/esotericsoftware/spine/SkeletonRendererDebug.java | 2 +- .../src/com/esotericsoftware/spine/TransformConstraint.java | 5 ++--- .../esotericsoftware/spine/attachments/MeshAttachment.java | 2 +- .../spine/attachments/RegionSequenceAttachment.java | 2 +- 6 files changed, 6 insertions(+), 9 deletions(-) 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 2def4f12f..1869d4746 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -266,7 +266,7 @@ public class Animation { /** Sets the time and angle of the specified keyframe. */ public void setFrame (int frameIndex, float time, float degrees) { - frameIndex >>= 1; + frameIndex <<= 1; frames[frameIndex] = time; frames[frameIndex + ROTATION] = degrees; } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java index 7a59dab2c..d392315c4 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java @@ -23,11 +23,9 @@ public class PathConstraint implements Updatable { rotateMix = data.rotateMix; translateMix = data.translateMix; scaleMix = data.scaleMix; - bones = new Array(data.bones.size); for (BoneData boneData : data.bones) bones.add(skeleton.findBone(boneData.name)); - target = skeleton.findSlot(data.target.name); } 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 253ad77f2..f53726a19 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRendererDebug.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRendererDebug.java @@ -137,7 +137,7 @@ public class SkeletonRendererDebug { } 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]; 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 67858ab5e..bf484eb36 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraint.java @@ -19,7 +19,6 @@ public class TransformConstraint implements Updatable { translateMix = data.translateMix; scaleMix = data.scaleMix; shearMix = data.shearMix; - bone = skeleton.findBone(data.bone.name); target = skeleton.findBone(data.target.name); } @@ -29,12 +28,12 @@ public class TransformConstraint implements Updatable { 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.data.index); - target = skeleton.bones.get(constraint.target.data.index); rotateMix = constraint.rotateMix; translateMix = constraint.translateMix; scaleMix = constraint.scaleMix; shearMix = constraint.shearMix; + bone = skeleton.bones.get(constraint.bone.data.index); + target = skeleton.bones.get(constraint.target.data.index); } public void apply () { 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 b9fdefa6b..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 @@ -72,7 +72,7 @@ public class MeshAttachment extends VertexAttachment { public void updateUVs () { float[] regionUVs = this.regionUVs; int verticesLength = regionUVs.length; - int worldVerticesLength = verticesLength / 2 * 5; + int worldVerticesLength = (verticesLength >> 1) * 5; if (worldVertices == null || worldVertices.length != worldVerticesLength) worldVertices = new float[worldVerticesLength]; float u, v, width, height; 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: From 86e9a75846f9c854319cc3149f925beb6b19a20b Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Wed, 1 Jun 2016 14:44:11 +0200 Subject: [PATCH 19/45] Improved updateLocalTransform. --- .../src/com/esotericsoftware/spine/Bone.java | 47 +++++++++++++------ 1 file changed, 32 insertions(+), 15 deletions(-) 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 a8ae92eae..5c6d714b2 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java @@ -209,25 +209,42 @@ public class Bone implements Updatable { * transform values may differ from the original values but are functionally the same. */ public void updateLocalTransform () { Bone parent = this.parent; - if (parent != null) { - float a = parent.a, b = parent.b, c = parent.c, d = parent.d; - float invDet = 1 / (a * d - b * c); - float x = worldX - parent.worldX, y = worldY - parent.worldY; - x = (x * d * invDet - y * b * invDet); - y = (y * a * invDet - x * c * invDet); - } - shearX = 0; - scaleX = (float)Math.sqrt(a * a + c * c); - if (scaleX > 0.00001f) { - float det = a * d - b * c; - shearY = atan2(a * b + c * d, det) * radDeg; - scaleY = det / scaleX; + 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; - scaleY = (float)Math.sqrt(b * b + d * d); - rotation = 90 - atan2(d, b) * radDeg; + rotation = 90 - atan2(rd, rb) * radDeg; } appliedRotation = rotation; appliedScaleX = scaleX; From 8538e793d6411da0dd4910ee8c286b4c9d9684be Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Wed, 1 Jun 2016 15:58:50 +0200 Subject: [PATCH 20/45] Clean up, convenience methods to get local rotation from world. --- .../src/com/esotericsoftware/spine/Bone.java | 146 ++++++++++-------- 1 file changed, 80 insertions(+), 66 deletions(-) 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 5c6d714b2..2f9d6e561 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java @@ -141,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; @@ -160,22 +160,22 @@ public class Bone implements Updatable { pc = 0; pd = 1; do { - float r = parent.appliedRotation, cos = cosDeg(r), sin = sinDeg(r); + float cos = cosDeg(parent.appliedRotation), sin = sinDeg(parent.appliedRotation); float psx = parent.appliedScaleX, psy = parent.appliedScaleY; - float za = cos * psx, zb = -sin * psy, zc = sin * psx, zd = cos * psy; + 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) 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; @@ -202,64 +202,6 @@ public class Bone implements Updatable { } } - /** 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; - appliedScaleX = scaleX; - appliedScaleY = scaleY; - } - - 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; - } - public void setToSetupPose () { BoneData data = this.data; x = data.x; @@ -406,6 +348,78 @@ 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; + appliedScaleX = scaleX; + appliedScaleY = scaleY; + } + public Matrix3 getWorldTransform (Matrix3 worldTransform) { if (worldTransform == null) throw new IllegalArgumentException("worldTransform cannot be null."); float[] val = worldTransform.val; From 5dcf6b3b023363b1b6cbe4742b2deee6ed553eb5 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Fri, 3 Jun 2016 21:46:02 +0200 Subject: [PATCH 21/45] Update cache now handles all constraint configurations. Can also apply constraints in a specific order, except for IK which must always be first. --- .../src/com/esotericsoftware/spine/Bone.java | 2 + .../esotericsoftware/spine/IkConstraint.java | 58 +++++--- .../com/esotericsoftware/spine/Skeleton.java | 140 +++++++++++++----- 3 files changed, 143 insertions(+), 57 deletions(-) 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 2f9d6e561..124a83ef0 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java @@ -50,6 +50,8 @@ public class Bone implements Updatable { float c, d, worldY; float worldSignX, worldSignY; + boolean sorted; + /** @param parent May be null. */ public Bone (BoneData data, Skeleton skeleton, Bone parent) { if (data == null) throw new IllegalArgumentException("data cannot be null."); 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 2c126492e..4b50b5055 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java @@ -42,6 +42,8 @@ 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."); @@ -135,6 +137,10 @@ public class IkConstraint implements Updatable { 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); + /* + * parents of this thing this thing (bone) ik constraint + */ + } /** Adjusts the parent and child bone rotations so the tip of the child is as close to the target position as possible. The @@ -142,7 +148,7 @@ public class IkConstraint implements Updatable { * @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; + float px = parent.x, py = parent.y, psx = parent.appliedScaleX, psy = parent.appliedScaleY, csx = child.appliedScaleX; int os1, os2, s2; if (psx < 0) { psx = -psx; @@ -156,25 +162,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) { @@ -184,18 +197,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; 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 7d17cc563..cdfe12376 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java @@ -140,47 +140,115 @@ public class Skeleton { /** Caches information about bones and constraints. Must be called if bones or constraints are added or removed. */ public void updateCache () { - Array bones = this.bones; Array updateCache = this.updateCache; - Array ikConstraints = this.ikConstraints; - Array transformConstraints = this.transformConstraints; - Array pathConstraints = this.pathConstraints; - int ikConstraintsCount = ikConstraints.size; updateCache.clear(); + Array bones = this.bones; + for (int i = 0, n = bones.size; i < n; i++) + bones.get(i).sorted = false; + + // IK first, in hierarchy depth order. + Array ikConstraints = 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); + + reset(target.children); + reset(parent.children); + constrained.peek().sorted = true; + } + + Array pathConstraints = this.pathConstraints; + for (int i = 0, n = pathConstraints.size; i < n; i++) { + PathConstraint constraint = pathConstraints.get(i); + + Bone target = constraint.target.bone; + sortBone(target); + + Array constrained = constraint.bones; + int boneCount = constrained.size; + for (int ii = 0; ii < boneCount; ii++) + sortBone(constrained.get(ii)); + + updateCache.add(constraint); + + resetChildren(constrained); + reset(target.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); + + Bone target = constraint.target; + sortBone(target); + + // BOZO - Update transform constraints to support multiple constrained bones. + // Array constrained = constraint.bones; + // int boneCount = constrained.size; + // for (int ii = 0; ii < boneCount; ii++) + // sortBone(constrained.get(ii)); + sortBone(constraint.bone); + + updateCache.add(constraint); + + // resetChildren(constrained); + reset(constraint.bone.children); // BOZO - Remove. + reset(target.children); + // for (int ii = 0; ii < boneCount; ii++) + // constrained.get(ii).sorted = true; + constraint.bone.sorted = true; // BOZO - Remove. + } + + for (int i = 0, n = bones.size; i < n; i++) + sortBone(bones.get(i)); + } + + 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 resetChildren (Array bones) { + for (int i = 0, n = bones.size; i < n; i++) + reset(bones.get(i).children); + } + + private void reset (Array bones) { 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; - } - } - } - - for (int i = 0, n = pathConstraints.size; i < n; i++) { - PathConstraint pathConstraint = pathConstraints.get(i); - // BOZO! - Fix update order for multiple bones. - Bone bone = pathConstraint.bones.peek(); - for (int ii = updateCache.size - 1; ii >= 0; ii--) { - if (updateCache.get(ii) == bone) { - updateCache.insert(ii + 1, pathConstraint); - break; - } - } - } - - for (int i = 0, n = transformConstraints.size; i < n; i++) { - TransformConstraint transformConstraint = transformConstraints.get(i); - Bone bone = transformConstraint.bone; - for (int ii = updateCache.size - 1; ii >= 0; ii--) { - if (updateCache.get(ii) == bone) { - updateCache.insert(ii + 1, transformConstraint); - break; - } - } + if (bone.sorted) reset(bone.children); + bone.sorted = false; } } From 99c41f7581c2e70a2f6ffe4cb4f6dd6fd1f75951 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Fri, 3 Jun 2016 22:14:05 +0200 Subject: [PATCH 22/45] Removed resetChildren. --- .../src/com/esotericsoftware/spine/Skeleton.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) 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 cdfe12376..964f02165 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java @@ -197,8 +197,9 @@ public class Skeleton { updateCache.add(constraint); - resetChildren(constrained); reset(target.children); + for (int ii = 0; ii < boneCount; ii++) + reset(constrained.get(ii).children); for (int ii = 0; ii < boneCount; ii++) constrained.get(ii).sorted = true; } @@ -219,7 +220,8 @@ public class Skeleton { updateCache.add(constraint); - // resetChildren(constrained); + // for (int ii = 0; ii < boneCount; ii++) + // reset(constrained.get(ii).children); reset(constraint.bone.children); // BOZO - Remove. reset(target.children); // for (int ii = 0; ii < boneCount; ii++) @@ -239,11 +241,6 @@ public class Skeleton { updateCache.add(bone); } - private void resetChildren (Array bones) { - for (int i = 0, n = bones.size; i < n; i++) - reset(bones.get(i).children); - } - private void reset (Array bones) { for (int i = 0, n = bones.size; i < n; i++) { Bone bone = bones.get(i); From b94e04a7a51ba1b08e46a6b9eea31c2477a712d9 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Fri, 3 Jun 2016 22:38:10 +0200 Subject: [PATCH 23/45] Don't change IK constraint indices. --- .../src/com/esotericsoftware/spine/Skeleton.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) 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 964f02165..b15e0f1d1 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java @@ -43,7 +43,7 @@ public class Skeleton { final Array bones; final Array slots; Array drawOrder; - final Array ikConstraints; + final Array ikConstraints, ikConstraintsSorted; final Array transformConstraints; final Array pathConstraints; final Array updateCache = new Array(); @@ -80,6 +80,7 @@ public class Skeleton { } ikConstraints = new Array(data.ikConstraints.size); + ikConstraintsSorted = new Array(ikConstraints.size); for (IkConstraintData ikConstraintData : data.ikConstraints) ikConstraints.add(new IkConstraint(ikConstraintData, this)); @@ -118,6 +119,7 @@ public class Skeleton { 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)); @@ -147,8 +149,10 @@ public class Skeleton { for (int i = 0, n = bones.size; i < n; i++) bones.get(i).sorted = false; - // IK first, in hierarchy depth order. - Array ikConstraints = this.ikConstraints; + // 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); From 7e7aaad550c94cca5a7bd0ff0411db29766f7797 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Sun, 5 Jun 2016 00:56:56 +0200 Subject: [PATCH 24/45] Clean up. --- .../com/esotericsoftware/spine/Skeleton.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) 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 b15e0f1d1..e02e72dc2 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java @@ -182,8 +182,8 @@ public class Skeleton { updateCache.add(constraint); - reset(target.children); - reset(parent.children); + sortReset(target.children); + sortReset(parent.children); constrained.peek().sorted = true; } @@ -201,9 +201,9 @@ public class Skeleton { updateCache.add(constraint); - reset(target.children); + sortReset(target.children); for (int ii = 0; ii < boneCount; ii++) - reset(constrained.get(ii).children); + sortReset(constrained.get(ii).children); for (int ii = 0; ii < boneCount; ii++) constrained.get(ii).sorted = true; } @@ -226,8 +226,8 @@ public class Skeleton { // for (int ii = 0; ii < boneCount; ii++) // reset(constrained.get(ii).children); - reset(constraint.bone.children); // BOZO - Remove. - reset(target.children); + sortReset(constraint.bone.children); // BOZO - Remove. + sortReset(target.children); // for (int ii = 0; ii < boneCount; ii++) // constrained.get(ii).sorted = true; constraint.bone.sorted = true; // BOZO - Remove. @@ -245,10 +245,10 @@ public class Skeleton { updateCache.add(bone); } - private void reset (Array bones) { + private void sortReset (Array bones) { for (int i = 0, n = bones.size; i < n; i++) { Bone bone = bones.get(i); - if (bone.sorted) reset(bone.children); + if (bone.sorted) sortReset(bone.children); bone.sorted = false; } } @@ -315,6 +315,10 @@ public class Skeleton { return bones; } + public Array getUpdateCache () { + return updateCache; + } + /** @return May return null. */ public Bone getRootBone () { if (bones.size == 0) return null; From 3d8b674444a9b74af86a76811536439cd4580c48 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Sun, 5 Jun 2016 00:57:49 +0200 Subject: [PATCH 25/45] Removed appliedScaleX/Y. These existed because TransformConstraint used to manipulate the local transform. Now that it manipulates the world transform, they aren't used. --- .../src/com/esotericsoftware/spine/Bone.java | 8 ++------ .../com/esotericsoftware/spine/IkConstraint.java | 15 +++++---------- 2 files changed, 7 insertions(+), 16 deletions(-) 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 124a83ef0..f0e61f622 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java @@ -44,7 +44,7 @@ public class Bone implements Updatable { 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; @@ -92,8 +92,6 @@ public class Bone implements Updatable { /** 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; @@ -163,7 +161,7 @@ public class Bone implements Updatable { pd = 1; do { float cos = cosDeg(parent.appliedRotation), sin = sinDeg(parent.appliedRotation); - float psx = parent.appliedScaleX, psy = parent.appliedScaleY; + 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 = pb * zd - pa * zb; @@ -418,8 +416,6 @@ public class Bone implements Updatable { rotation = 90 - atan2(rd, rb) * radDeg; } appliedRotation = rotation; - appliedScaleX = scaleX; - appliedScaleY = scaleY; } public Matrix3 getWorldTransform (Matrix3 worldTransform) { 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 4b50b5055..4b929b9f7 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java @@ -135,12 +135,8 @@ 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); - /* - * parents of this thing this thing (bone) ik constraint - */ - + 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 @@ -148,7 +144,7 @@ public class IkConstraint implements Updatable { * @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, csx = child.appliedScaleX; + 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; @@ -266,13 +262,12 @@ public class IkConstraint implements Updatable { if (a1 > 180) a1 -= 360; else if (a1 < -180) a1 += 360; - parent.updateWorldTransform(px, py, rotation + a1 * alpha, parent.appliedScaleX, parent.appliedScaleY, 0, 0); + 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; - child.updateWorldTransform(cx, cy, rotation + a2 * alpha, child.appliedScaleX, child.appliedScaleY, child.shearX, - child.shearY); + child.updateWorldTransform(cx, cy, rotation + a2 * alpha, child.scaleX, child.scaleY, child.shearX, child.shearY); } } From 633432647bb0ecb484d1e97293a2c130f5b6c616 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Sun, 5 Jun 2016 00:58:07 +0200 Subject: [PATCH 26/45] Clean up, optimization. --- .../spine/PathConstraint.java | 93 ++++++++++--------- .../spine/attachments/PathAttachment.java | 18 +++- 2 files changed, 61 insertions(+), 50 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java index d392315c4..5611adfa6 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java @@ -9,6 +9,10 @@ import com.esotericsoftware.spine.attachments.Attachment; import com.esotericsoftware.spine.attachments.PathAttachment; public class PathConstraint implements Updatable { + static private final int NONE = -1; + static private final int BEFORE = -2; + static private final int AFTER = -3; + final PathConstraintData data; final Array bones; Slot target; @@ -98,9 +102,12 @@ public class PathConstraint implements Updatable { bone.worldY += (boneY - bone.worldY) * translateMix; float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; if (scale) { - float s = ((float)Math.sqrt(dx * dx + dy * dy) / lengths.get(i + 1) - 1) * scaleMix + 1; - bone.a *= s; - bone.c *= s; + float length = lengths.get(i + 1); + if (length != 0) { + float s = ((float)Math.sqrt(dx * dx + dy * dy) / length - 1) * scaleMix + 1; + bone.a *= s; + bone.c *= s; + } } if (!rotate) { boneX = x; @@ -143,11 +150,11 @@ public class PathConstraint implements Updatable { boolean closed = path.getClosed(); int verticesLength = path.getWorldVerticesLength(), curves = verticesLength / 6; float[] temp; - int lastCurve = -1; + int lastCurve = NONE; if (!path.getConstantSpeed()) { if (!closed) curves--; - float pathLength = path.getLength(); + float pathLength = path.getTotalLength(); temp = this.temp.setSize(8); for (int i = 0; i < lengthCount; i++) { position += lengths[i] / pathLength; @@ -155,15 +162,15 @@ public class PathConstraint implements Updatable { position %= 1; if (position < 0) position += 1; } else if (position < 0) { - if (lastCurve != -2) { - lastCurve = -2; + if (lastCurve != BEFORE) { + lastCurve = BEFORE; path.computeWorldVertices(target, 2, 4, temp, 0); } addBeforePosition(position * pathLength, temp, 0, positions, tangents); continue; } else if (position > 1) { - if (lastCurve != -3) { - lastCurve = -3; + if (lastCurve != AFTER) { + lastCurve = AFTER; path.computeWorldVertices(target, verticesLength - 6, 4, temp, 0); } addAfterPosition((position - 1) * pathLength, temp, 0, positions, tangents); @@ -184,7 +191,7 @@ public class PathConstraint implements Updatable { return positions.items; } - // World vertices, verticesStart to verticesStart + verticesLength. + // World vertices, verticesStart to verticesStart + verticesLength - 1. int verticesStart = 10 + curves; temp = this.temp.setSize(verticesStart + verticesLength + 2); if (closed) { @@ -200,7 +207,7 @@ public class PathConstraint implements Updatable { path.computeWorldVertices(target, 2, verticesLength, temp, verticesStart); } - // Curve lengths, 10 to verticesStart. + // Curve lengths, 10 to verticesStart - 1. float pathLength = 0; float x1 = temp[verticesStart], y1 = temp[verticesStart + 1], cx1 = 0, cy1 = 0, cx2 = 0, cy2 = 0, x2 = 0, y2 = 0; float tmpx, tmpy, dddfx, dddfy, ddfx, ddfy, dfx, dfy; @@ -238,6 +245,7 @@ public class PathConstraint implements Updatable { position *= pathLength; float curveLength = 0; + int curve = 10, segment = 0; for (int i = 0; i < lengthCount; i++) { position += lengths[i]; float p = position; @@ -254,34 +262,30 @@ public class PathConstraint implements Updatable { } // Determine curve containing position. - int curve; - float length = temp[10]; - if (p <= length) { - curve = verticesStart; - p /= length; - } else { - for (curve = 11;; curve++) { - length = temp[curve]; - if (p <= length) { - float prev = temp[curve - 1]; - p = (p - prev) / (length - prev); - break; - } + for (;; curve++) { + float length = temp[curve]; + if (p > length) continue; + if (curve == 10) + p /= length; + else { + float prev = temp[curve - 1]; + p = (p - prev) / (length - prev); } - curve = verticesStart + (curve - 10) * 6; + break; } - // Curve segment lengths, 0 to 10. + // Curve segment lengths, 0 to 9. if (curve != lastCurve) { lastCurve = curve; - x1 = temp[curve]; - y1 = temp[curve + 1]; - cx1 = temp[curve + 2]; - cy1 = temp[curve + 3]; - cx2 = temp[curve + 4]; - cy2 = temp[curve + 5]; - x2 = temp[curve + 6]; - y2 = temp[curve + 7]; + int index = verticesStart + (curve - 10) * 6; + x1 = temp[index]; + y1 = temp[index + 1]; + cx1 = temp[index + 2]; + cy1 = temp[index + 3]; + cx2 = temp[index + 4]; + cy2 = temp[index + 5]; + x2 = temp[index + 6]; + y2 = temp[index + 7]; tmpx = (x1 - cx1 * 2 + cx2) * 0.03f; tmpy = (y1 - cy1 * 2 + cy2) * 0.03f; dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.006f; @@ -308,22 +312,21 @@ public class PathConstraint implements Updatable { dfy += ddfy + dddfy; curveLength += (float)Math.sqrt(dfx * dfx + dfy * dfy); temp[9] = curveLength; + segment = 0; } // Weight by segment length. p *= curveLength; - length = temp[0]; - if (p <= length) - p = 0.1f * p / length; - else { - for (int ii = 1;; ii++) { - length = temp[ii]; - if (p <= length) { - float prev = temp[ii - 1]; - p = 0.1f * (ii + (p - prev) / (length - prev)); - break; - } + for (;; segment++) { + float length = temp[segment]; + if (p > length) continue; + if (segment == 0) + p = 0.1f * p / length; + else { + float prev = temp[segment - 1]; + p = 0.1f * (segment + (p - prev) / (length - prev)); } + break; } addCurvePosition(p, x1, y1, cx1, cy1, cx2, cy2, x2, y2, positions, tangents); 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 index 4683221b4..c64ae8912 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java @@ -32,10 +32,12 @@ package com.esotericsoftware.spine.attachments; import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.utils.FloatArray; import com.esotericsoftware.spine.Slot; public class PathAttachment extends VertexAttachment { - float length; + float totalLength; + final FloatArray lengths = new FloatArray(); boolean closed, constantSpeed; // Nonessential. @@ -69,12 +71,18 @@ public class PathAttachment extends VertexAttachment { this.constantSpeed = constantSpeed; } - public float getLength () { - return length; + /** Returns the length of the path in the setup pose. */ + public float getTotalLength () { + return totalLength; } - public void setLength (float length) { - this.length = length; + public void setTotalLength (float totalLength) { + this.totalLength = totalLength; + } + + /** Returns the length of each curve in the setup pose. */ + public FloatArray getLengths () { + return lengths; } public Color getColor () { From 7729d423f7b0642adf165a02e3974efb57528c36 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Sun, 5 Jun 2016 01:36:32 +0200 Subject: [PATCH 27/45] Improved non-constant speed paths. --- .../spine/PathConstraint.java | 65 ++++++++++++------- .../spine/attachments/PathAttachment.java | 2 +- 2 files changed, 42 insertions(+), 25 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java index 5611adfa6..054931942 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java @@ -17,7 +17,8 @@ public class PathConstraint implements Updatable { final Array bones; Slot target; float position, rotateMix, translateMix, scaleMix; - final FloatArray lengths = new FloatArray(), positions = new FloatArray(), temp = new FloatArray(); + + final FloatArray spacing = new FloatArray(), positions = new FloatArray(), temp = new FloatArray(); public PathConstraint (PathConstraintData data, Skeleton skeleton) { if (data == null) throw new IllegalArgumentException("data cannot be null."); @@ -61,9 +62,9 @@ public class PathConstraint implements Updatable { if (!translate && !rotate && !scale) return; PathAttachment path = (PathAttachment)attachment; - FloatArray lengths = this.lengths; - lengths.clear(); - lengths.add(0); + FloatArray spacing = this.spacing; + spacing.clear(); + spacing.add(0); Array bones = this.bones; int boneCount = bones.size; @@ -91,7 +92,7 @@ public class PathConstraint implements Updatable { for (int i = 0; i < boneCount; i++) { Bone bone = bones.get(i); float length = bone.data.length, x = length * bone.a, y = length * bone.c; - lengths.add((float)Math.sqrt(x * x + y * y)); + spacing.add((float)Math.sqrt(x * x + y * y)); } float[] positions = computeWorldPositions(path, false); @@ -102,9 +103,9 @@ public class PathConstraint implements Updatable { bone.worldY += (boneY - bone.worldY) * translateMix; float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; if (scale) { - float length = lengths.get(i + 1); - if (length != 0) { - float s = ((float)Math.sqrt(dx * dx + dy * dy) / length - 1) * scaleMix + 1; + float space = spacing.get(i + 1); + if (space != 0) { + float s = ((float)Math.sqrt(dx * dx + dy * dy) / space - 1) * scaleMix + 1; bone.a *= s; bone.c *= s; } @@ -143,8 +144,8 @@ public class PathConstraint implements Updatable { private float[] computeWorldPositions (PathAttachment path, boolean tangents) { Slot target = this.target; float position = this.position; - int lengthCount = lengths.size; - float[] lengths = this.lengths.items; + int spacingCount = spacing.size; + float[] spacing = this.spacing.items; FloatArray positions = this.positions; positions.clear(); boolean closed = path.getClosed(); @@ -152,31 +153,49 @@ public class PathConstraint implements Updatable { float[] temp; int lastCurve = NONE; + // New. if (!path.getConstantSpeed()) { if (!closed) curves--; + float[] curveLengths = path.getCurveLengths().items; float pathLength = path.getTotalLength(); + position *= pathLength; temp = this.temp.setSize(8); - for (int i = 0; i < lengthCount; i++) { - position += lengths[i] / pathLength; + for (int i = 0, curve = 0; i < spacingCount; i++) { + position += spacing[i]; + float p = position; + if (closed) { - position %= 1; - if (position < 0) position += 1; - } else if (position < 0) { + p %= 1; + if (p < 0) p += 1; + } else if (p < 0) { if (lastCurve != BEFORE) { lastCurve = BEFORE; path.computeWorldVertices(target, 2, 4, temp, 0); } - addBeforePosition(position * pathLength, temp, 0, positions, tangents); + addBeforePosition(p, temp, 0, positions, tangents); continue; - } else if (position > 1) { + } else if (p > pathLength) { if (lastCurve != AFTER) { lastCurve = AFTER; path.computeWorldVertices(target, verticesLength - 6, 4, temp, 0); } - addAfterPosition((position - 1) * pathLength, temp, 0, positions, tangents); + addAfterPosition(p - pathLength, temp, 0, positions, tangents); continue; } - int curve = position < 1 ? (int)(curves * position) : curves - 1; + + // Determine curve containing position. + for (;; curve++) { + float length = curveLengths[curve]; + if (p > length) continue; + if (curve == 0) + p /= length; + else { + float prev = curveLengths[curve - 1]; + p = (p - prev) / (length - prev); + } + break; + } + if (curve != lastCurve) { lastCurve = curve; if (closed && curve == curves - 1) { @@ -185,8 +204,7 @@ public class PathConstraint implements Updatable { } else path.computeWorldVertices(target, curve * 6 + 2, 8, temp, 0); } - addCurvePosition((position - curve / (float)curves) * curves, temp[0], temp[1], temp[2], temp[3], temp[4], temp[5], - temp[6], temp[7], positions, tangents); + addCurvePosition(p, temp[0], temp[1], temp[2], temp[3], temp[4], temp[5], temp[6], temp[7], positions, tangents); } return positions.items; } @@ -245,9 +263,8 @@ public class PathConstraint implements Updatable { position *= pathLength; float curveLength = 0; - int curve = 10, segment = 0; - for (int i = 0; i < lengthCount; i++) { - position += lengths[i]; + for (int i = 0, curve = 10, segment = 0; i < spacingCount; i++) { + position += spacing[i]; float p = position; if (closed) { 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 index c64ae8912..843deb58f 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java @@ -81,7 +81,7 @@ public class PathAttachment extends VertexAttachment { } /** Returns the length of each curve in the setup pose. */ - public FloatArray getLengths () { + public FloatArray getCurveLengths () { return lengths; } From b3fdaca01961b8af469887bd23e406275de35dfb Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Sun, 5 Jun 2016 01:44:35 +0200 Subject: [PATCH 28/45] Fixed child IK bone not being updated when IK mix = 0. --- .../src/com/esotericsoftware/spine/IkConstraint.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 4b929b9f7..50dd12514 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java @@ -143,7 +143,10 @@ public class IkConstraint implements Updatable { * 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; + 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) { From 5cfa647ce29f65653b3e5fd34a8699d1038b1c2f Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Mon, 6 Jun 2016 11:30:50 +0200 Subject: [PATCH 29/45] Added path constraint spacing, spacing mode, rotate mode. --- .../com/esotericsoftware/spine/Animation.java | 11 +- .../spine/PathConstraint.java | 195 +++++++++++------- .../spine/PathConstraintData.java | 54 +++-- .../com/esotericsoftware/spine/Skeleton.java | 2 +- .../spine/SkeletonBinary.java | 9 +- .../esotericsoftware/spine/SkeletonJson.java | 8 +- 6 files changed, 171 insertions(+), 108 deletions(-) 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 1869d4746..ef51edb4b 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -866,10 +866,11 @@ public class Animation { } } + // BOZO! - Separate into multiple timelines. static public class PathConstraintTimeline extends CurveTimeline { static public final int ENTRIES = 5; - static private final int PREV_TIME = -5, PREV_POSITION = -4, PREV_ROTATE = -3, PREV_TRANSLATE = -2, PREV_SCALE = -1; - static private final int POSITION = 1, ROTATE = 2, TRANSLATE = 3, SCALE = 4; + static private final int PREV_TIME = -5, PREV_POSITION = -4, PREV_ROTATE = -3, PREV_TRANSLATE = -2; + static private final int POSITION = 1, ROTATE = 2, TRANSLATE = 3; int pathConstraintIndex; @@ -894,13 +895,12 @@ public class Animation { } /** Sets the time, position, and mixes of the specified keyframe. */ - public void setFrame (int frameIndex, float time, float position, float rotateMix, float translateMix, float scaleMix) { + public void setFrame (int frameIndex, float time, float position, float rotateMix, float translateMix) { frameIndex *= ENTRIES; frames[frameIndex] = time; frames[frameIndex + POSITION] = position; frames[frameIndex + ROTATE] = rotateMix; frames[frameIndex + TRANSLATE] = translateMix; - frames[frameIndex + SCALE] = scaleMix; } public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha) { @@ -914,7 +914,6 @@ public class Animation { constraint.position += (frames[i + PREV_POSITION] - constraint.position) * alpha; 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; return; } @@ -923,7 +922,6 @@ public class Animation { float position = frames[frame + PREV_POSITION]; float rotate = frames[frame + PREV_ROTATE]; float translate = frames[frame + PREV_TRANSLATE]; - float scale = frames[frame + PREV_SCALE]; float frameTime = frames[frame]; float percent = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); @@ -931,7 +929,6 @@ public class Animation { 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; } } } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java index 054931942..e32ebfa20 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java @@ -16,18 +16,18 @@ public class PathConstraint implements Updatable { final PathConstraintData data; final Array bones; Slot target; - float position, rotateMix, translateMix, scaleMix; + float position, spacing, rotateMix, translateMix; - final FloatArray spacing = new FloatArray(), positions = new FloatArray(), temp = new FloatArray(); + final FloatArray spaces = new FloatArray(), positions = new FloatArray(), temp = new FloatArray(); 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; position = data.position; + spacing = data.spacing; rotateMix = data.rotateMix; translateMix = data.translateMix; - scaleMix = data.scaleMix; bones = new Array(data.bones.size); for (BoneData boneData : data.bones) bones.add(skeleton.findBone(boneData.name)); @@ -44,9 +44,9 @@ public class PathConstraint implements Updatable { 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; - scaleMix = constraint.scaleMix; } public void apply () { @@ -57,19 +57,21 @@ public class PathConstraint implements Updatable { Attachment attachment = target.getAttachment(); if (!(attachment instanceof PathAttachment)) return; - float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix; - boolean translate = translateMix > 0, rotate = rotateMix > 0, scale = scaleMix > 0; - if (!translate && !rotate && !scale) return; + float rotateMix = this.rotateMix, translateMix = this.translateMix; + RotateMode rotateMode = data.rotateMode; + SpacingMode spacingMode = data.spacingMode; + boolean translate = translateMix > 0, rotate = rotateMix > 0; + if (!translate && !rotate) return; PathAttachment path = (PathAttachment)attachment; - FloatArray spacing = this.spacing; - spacing.clear(); - spacing.add(0); + FloatArray spacesArray = this.spaces; + spacesArray.clear(); + spacesArray.add(0); Array bones = this.bones; int boneCount = bones.size; if (boneCount == 1) { - float[] positions = computeWorldPositions(path, rotate); + float[] positions = computeWorldPositions(path, rotate, spacingMode == SpacingMode.percent); Bone bone = bones.first(); bone.worldX += (positions[0] - bone.worldX) * translateMix; bone.worldY += (positions[1] - bone.worldY) * translateMix; @@ -89,42 +91,59 @@ public class PathConstraint implements Updatable { return; } - for (int i = 0; i < boneCount; i++) { - Bone bone = bones.get(i); - float length = bone.data.length, x = length * bone.a, y = length * bone.c; - spacing.add((float)Math.sqrt(x * x + y * y)); + float[] spaces; + float spacing = this.spacing; + boolean scale = rotateMode == RotateMode.chainScale, lengthMode = spacingMode == SpacingMode.length; + if (!scale || lengthMode) { + spaces = spacesArray.setSize(1 + boneCount * 2); + for (int i = 0; i < boneCount;) { + Bone bone = bones.get(i++); + float length = bone.data.length, x = length * bone.a, y = length * bone.c; + length = (float)Math.sqrt(x * x + y * y); + spaces[i] = lengthMode ? Math.max(0, length + spacing) : spacing; + spaces[i + boneCount] = length; + } + } else { + spaces = spacesArray.setSize(1 + boneCount); + for (int i = 1; i <= boneCount; i++) + spaces[i] = spacing; } - float[] positions = computeWorldPositions(path, false); + + boolean tangents = rotateMode == RotateMode.tangent; + float[] positions = computeWorldPositions(path, tangents, spacingMode == SpacingMode.percent); float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation; - for (int i = 0, p = 2; i < boneCount; i++, p += 2) { - Bone bone = bones.get(i); + boolean tip = rotateMode == RotateMode.chain && offsetRotation == 0; + for (int i = 0, p = 3; i < boneCount; p += 3) { + Bone bone = bones.get(i++); bone.worldX += (boneX - bone.worldX) * translateMix; bone.worldY += (boneY - bone.worldY) * translateMix; float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; if (scale) { - float space = spacing.get(i + 1); + float space = spaces[i + boneCount]; if (space != 0) { - float s = ((float)Math.sqrt(dx * dx + dy * dy) / space - 1) * scaleMix + 1; + float s = ((float)Math.sqrt(dx * dx + dy * dy) / space - 1) * rotateMix + 1; bone.a *= s; bone.c *= s; } } - if (!rotate) { - boneX = x; - boneY = y; - } else { - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float r = atan2(dy, dx) - atan2(c, a) + offsetRotation * degRad, cos, sin; - if (offsetRotation != 0) { - boneX = x; - boneY = y; - } else { // Mix between on path and at tip. + 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] == 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 = x + (length * (cos * a - sin * c) - dx) * rotateMix; - boneY = y + (length * (sin * a + cos * c) - dy) * rotateMix; + boneX += (length * (cos * a - sin * c) - dx) * rotateMix; + boneY += (length * (sin * a + cos * c) - dy) * rotateMix; } if (r > PI) r -= PI2; @@ -141,45 +160,46 @@ public class PathConstraint implements Updatable { } } - private float[] computeWorldPositions (PathAttachment path, boolean tangents) { + private float[] computeWorldPositions (PathAttachment path, boolean tangents, boolean percentSpacing) { Slot target = this.target; float position = this.position; - int spacingCount = spacing.size; - float[] spacing = this.spacing.items; - FloatArray positions = this.positions; - positions.clear(); + int verticesLength = path.getWorldVerticesLength(), curves = verticesLength / 6, lastCurve = NONE; + int spacesCount = spaces.size; + float[] spaces = this.spaces.items, out = this.positions.setSize(spacesCount * 3), temp; boolean closed = path.getClosed(); - int verticesLength = path.getWorldVerticesLength(), curves = verticesLength / 6; - float[] temp; - int lastCurve = NONE; - // New. if (!path.getConstantSpeed()) { - if (!closed) curves--; - float[] curveLengths = path.getCurveLengths().items; float pathLength = path.getTotalLength(); position *= pathLength; + if (percentSpacing) { + for (int i = 0; i < spacesCount; i++) + spaces[i] *= pathLength; + } + curves--; + float[] curveLengths = path.getCurveLengths().items; temp = this.temp.setSize(8); - for (int i = 0, curve = 0; i < spacingCount; i++) { - position += spacing[i]; + 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 %= 1; - if (p < 0) p += 1; + p %= pathLength; + if (p < 0) p += pathLength; + curve = 0; } else if (p < 0) { if (lastCurve != BEFORE) { lastCurve = BEFORE; path.computeWorldVertices(target, 2, 4, temp, 0); } - addBeforePosition(p, temp, 0, positions, tangents); + addBeforePosition(p, temp, 0, out, o); continue; } else if (p > pathLength) { if (lastCurve != AFTER) { lastCurve = AFTER; path.computeWorldVertices(target, verticesLength - 6, 4, temp, 0); } - addAfterPosition(p - pathLength, temp, 0, positions, tangents); + addAfterPosition(p - pathLength, temp, 0, out, o); continue; } @@ -198,15 +218,16 @@ public class PathConstraint implements Updatable { if (curve != lastCurve) { lastCurve = curve; - if (closed && curve == curves - 1) { + if (closed && curve == curves) { path.computeWorldVertices(target, verticesLength - 4, 4, temp, 0); path.computeWorldVertices(target, 0, 4, temp, 4); } else path.computeWorldVertices(target, curve * 6 + 2, 8, temp, 0); } - addCurvePosition(p, temp[0], temp[1], temp[2], temp[3], temp[4], temp[5], temp[6], temp[7], positions, tangents); + addCurvePosition(p, temp[0], temp[1], temp[2], temp[3], temp[4], temp[5], temp[6], temp[7], out, o, + tangents || (space == 0 && i > 0)); } - return positions.items; + return out; } // World vertices, verticesStart to verticesStart + verticesLength - 1. @@ -261,20 +282,26 @@ public class PathConstraint implements Updatable { y1 = y2; } position *= pathLength; + if (percentSpacing) { + for (int i = 0; i < spacesCount; i++) + spaces[i] *= pathLength; + } float curveLength = 0; - for (int i = 0, curve = 10, segment = 0; i < spacingCount; i++) { - position += spacing[i]; + for (int i = 0, o = 0, curve = 10, 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 = 10; } else if (p < 0) { - addBeforePosition(p, temp, verticesStart, positions, tangents); + addBeforePosition(p, temp, verticesStart, out, o); continue; } else if (p > pathLength) { - addAfterPosition(p - pathLength, temp, verticesStart + verticesLength - 4, positions, tangents); + addAfterPosition(p - pathLength, temp, verticesStart + verticesLength - 4, out, o); continue; } @@ -346,35 +373,35 @@ public class PathConstraint implements Updatable { break; } - addCurvePosition(p, x1, y1, cx1, cy1, cx2, cy2, x2, y2, positions, tangents); + addCurvePosition(p, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, tangents || (space == 0 && i > 0)); } - return positions.items; + return out; } - private void addBeforePosition (float p, float[] temp, int i, FloatArray out, boolean tangents) { + 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.add(x1 + p * cos(r)); - out.add(y1 + p * sin(r)); - if (tangents) out.add(r + PI); + 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, FloatArray out, boolean tangents) { + 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.add(x1 + p * cos(r)); - out.add(y1 + p * sin(r)); - if (tangents) out.add(r + PI); + 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, - FloatArray out, boolean tangents) { + 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.add(x); - out.add(y); - if (tangents) out.add(atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt))); + 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 () { @@ -385,6 +412,14 @@ public class PathConstraint implements Updatable { this.position = position; } + public float getSpacing () { + return spacing; + } + + public void setSpacing (float spacing) { + this.spacing = spacing; + } + public float getRotateMix () { return rotateMix; } @@ -401,14 +436,6 @@ public class PathConstraint implements Updatable { this.translateMix = translateMix; } - public float getScaleMix () { - return scaleMix; - } - - public void setScaleMix (float scaleMix) { - this.scaleMix = scaleMix; - } - public Array getBones () { return bones; } @@ -428,4 +455,16 @@ public class PathConstraint implements Updatable { public String toString () { return data.name; } + + static public enum RotateMode { + tangent, chain, chainScale; + + static public final RotateMode[] values = RotateMode.values(); + } + + static public enum SpacingMode { + length, fixed, percent; + + static public final SpacingMode[] values = SpacingMode.values(); + } } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java index 6957c3716..870ce8ead 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java @@ -2,13 +2,17 @@ package com.esotericsoftware.spine; import com.badlogic.gdx.utils.Array; +import com.esotericsoftware.spine.PathConstraint.RotateMode; +import com.esotericsoftware.spine.PathConstraint.SpacingMode; public class PathConstraintData { final String name; final Array bones = new Array(); SlotData target; - float position, rotateMix, translateMix, scaleMix; + 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."); @@ -27,6 +31,30 @@ public class PathConstraintData { this.target = target; } + 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; } @@ -35,6 +63,14 @@ public class PathConstraintData { this.position = position; } + public float getSpacing () { + return spacing; + } + + public void setSpacing (float spacing) { + this.spacing = spacing; + } + public float getRotateMix () { return rotateMix; } @@ -51,22 +87,6 @@ public class PathConstraintData { this.translateMix = translateMix; } - public float getScaleMix () { - return scaleMix; - } - - public void setScaleMix (float scaleMix) { - this.scaleMix = scaleMix; - } - - public float getOffsetRotation () { - return offsetRotation; - } - - public void setOffsetRotation (float offsetRotation) { - this.offsetRotation = offsetRotation; - } - public String getName () { return name; } 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 e02e72dc2..859ab69d2 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java @@ -294,9 +294,9 @@ public class Skeleton { 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; - constraint.scaleMix = data.scaleMix; } } 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 dfaf9ee32..8abc8ca03 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java @@ -55,6 +55,8 @@ 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.PathConstraint.RotateMode; +import com.esotericsoftware.spine.PathConstraint.SpacingMode; import com.esotericsoftware.spine.SkeletonJson.LinkedMesh; import com.esotericsoftware.spine.attachments.AtlasAttachmentLoader; import com.esotericsoftware.spine.attachments.Attachment; @@ -216,9 +218,11 @@ public class SkeletonBinary { data.target = skeletonData.slots.get(input.readInt(true)); data.offsetRotation = input.readFloat(); data.position = input.readFloat(); + data.spacing = input.readFloat(); + data.spacingMode = SpacingMode.values[input.readInt(true)]; + data.rotateMode = RotateMode.values[input.readInt(true)]; data.rotateMix = input.readFloat(); data.translateMix = input.readFloat(); - data.scaleMix = input.readFloat(); skeletonData.pathConstraints.add(data); } @@ -590,8 +594,7 @@ public class SkeletonBinary { PathConstraintTimeline timeline = new PathConstraintTimeline(frameCount); timeline.pathConstraintIndex = index; for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.setFrame(frameIndex, input.readFloat(), input.readFloat(), input.readFloat(), input.readFloat(), - input.readFloat()); + timeline.setFrame(frameIndex, input.readFloat(), input.readFloat(), input.readFloat(), input.readFloat()); if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline); } timelines.add(timeline); 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 54dd11178..d431c7eeb 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -54,6 +54,8 @@ 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.PathConstraint.RotateMode; +import com.esotericsoftware.spine.PathConstraint.SpacingMode; import com.esotericsoftware.spine.attachments.AtlasAttachmentLoader; import com.esotericsoftware.spine.attachments.Attachment; import com.esotericsoftware.spine.attachments.AttachmentLoader; @@ -198,9 +200,11 @@ public class SkeletonJson { data.offsetRotation = constraintMap.getFloat("rotation", 0); data.position = constraintMap.getFloat("position", 0); + data.spacing = constraintMap.getFloat("spacing", 0); + data.spacingMode = SpacingMode.valueOf(constraintMap.getString("spacingMode", "length")); + data.rotateMode = RotateMode.valueOf(constraintMap.getString("rotateMode", "tangent")); data.rotateMix = constraintMap.getFloat("rotateMix", 1); data.translateMix = constraintMap.getFloat("translateMix", 1); - data.scaleMix = constraintMap.getFloat("scaleMix", 1); skeletonData.pathConstraints.add(data); } @@ -509,7 +513,7 @@ public class SkeletonJson { int frameIndex = 0; for (JsonValue valueMap = constraintMap.child; valueMap != null; valueMap = valueMap.next) { timeline.setFrame(frameIndex, valueMap.getFloat("time"), valueMap.getFloat("scaleMix", 1), - valueMap.getFloat("rotateMix", 1), valueMap.getFloat("translateMix", 1), valueMap.getFloat("scaleMix", 1)); + valueMap.getFloat("rotateMix", 1), valueMap.getFloat("translateMix", 1)); readCurve(valueMap, timeline, frameIndex); frameIndex++; } From 932808bd0647432d83be5c70dc31d40a2c526ce4 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Mon, 6 Jun 2016 11:52:49 +0200 Subject: [PATCH 30/45] Minor improvements. Think she's done! Just need 3 path timelines. --- .../spine/PathConstraint.java | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java index e32ebfa20..3ac1da1e7 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java @@ -18,7 +18,7 @@ public class PathConstraint implements Updatable { Slot target; float position, spacing, rotateMix, translateMix; - final FloatArray spaces = new FloatArray(), positions = new FloatArray(), temp = new FloatArray(); + FloatArray spaces = new FloatArray(), positions = new FloatArray(), temp = new FloatArray(); public PathConstraint (PathConstraintData data, Skeleton skeleton) { if (data == null) throw new IllegalArgumentException("data cannot be null."); @@ -58,12 +58,19 @@ public class PathConstraint implements Updatable { if (!(attachment instanceof PathAttachment)) return; float rotateMix = this.rotateMix, translateMix = this.translateMix; - RotateMode rotateMode = data.rotateMode; - SpacingMode spacingMode = data.spacingMode; boolean translate = translateMix > 0, rotate = rotateMix > 0; if (!translate && !rotate) return; + // BOZO! + spaces = new FloatArray(); + positions = new FloatArray(); + temp = new FloatArray(); + PathAttachment path = (PathAttachment)attachment; + PathConstraintData data = this.data; + SpacingMode spacingMode = data.spacingMode; + RotateMode rotateMode = data.rotateMode; + float offsetRotation = data.offsetRotation; FloatArray spacesArray = this.spaces; spacesArray.clear(); spacesArray.add(0); @@ -77,7 +84,7 @@ public class PathConstraint implements Updatable { bone.worldY += (positions[1] - bone.worldY) * translateMix; if (rotate) { float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float r = positions[2] - atan2(c, a) + data.offsetRotation * degRad; + float r = positions[2] - atan2(c, a) + offsetRotation * degRad; if (r > PI) r -= PI2; else if (r < -PI) r += PI2; @@ -94,7 +101,7 @@ public class PathConstraint implements Updatable { float[] spaces; float spacing = this.spacing; boolean scale = rotateMode == RotateMode.chainScale, lengthMode = spacingMode == SpacingMode.length; - if (!scale || lengthMode) { + if (scale || lengthMode) { spaces = spacesArray.setSize(1 + boneCount * 2); for (int i = 0; i < boneCount;) { Bone bone = bones.get(i++); @@ -112,7 +119,7 @@ public class PathConstraint implements Updatable { boolean tangents = rotateMode == RotateMode.tangent; float[] positions = computeWorldPositions(path, tangents, spacingMode == SpacingMode.percent); - float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation; + float boneX = positions[0], boneY = positions[1]; boolean tip = rotateMode == RotateMode.chain && offsetRotation == 0; for (int i = 0, p = 3; i < boneCount; p += 3) { Bone bone = bones.get(i++); @@ -321,15 +328,15 @@ public class PathConstraint implements Updatable { // Curve segment lengths, 0 to 9. if (curve != lastCurve) { lastCurve = curve; - int index = verticesStart + (curve - 10) * 6; - x1 = temp[index]; - y1 = temp[index + 1]; - cx1 = temp[index + 2]; - cy1 = temp[index + 3]; - cx2 = temp[index + 4]; - cy2 = temp[index + 5]; - x2 = temp[index + 6]; - y2 = temp[index + 7]; + int ii = verticesStart + (curve - 10) * 6; + x1 = temp[ii]; + y1 = temp[ii + 1]; + cx1 = temp[ii + 2]; + cy1 = temp[ii + 3]; + cx2 = temp[ii + 4]; + cy2 = temp[ii + 5]; + x2 = temp[ii + 6]; + y2 = temp[ii + 7]; tmpx = (x1 - cx1 * 2 + cx2) * 0.03f; tmpy = (y1 - cy1 * 2 + cy2) * 0.03f; dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.006f; @@ -340,7 +347,7 @@ public class PathConstraint implements Updatable { dfy = (cy1 - y1) * 0.3f + tmpy + dddfy * 0.16666667f; curveLength = (float)Math.sqrt(dfx * dfx + dfy * dfy); temp[0] = curveLength; - for (int ii = 1; ii < 8; ii++) { + for (ii = 1; ii < 8; ii++) { dfx += ddfx; dfy += ddfy; ddfx += dddfx; @@ -365,17 +372,16 @@ public class PathConstraint implements Updatable { float length = temp[segment]; if (p > length) continue; if (segment == 0) - p = 0.1f * p / length; + p /= length; else { float prev = temp[segment - 1]; - p = 0.1f * (segment + (p - prev) / (length - prev)); + p = segment + (p - prev) / (length - prev); } break; } - addCurvePosition(p, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, tangents || (space == 0 && i > 0)); + addCurvePosition(p * 0.1f, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, tangents || (space == 0 && i > 0)); } - return out; } From 2bb702e2bee8c3e3f59e091863f0be51a58f04e7 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Mon, 6 Jun 2016 12:38:01 +0200 Subject: [PATCH 31/45] BOZO removal. --- .../src/com/esotericsoftware/spine/PathConstraint.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java index 3ac1da1e7..e9ff8b89c 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java @@ -61,11 +61,6 @@ public class PathConstraint implements Updatable { boolean translate = translateMix > 0, rotate = rotateMix > 0; if (!translate && !rotate) return; - // BOZO! - spaces = new FloatArray(); - positions = new FloatArray(); - temp = new FloatArray(); - PathAttachment path = (PathAttachment)attachment; PathConstraintData data = this.data; SpacingMode spacingMode = data.spacingMode; From b41dead26330c849124d1298de91a94950af6c15 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Mon, 6 Jun 2016 17:29:44 +0200 Subject: [PATCH 32/45] Path constraint refactoring + position mode. --- .../spine/PathConstraint.java | 212 ++++++++---------- .../spine/PathConstraintData.java | 29 ++- .../spine/SkeletonBinary.java | 4 +- .../esotericsoftware/spine/SkeletonJson.java | 4 +- 4 files changed, 120 insertions(+), 129 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java index e9ff8b89c..dd30a896f 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java @@ -5,20 +5,23 @@ 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; - static private final int BEFORE = -2; - static private final int AFTER = -3; + static private final int NONE = -1, BEFORE = -2, AFTER = -3; final PathConstraintData data; final Array bones; Slot target; float position, spacing, rotateMix, translateMix; - FloatArray spaces = new FloatArray(), positions = new FloatArray(), temp = new FloatArray(); + final FloatArray spaces = new FloatArray(), positions = new FloatArray(); + final FloatArray world = new FloatArray(), curves = new FloatArray(), lengths = new FloatArray(); + final float[] segments = new float[10]; public PathConstraint (PathConstraintData data, Skeleton skeleton) { if (data == null) throw new IllegalArgumentException("data cannot be null."); @@ -53,6 +56,7 @@ public class PathConstraint implements Updatable { update(); } + @SuppressWarnings("null") public void update () { Attachment attachment = target.getAttachment(); if (!(attachment instanceof PathAttachment)) return; @@ -61,70 +65,43 @@ public class PathConstraint implements Updatable { boolean translate = translateMix > 0, rotate = rotateMix > 0; if (!translate && !rotate) return; - PathAttachment path = (PathAttachment)attachment; PathConstraintData data = this.data; + Object[] bones = this.bones.items; + int boneCount = this.bones.size; SpacingMode spacingMode = data.spacingMode; RotateMode rotateMode = data.rotateMode; - float offsetRotation = data.offsetRotation; - FloatArray spacesArray = this.spaces; - spacesArray.clear(); - spacesArray.add(0); - - Array bones = this.bones; - int boneCount = bones.size; - if (boneCount == 1) { - float[] positions = computeWorldPositions(path, rotate, spacingMode == SpacingMode.percent); - Bone bone = bones.first(); - bone.worldX += (positions[0] - bone.worldX) * translateMix; - bone.worldY += (positions[1] - bone.worldY) * translateMix; - if (rotate) { - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float r = positions[2] - 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; - } - return; - } - - float[] spaces; + boolean tangents = rotateMode == RotateMode.tangent, scale = rotateMode == RotateMode.chainScale; + boolean lengthSpacing = spacingMode == SpacingMode.length; + int spacesCount = tangents ? boneCount : boneCount + 1; + float[] spaces = this.spaces.setSize(spacesCount), lengths = null; float spacing = this.spacing; - boolean scale = rotateMode == RotateMode.chainScale, lengthMode = spacingMode == SpacingMode.length; - if (scale || lengthMode) { - spaces = spacesArray.setSize(1 + boneCount * 2); + if (scale || lengthSpacing) { + if (scale) lengths = this.lengths.setSize(boneCount); for (int i = 0; i < boneCount;) { - Bone bone = bones.get(i++); + 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); - spaces[i] = lengthMode ? Math.max(0, length + spacing) : spacing; - spaces[i + boneCount] = length; + if (scale) lengths[i] = length; + spaces[++i] = lengthSpacing ? Math.max(0, length + spacing) : spacing; } } else { - spaces = spacesArray.setSize(1 + boneCount); - for (int i = 1; i <= boneCount; i++) + for (int i = 1; i <= spacesCount; i++) spaces[i] = spacing; } - boolean tangents = rotateMode == RotateMode.tangent; - float[] positions = computeWorldPositions(path, tangents, spacingMode == SpacingMode.percent); - - float boneX = positions[0], boneY = positions[1]; + float[] positions = computeWorldPositions((PathAttachment)attachment, spacesCount, tangents, + data.positionMode == PositionMode.percent, spacingMode == SpacingMode.percent); + 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; p += 3) { - Bone bone = bones.get(i++); + for (int i = 0, p = 3; i < boneCount; i++, p += 3) { + Bone bone = (Bone)bones[i]; bone.worldX += (boneX - bone.worldX) * translateMix; bone.worldY += (boneY - bone.worldY) * translateMix; float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; if (scale) { - float space = spaces[i + boneCount]; - if (space != 0) { - float s = ((float)Math.sqrt(dx * dx + dy * dy) / space - 1) * rotateMix + 1; + 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; } @@ -135,7 +112,7 @@ public class PathConstraint implements Updatable { 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] == 0) + else if (spaces[i + 1] == 0) r = positions[p + 2]; else r = atan2(dy, dx); @@ -162,24 +139,24 @@ public class PathConstraint implements Updatable { } } - private float[] computeWorldPositions (PathAttachment path, boolean tangents, boolean percentSpacing) { + private float[] computeWorldPositions (PathAttachment path, int spacesCount, boolean tangents, boolean percentPosition, + boolean percentSpacing) { Slot target = this.target; float position = this.position; - int verticesLength = path.getWorldVerticesLength(), curves = verticesLength / 6, lastCurve = NONE; - int spacesCount = spaces.size; - float[] spaces = this.spaces.items, out = this.positions.setSize(spacesCount * 3), temp; + int verticesLength = path.getWorldVerticesLength(), curveCount = verticesLength / 6, lastCurve = NONE; + float[] spaces = this.spaces.items, out = this.positions.setSize(spacesCount * 3), world; boolean closed = path.getClosed(); if (!path.getConstantSpeed()) { float pathLength = path.getTotalLength(); - position *= pathLength; + if (percentPosition) position *= pathLength; if (percentSpacing) { for (int i = 0; i < spacesCount; i++) spaces[i] *= pathLength; } - curves--; + curveCount--; float[] curveLengths = path.getCurveLengths().items; - temp = this.temp.setSize(8); + world = this.world.setSize(8); for (int i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) { float space = spaces[i]; position += space; @@ -192,16 +169,16 @@ public class PathConstraint implements Updatable { } else if (p < 0) { if (lastCurve != BEFORE) { lastCurve = BEFORE; - path.computeWorldVertices(target, 2, 4, temp, 0); + path.computeWorldVertices(target, 2, 4, world, 0); } - addBeforePosition(p, temp, 0, out, o); + addBeforePosition(p, world, 0, out, o); continue; } else if (p > pathLength) { if (lastCurve != AFTER) { lastCurve = AFTER; - path.computeWorldVertices(target, verticesLength - 6, 4, temp, 0); + path.computeWorldVertices(target, verticesLength - 6, 4, world, 0); } - addAfterPosition(p - pathLength, temp, 0, out, o); + addAfterPosition(p - pathLength, world, 0, out, o); continue; } @@ -220,45 +197,45 @@ public class PathConstraint implements Updatable { if (curve != lastCurve) { lastCurve = curve; - if (closed && curve == curves) { - path.computeWorldVertices(target, verticesLength - 4, 4, temp, 0); - path.computeWorldVertices(target, 0, 4, temp, 4); + 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, temp, 0); + path.computeWorldVertices(target, curve * 6 + 2, 8, world, 0); } - addCurvePosition(p, temp[0], temp[1], temp[2], temp[3], temp[4], temp[5], temp[6], temp[7], out, o, + addCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], out, o, tangents || (space == 0 && i > 0)); } return out; } - // World vertices, verticesStart to verticesStart + verticesLength - 1. - int verticesStart = 10 + curves; - temp = this.temp.setSize(verticesStart + verticesLength + 2); + // World vertices. if (closed) { verticesLength += 2; - int verticesEnd = verticesStart + verticesLength; - path.computeWorldVertices(target, 2, verticesLength - 4, temp, verticesStart); - path.computeWorldVertices(target, 0, 2, temp, verticesEnd - 4); - temp[verticesEnd - 2] = temp[verticesStart]; - temp[verticesEnd - 1] = temp[verticesStart + 1]; + 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 { - verticesStart--; + curveCount--; verticesLength -= 4; - path.computeWorldVertices(target, 2, verticesLength, temp, verticesStart); + world = this.world.setSize(verticesLength); + path.computeWorldVertices(target, 2, verticesLength, world, 0); } - // Curve lengths, 10 to verticesStart - 1. + // Curve lengths. + float[] curves = this.curves.setSize(curveCount); float pathLength = 0; - float x1 = temp[verticesStart], y1 = temp[verticesStart + 1], cx1 = 0, cy1 = 0, cx2 = 0, cy2 = 0, x2 = 0, y2 = 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 = 10, v = verticesStart + 2; i < verticesStart; i++, v += 6) { - cx1 = temp[v]; - cy1 = temp[v + 1]; - cx2 = temp[v + 2]; - cy2 = temp[v + 3]; - x2 = temp[v + 4]; - y2 = temp[v + 5]; + 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; @@ -279,18 +256,19 @@ public class PathConstraint implements Updatable { dfx += ddfx + dddfx; dfy += ddfy + dddfy; pathLength += (float)Math.sqrt(dfx * dfx + dfy * dfy); - temp[i] = pathLength; + curves[i] = pathLength; x1 = x2; y1 = y2; } - position *= pathLength; + 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 = 10, segment = 0; i < spacesCount; i++, o += 3) { + for (int i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) { float space = spaces[i]; position += space; float p = position; @@ -298,23 +276,23 @@ public class PathConstraint implements Updatable { if (closed) { p %= pathLength; if (p < 0) p += pathLength; - curve = 10; + curve = 0; } else if (p < 0) { - addBeforePosition(p, temp, verticesStart, out, o); + addBeforePosition(p, world, 0, out, o); continue; } else if (p > pathLength) { - addAfterPosition(p - pathLength, temp, verticesStart + verticesLength - 4, out, o); + addAfterPosition(p - pathLength, world, verticesLength - 4, out, o); continue; } // Determine curve containing position. for (;; curve++) { - float length = temp[curve]; + float length = curves[curve]; if (p > length) continue; - if (curve == 10) + if (curve == 0) p /= length; else { - float prev = temp[curve - 1]; + float prev = curves[curve - 1]; p = (p - prev) / (length - prev); } break; @@ -323,15 +301,15 @@ public class PathConstraint implements Updatable { // Curve segment lengths, 0 to 9. if (curve != lastCurve) { lastCurve = curve; - int ii = verticesStart + (curve - 10) * 6; - x1 = temp[ii]; - y1 = temp[ii + 1]; - cx1 = temp[ii + 2]; - cy1 = temp[ii + 3]; - cx2 = temp[ii + 4]; - cy2 = temp[ii + 5]; - x2 = temp[ii + 6]; - y2 = temp[ii + 7]; + 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; @@ -341,35 +319,35 @@ public class PathConstraint implements Updatable { 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); - temp[0] = curveLength; + 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); - temp[ii] = curveLength; + segments[ii] = curveLength; } dfx += ddfx; dfy += ddfy; curveLength += (float)Math.sqrt(dfx * dfx + dfy * dfy); - temp[8] = curveLength; + segments[8] = curveLength; dfx += ddfx + dddfx; dfy += ddfy + dddfy; curveLength += (float)Math.sqrt(dfx * dfx + dfy * dfy); - temp[9] = curveLength; + segments[9] = curveLength; segment = 0; } // Weight by segment length. p *= curveLength; for (;; segment++) { - float length = temp[segment]; + float length = segments[segment]; if (p > length) continue; if (segment == 0) p /= length; else { - float prev = temp[segment - 1]; + float prev = segments[segment - 1]; p = segment + (p - prev) / (length - prev); } break; @@ -456,16 +434,4 @@ public class PathConstraint implements Updatable { public String toString () { return data.name; } - - static public enum RotateMode { - tangent, chain, chainScale; - - static public final RotateMode[] values = RotateMode.values(); - } - - static public enum SpacingMode { - length, fixed, percent; - - static public final SpacingMode[] values = SpacingMode.values(); - } } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java index 870ce8ead..c3be9bd2f 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java @@ -2,13 +2,12 @@ package com.esotericsoftware.spine; import com.badlogic.gdx.utils.Array; -import com.esotericsoftware.spine.PathConstraint.RotateMode; -import com.esotericsoftware.spine.PathConstraint.SpacingMode; public class PathConstraintData { final String name; final Array bones = new Array(); SlotData target; + PositionMode positionMode; SpacingMode spacingMode; RotateMode rotateMode; float offsetRotation; @@ -31,6 +30,14 @@ public class PathConstraintData { this.target = target; } + public PositionMode getPositionMode () { + return positionMode; + } + + public void setPositionMode (PositionMode positionMode) { + this.positionMode = positionMode; + } + public SpacingMode getSpacingMode () { return spacingMode; } @@ -94,4 +101,22 @@ public class PathConstraintData { 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/SkeletonBinary.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java index 8abc8ca03..4592e5ac6 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java @@ -55,8 +55,8 @@ 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.PathConstraint.RotateMode; -import com.esotericsoftware.spine.PathConstraint.SpacingMode; +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; 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 d431c7eeb..7c1177941 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -54,8 +54,8 @@ 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.PathConstraint.RotateMode; -import com.esotericsoftware.spine.PathConstraint.SpacingMode; +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; From b3f84390c5b869025e03d57b31bce385afbf487d Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Tue, 7 Jun 2016 13:15:08 +0200 Subject: [PATCH 33/45] Separate path timelines. --- .../com/esotericsoftware/spine/Animation.java | 104 +++++++++++++++--- .../spine/PathConstraint.java | 2 +- .../spine/SkeletonBinary.java | 75 +++++++++---- .../esotericsoftware/spine/SkeletonData.java | 11 +- .../esotericsoftware/spine/SkeletonJson.java | 55 ++++++--- 5 files changed, 193 insertions(+), 54 deletions(-) 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 ef51edb4b..baefe5c3f 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -866,17 +866,16 @@ public class Animation { } } - // BOZO! - Separate into multiple timelines. - static public class PathConstraintTimeline extends CurveTimeline { - static public final int ENTRIES = 5; - static private final int PREV_TIME = -5, PREV_POSITION = -4, PREV_ROTATE = -3, PREV_TRANSLATE = -2; - static private final int POSITION = 1, ROTATE = 2, TRANSLATE = 3; + 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; - private final float[] frames; // time, rotate mix, translate mix, scale mix, shear mix, ... + final float[] frames; // time, position, ... - public PathConstraintTimeline (int frameCount) { + public PathConstraintPositionTimeline (int frameCount) { super(frameCount); frames = new float[frameCount * ENTRIES]; } @@ -894,11 +893,93 @@ public class Animation { return frames; } - /** Sets the time, position, and mixes of the specified keyframe. */ - public void setFrame (int frameIndex, float time, float position, float rotateMix, float translateMix) { + /** 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 + POSITION] = position; frames[frameIndex + ROTATE] = rotateMix; frames[frameIndex + TRANSLATE] = translateMix; } @@ -911,7 +992,6 @@ public class Animation { if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. int i = frames.length; - constraint.position += (frames[i + PREV_POSITION] - constraint.position) * alpha; constraint.rotateMix += (frames[i + PREV_ROTATE] - constraint.rotateMix) * alpha; constraint.translateMix += (frames[i + PREV_TRANSLATE] - constraint.translateMix) * alpha; return; @@ -919,13 +999,11 @@ public class Animation { // Interpolate between the previous frame and the current frame. int frame = binarySearch(frames, time, ENTRIES); - float position = frames[frame + PREV_POSITION]; 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.position += (position + (frames[frame + POSITION] - position) * percent - constraint.position) * alpha; constraint.rotateMix += (rotate + (frames[frame + ROTATE] - rotate) * percent - constraint.rotateMix) * alpha; constraint.translateMix += (translate + (frames[frame + TRANSLATE] - translate) * percent - constraint.translateMix) * alpha; diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java index dd30a896f..ad84ed11c 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java @@ -144,7 +144,7 @@ public class PathConstraint implements Updatable { Slot target = this.target; float position = this.position; int verticesLength = path.getWorldVerticesLength(), curveCount = verticesLength / 6, lastCurve = NONE; - float[] spaces = this.spaces.items, out = this.positions.setSize(spacesCount * 3), world; + float[] spaces = this.spaces.items, out = this.positions.setSize(spacesCount * 3 + 2), world; boolean closed = path.getClosed(); if (!path.getConstantSpeed()) { 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 4592e5ac6..bc7b1de8c 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java @@ -48,7 +48,9 @@ import com.esotericsoftware.spine.Animation.DeformTimeline; import com.esotericsoftware.spine.Animation.DrawOrderTimeline; import com.esotericsoftware.spine.Animation.EventTimeline; import com.esotericsoftware.spine.Animation.IkConstraintTimeline; -import com.esotericsoftware.spine.Animation.PathConstraintTimeline; +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; @@ -69,12 +71,17 @@ import com.esotericsoftware.spine.attachments.RegionAttachment; 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; @@ -488,7 +495,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++) { @@ -501,7 +508,7 @@ public class SkeletonBinary { 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++) @@ -520,7 +527,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++) { @@ -531,14 +538,14 @@ public class SkeletonBinary { 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); @@ -590,15 +597,37 @@ public class SkeletonBinary { // Path constraint timelines. for (int i = 0, n = input.readInt(true); i < n; i++) { int index = input.readInt(true); - int frameCount = input.readInt(true); - PathConstraintTimeline timeline = new PathConstraintTimeline(frameCount); - timeline.pathConstraintIndex = index; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.setFrame(frameIndex, input.readFloat(), input.readFloat(), input.readFloat(), input.readFloat()); - if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline); + 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; + if (timelineType == PATH_SPACING) + timeline = new PathConstraintSpacingTimeline(frameCount); + else + timeline = new PathConstraintPositionTimeline(frameCount); + timeline.pathConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { + timeline.setFrame(frameIndex, input.readFloat(), input.readFloat()); + if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline); + } + timelines.add(timeline); + duration = Math.max(duration, timeline.getFrames()[(frameCount - 1) * PathConstraintPositionTimeline.ENTRIES]); + } + 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]); + } + } } - timelines.add(timeline); - duration = Math.max(duration, timeline.getFrames()[(frameCount - 1) * PathConstraintTimeline.ENTRIES]); } // Deform timelines. 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 c48c63b5c..9b90d213f 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonData.java @@ -90,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; @@ -206,6 +206,15 @@ public class SkeletonData { 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 7c1177941..bb5c9ec2e 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -47,7 +47,9 @@ import com.esotericsoftware.spine.Animation.DeformTimeline; import com.esotericsoftware.spine.Animation.DrawOrderTimeline; import com.esotericsoftware.spine.Animation.EventTimeline; import com.esotericsoftware.spine.Animation.IkConstraintTimeline; -import com.esotericsoftware.spine.Animation.PathConstraintTimeline; +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; @@ -280,7 +282,7 @@ public class SkeletonJson { String type = map.getString("type", AttachmentType.region.name()); - // Warning: These types are deprecated and will be removed in the near future. + // 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"; @@ -393,7 +395,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")) { @@ -428,7 +429,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")) { @@ -506,19 +506,42 @@ public class SkeletonJson { } // Path constraint timelines. - for (JsonValue constraintMap = map.getChild("path"); constraintMap != null; constraintMap = constraintMap.next) { - PathConstraintData constraint = skeletonData.findPathConstraint(constraintMap.name); - PathConstraintTimeline timeline = new PathConstraintTimeline(constraintMap.size); - timeline.pathConstraintIndex = skeletonData.getPathConstraints().indexOf(constraint, true); - int frameIndex = 0; - for (JsonValue valueMap = constraintMap.child; valueMap != null; valueMap = valueMap.next) { - timeline.setFrame(frameIndex, valueMap.getFloat("time"), valueMap.getFloat("scaleMix", 1), - valueMap.getFloat("rotateMix", 1), valueMap.getFloat("translateMix", 1)); - readCurve(valueMap, timeline, frameIndex); - frameIndex++; + 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); + for (JsonValue timelineMap = constraintMap.child; timelineMap != null; timelineMap = timelineMap.next) { + String timelineName = timelineMap.name; + if (timelineName.equals("position") || timelineName.equals("spacing")) { + PathConstraintPositionTimeline timeline; + if (timelineName.equals("spacing")) + timeline = new PathConstraintSpacingTimeline(timelineMap.size); + else + timeline = new PathConstraintPositionTimeline(timelineMap.size); + timeline.pathConstraintIndex = index; + int frameIndex = 0; + for (JsonValue valueMap = constraintMap.child; valueMap != null; valueMap = valueMap.next) { + timeline.setFrame(frameIndex, valueMap.getFloat("time"), valueMap.getFloat(timelineName, 1)); + 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 = constraintMap.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]); + } } - timelines.add(timeline); - duration = Math.max(duration, timeline.getFrames()[(timeline.getFrameCount() - 1) * PathConstraintTimeline.ENTRIES]); } // Deform timelines. From 3511d7664bf1a0f518582c6babae4a38c7f07d3f Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Wed, 8 Jun 2016 16:19:49 +0200 Subject: [PATCH 34/45] Fixed path constraint crash. --- .../esotericsoftware/spine/PathConstraint.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java index ad84ed11c..c5d5daf49 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java @@ -19,9 +19,9 @@ public class PathConstraint implements Updatable { Slot target; float position, spacing, rotateMix, translateMix; - final FloatArray spaces = new FloatArray(), positions = new FloatArray(); - final FloatArray world = new FloatArray(), curves = new FloatArray(), lengths = new FloatArray(); - final float[] segments = new float[10]; + 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."); @@ -67,17 +67,16 @@ public class PathConstraint implements Updatable { PathConstraintData data = this.data; Object[] bones = this.bones.items; - int boneCount = this.bones.size; SpacingMode spacingMode = data.spacingMode; RotateMode rotateMode = data.rotateMode; boolean tangents = rotateMode == RotateMode.tangent, scale = rotateMode == RotateMode.chainScale; boolean lengthSpacing = spacingMode == SpacingMode.length; - int spacesCount = tangents ? boneCount : boneCount + 1; + int boneCount = this.bones.size, spacesCount = tangents ? boneCount : boneCount + 1; 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; i < 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); @@ -85,7 +84,7 @@ public class PathConstraint implements Updatable { spaces[++i] = lengthSpacing ? Math.max(0, length + spacing) : spacing; } } else { - for (int i = 1; i <= spacesCount; i++) + for (int i = 1; i < spacesCount; i++) spaces[i] = spacing; } @@ -298,7 +297,7 @@ public class PathConstraint implements Updatable { break; } - // Curve segment lengths, 0 to 9. + // Curve segment lengths. if (curve != lastCurve) { lastCurve = curve; int ii = curve * 6; From a6ccff5fde164001be2ee443336f25ae3fc0f862 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Thu, 9 Jun 2016 03:33:14 +0200 Subject: [PATCH 35/45] Better path debug rendering. --- .../spine/SkeletonRendererDebug.java | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) 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 f53726a19..9aabd2662 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRendererDebug.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRendererDebug.java @@ -171,18 +171,32 @@ public class SkeletonRendererDebug { if (!(attachment instanceof PathAttachment)) continue; PathAttachment path = (PathAttachment)attachment; int nn = path.getWorldVerticesLength(); - float[] worldVertices = temp.setSize(nn); - path.computeWorldVertices(slot, worldVertices); - shapes.setColor(path.getColor()); - float x1 = worldVertices[0], y1 = worldVertices[1]; - float cx1 = x1 + (x1 - worldVertices[2]), cy1 = y1 + (y1 - worldVertices[3]); - for (int ii = 4; ii < nn; ii += 4) { - float x2 = worldVertices[ii], y2 = worldVertices[ii + 1], cx2 = worldVertices[ii + 2], cy2 = worldVertices[ii + 3]; + 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; - cx1 = x2 + (x2 - cx2); - cy1 = y2 + (y2 - cy2); } } } From 40950eded73c31b5dee7ccfcd8c47b2462a869b8 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Thu, 9 Jun 2016 03:33:48 +0200 Subject: [PATCH 36/45] JSON and binary path attachment and constraint loading. --- .../spine/PathConstraint.java | 13 +++-- .../com/esotericsoftware/spine/Skeleton.java | 8 ++- .../spine/SkeletonBinary.java | 38 ++++++++----- .../esotericsoftware/spine/SkeletonJson.java | 56 +++++++++++-------- .../spine/attachments/PathAttachment.java | 12 ++-- 5 files changed, 78 insertions(+), 49 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java index c5d5daf49..baffcc0f6 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java @@ -90,12 +90,14 @@ public class PathConstraint implements Updatable { 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 - bone.worldX) * translateMix; - bone.worldY += (boneY - bone.worldY) * translateMix; + bone.worldX += (boneX - bone.worldX) * translateMix - skeletonX; + bone.worldY += (boneY - bone.worldY) * translateMix - skeletonY; float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; if (scale) { float length = lengths[i]; @@ -147,7 +149,7 @@ public class PathConstraint implements Updatable { boolean closed = path.getClosed(); if (!path.getConstantSpeed()) { - float pathLength = path.getTotalLength(); + float pathLength = path.getLength(); if (percentPosition) position *= pathLength; if (percentSpacing) { for (int i = 0; i < spacesCount; i++) @@ -203,7 +205,7 @@ public class PathConstraint implements Updatable { 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 || (space == 0 && i > 0)); + tangents || (i > 0 && space == 0)); } return out; } @@ -351,8 +353,7 @@ public class PathConstraint implements Updatable { } break; } - - addCurvePosition(p * 0.1f, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, tangents || (space == 0 && i > 0)); + addCurvePosition(p * 0.1f, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, tangents || (i > 0 && space == 0)); } return out; } 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 859ab69d2..8a46cf1d2 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java @@ -140,7 +140,8 @@ 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 updateCache = this.updateCache; updateCache.clear(); @@ -191,6 +192,7 @@ public class Skeleton { for (int i = 0, n = pathConstraints.size; i < n; i++) { PathConstraint constraint = pathConstraints.get(i); + // BOZO! - All bones any paths in the target slot are weighted to must come before the path constraint. Bone target = constraint.target.bone; sortBone(target); @@ -215,7 +217,7 @@ public class Skeleton { Bone target = constraint.target; sortBone(target); - // BOZO - Update transform constraints to support multiple constrained bones. + // BOZO! - Update transform constraints to support multiple constrained bones. // Array constrained = constraint.bones; // int boneCount = constrained.size; // for (int ii = 0; ii < boneCount; ii++) @@ -235,6 +237,8 @@ public class Skeleton { for (int i = 0, n = bones.size; i < n; i++) sortBone(bones.get(i)); + + System.out.println(updateCache); } private void sortBone (Bone bone) { 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 bc7b1de8c..1336df015 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java @@ -57,6 +57,7 @@ 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; @@ -188,6 +189,17 @@ public class SkeletonBinary { 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 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()); @@ -223,27 +235,17 @@ public class SkeletonBinary { 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(); data.spacing = input.readFloat(); - data.spacingMode = SpacingMode.values[input.readInt(true)]; - data.rotateMode = RotateMode.values[input.readInt(true)]; data.rotateMix = input.readFloat(); data.translateMix = input.readFloat(); skeletonData.pathConstraints.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 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); - } - // Default skin. Skin defaultSkin = readSkin(input, "default", nonessential); if (defaultSkin != null) { @@ -422,15 +424,25 @@ public class SkeletonBinary { return mesh; } case path: { + boolean closed = input.readBoolean(); + boolean constantSpeed = input.readBoolean(); int vertexCount = input.readInt(true); Vertices vertices = readVertices(input, vertexCount); + float length = input.readFloat(); + float[] curveLengths = new float[vertexCount / 3]; + for (int i = 0, n = curveLengths.length; i < n; i++) + curveLengths[i] = input.readFloat(); int color = nonessential ? input.readInt() : 0; PathAttachment path = attachmentLoader.newPathAttachment(skin, name); if (path == null) return null; + path.setClosed(closed); + path.setConstantSpeed(constantSpeed); + path.setLength(length); path.setWorldVerticesLength(vertexCount << 1); path.setVertices(vertices.vertices); path.setBones(vertices.bones); + path.getCurveLengths().addAll(curveLengths); if (nonessential) Color.rgba8888ToColor(path.getColor(), color); return path; } 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 bb5c9ec2e..5510f473d 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -56,6 +56,7 @@ 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; @@ -137,6 +138,22 @@ public class SkeletonJson { skeletonData.bones.add(data); } + // Slots. + for (JsonValue slotMap = root.getChild("slots"); slotMap != null; slotMap = slotMap.next) { + String slotName = slotMap.getString("name"); + String boneName = slotMap.getString("bone"); + BoneData boneData = skeletonData.findBone(boneName); + if (boneData == null) throw new SerializationException("Slot bone not found: " + boneName); + SlotData data = new SlotData(skeletonData.slots.size, slotName, boneData); + + String color = slotMap.getString("color", null); + if (color != null) data.getColor().set(Color.valueOf(color)); + + 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")); @@ -200,33 +217,18 @@ public class SkeletonJson { 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); data.spacing = constraintMap.getFloat("spacing", 0); - data.spacingMode = SpacingMode.valueOf(constraintMap.getString("spacingMode", "length")); - data.rotateMode = RotateMode.valueOf(constraintMap.getString("rotateMode", "tangent")); data.rotateMix = constraintMap.getFloat("rotateMix", 1); data.translateMix = constraintMap.getFloat("translateMix", 1); skeletonData.pathConstraints.add(data); } - // Slots. - for (JsonValue slotMap = root.getChild("slots"); slotMap != null; slotMap = slotMap.next) { - String slotName = slotMap.getString("name"); - String boneName = slotMap.getString("bone"); - BoneData boneData = skeletonData.findBone(boneName); - if (boneData == null) throw new SerializationException("Slot bone not found: " + boneName); - SlotData data = new SlotData(skeletonData.slots.size, slotName, boneData); - - String color = slotMap.getString("color", null); - if (color != null) data.getColor().set(Color.valueOf(color)); - - data.attachmentName = slotMap.getString("attachment", null); - data.blendMode = BlendMode.valueOf(slotMap.getString("blend", BlendMode.normal.name())); - skeletonData.slots.add(data); - } - // Skins. for (JsonValue skinMap = root.getChild("skins"); skinMap != null; skinMap = skinMap.next) { Skin skin = new Skin(skinMap.name); @@ -349,7 +351,17 @@ public class SkeletonJson { case path: { PathAttachment path = attachmentLoader.newPathAttachment(skin, name); if (path == null) return null; - readVertices(map, path, map.getInt("vertexCount") << 1); + path.setClosed(map.getBoolean("closed", false)); + path.setConstantSpeed(map.getBoolean("constantSpeed", true)); + path.setLength(map.getFloat("length")); + + int vertexCount = map.getInt("vertexCount"); + readVertices(map, path, vertexCount << 1); + + float[] curveLengths = path.getCurveLengths().setSize(vertexCount / 3); + int i = 0; + for (JsonValue curves = map.get("curves").child; curves != null; curves = curves.next) + curveLengths[i++] = curves.asFloat(); String color = map.getString("color", null); if (color != null) path.getColor().set(Color.valueOf(color)); @@ -519,8 +531,8 @@ public class SkeletonJson { timeline = new PathConstraintPositionTimeline(timelineMap.size); timeline.pathConstraintIndex = index; int frameIndex = 0; - for (JsonValue valueMap = constraintMap.child; valueMap != null; valueMap = valueMap.next) { - timeline.setFrame(frameIndex, valueMap.getFloat("time"), valueMap.getFloat(timelineName, 1)); + for (JsonValue valueMap = timelineMap.child; valueMap != null; valueMap = valueMap.next) { + timeline.setFrame(frameIndex, valueMap.getFloat("time"), valueMap.getFloat(timelineName, 0)); readCurve(valueMap, timeline, frameIndex); frameIndex++; } @@ -531,7 +543,7 @@ public class SkeletonJson { PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(timelineMap.size); timeline.pathConstraintIndex = index; int frameIndex = 0; - for (JsonValue valueMap = constraintMap.child; valueMap != null; valueMap = valueMap.next) { + 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); 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 index 843deb58f..83c481d97 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java @@ -36,7 +36,7 @@ import com.badlogic.gdx.utils.FloatArray; import com.esotericsoftware.spine.Slot; public class PathAttachment extends VertexAttachment { - float totalLength; + float length; final FloatArray lengths = new FloatArray(); boolean closed, constantSpeed; @@ -72,15 +72,15 @@ public class PathAttachment extends VertexAttachment { } /** Returns the length of the path in the setup pose. */ - public float getTotalLength () { - return totalLength; + public float getLength () { + return length; } - public void setTotalLength (float totalLength) { - this.totalLength = totalLength; + public void setLength (float totalLength) { + this.length = totalLength; } - /** Returns the length of each curve in the setup pose. */ + /** Returns the distance in the setup pose from the start of the path to the end of each curve. */ public FloatArray getCurveLengths () { return lengths; } From 07bb307b726b28f3bf7d9b1b6917e8ae2395e3f0 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Thu, 9 Jun 2016 18:22:23 +0200 Subject: [PATCH 37/45] Fixed loader scale for paths. --- .../spine/PathConstraint.java | 23 +++++++++++-------- .../com/esotericsoftware/spine/Skeleton.java | 2 -- .../spine/SkeletonBinary.java | 23 +++++++++++-------- .../esotericsoftware/spine/SkeletonJson.java | 20 ++++++++++------ .../spine/attachments/PathAttachment.java | 14 ++--------- 5 files changed, 42 insertions(+), 40 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java index baffcc0f6..a0253d870 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java @@ -66,12 +66,12 @@ public class PathConstraint implements Updatable { if (!translate && !rotate) return; PathConstraintData data = this.data; - Object[] bones = this.bones.items; SpacingMode spacingMode = data.spacingMode; + boolean lengthSpacing = spacingMode == SpacingMode.length; RotateMode rotateMode = data.rotateMode; boolean tangents = rotateMode == RotateMode.tangent, scale = rotateMode == RotateMode.chainScale; - boolean lengthSpacing = spacingMode == SpacingMode.length; 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) { @@ -96,8 +96,8 @@ public class PathConstraint implements Updatable { 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 - bone.worldX) * translateMix - skeletonX; - bone.worldY += (boneY - bone.worldY) * translateMix - skeletonY; + 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]; @@ -149,14 +149,18 @@ public class PathConstraint implements Updatable { boolean closed = path.getClosed(); if (!path.getConstantSpeed()) { - float pathLength = path.getLength(); + float[] lengths = path.getLengths().items; + float pathLength; + if (closed) { + curveCount--; + pathLength = lengths[curveCount]; + } else + pathLength = lengths[curveCount - 2]; if (percentPosition) position *= pathLength; if (percentSpacing) { for (int i = 0; i < spacesCount; i++) spaces[i] *= pathLength; } - curveCount--; - float[] curveLengths = path.getCurveLengths().items; world = this.world.setSize(8); for (int i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) { float space = spaces[i]; @@ -185,17 +189,16 @@ public class PathConstraint implements Updatable { // Determine curve containing position. for (;; curve++) { - float length = curveLengths[curve]; + float length = lengths[curve]; if (p > length) continue; if (curve == 0) p /= length; else { - float prev = curveLengths[curve - 1]; + float prev = lengths[curve - 1]; p = (p - prev) / (length - prev); } break; } - if (curve != lastCurve) { lastCurve = curve; if (closed && curve == curveCount) { 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 8a46cf1d2..9d51e5ade 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java @@ -237,8 +237,6 @@ public class Skeleton { for (int i = 0, n = bones.size; i < n; i++) sortBone(bones.get(i)); - - System.out.println(updateCache); } private void sortBone (Bone bone) { 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 1336df015..1a556c90c 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java @@ -240,7 +240,9 @@ public class SkeletonBinary { 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); @@ -428,21 +430,19 @@ public class SkeletonBinary { boolean constantSpeed = input.readBoolean(); int vertexCount = input.readInt(true); Vertices vertices = readVertices(input, vertexCount); - float length = input.readFloat(); - float[] curveLengths = new float[vertexCount / 3]; - for (int i = 0, n = curveLengths.length; i < n; i++) - curveLengths[i] = input.readFloat(); + 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; PathAttachment path = attachmentLoader.newPathAttachment(skin, name); if (path == null) return null; path.setClosed(closed); path.setConstantSpeed(constantSpeed); - path.setLength(length); path.setWorldVerticesLength(vertexCount << 1); path.setVertices(vertices.vertices); path.setBones(vertices.bones); - path.getCurveLengths().addAll(curveLengths); + path.getLengths().addAll(lengths); if (nonessential) Color.rgba8888ToColor(path.getColor(), color); return path; } @@ -609,6 +609,7 @@ public class SkeletonBinary { // 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); @@ -616,13 +617,17 @@ public class SkeletonBinary { case PATH_POSITION: case PATH_SPACING: { PathConstraintPositionTimeline timeline; - if (timelineType == PATH_SPACING) + float timelineScale = 1; + if (timelineType == PATH_SPACING) { timeline = new PathConstraintSpacingTimeline(frameCount); - else + 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()); + timeline.setFrame(frameIndex, input.readFloat(), input.readFloat() * timelineScale); if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline); } timelines.add(timeline); 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 5510f473d..95c0a2918 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -222,7 +222,9 @@ public class SkeletonJson { 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); @@ -353,15 +355,14 @@ public class SkeletonJson { if (path == null) return null; path.setClosed(map.getBoolean("closed", false)); path.setConstantSpeed(map.getBoolean("constantSpeed", true)); - path.setLength(map.getFloat("length")); int vertexCount = map.getInt("vertexCount"); readVertices(map, path, vertexCount << 1); - float[] curveLengths = path.getCurveLengths().setSize(vertexCount / 3); + float[] lengths = path.getLengths().setSize(vertexCount / 3); int i = 0; - for (JsonValue curves = map.get("curves").child; curves != null; curves = curves.next) - curveLengths[i++] = curves.asFloat(); + for (JsonValue curves = map.require("lengths").child; curves != null; curves = curves.next) + lengths[i++] = curves.asFloat() * scale; String color = map.getString("color", null); if (color != null) path.getColor().set(Color.valueOf(color)); @@ -521,18 +522,23 @@ public class SkeletonJson { 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; - if (timelineName.equals("spacing")) + float timelineScale = 1; + if (timelineName.equals("spacing")) { timeline = new PathConstraintSpacingTimeline(timelineMap.size); - else + 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)); + timeline.setFrame(frameIndex, valueMap.getFloat("time"), valueMap.getFloat(timelineName, 0) * timelineScale); readCurve(valueMap, timeline, frameIndex); frameIndex++; } 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 index 83c481d97..43c4f3e0a 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java @@ -36,7 +36,6 @@ import com.badlogic.gdx.utils.FloatArray; import com.esotericsoftware.spine.Slot; public class PathAttachment extends VertexAttachment { - float length; final FloatArray lengths = new FloatArray(); boolean closed, constantSpeed; @@ -71,17 +70,8 @@ public class PathAttachment extends VertexAttachment { this.constantSpeed = constantSpeed; } - /** Returns the length of the path in the setup pose. */ - public float getLength () { - return length; - } - - public void setLength (float totalLength) { - this.length = totalLength; - } - - /** Returns the distance in the setup pose from the start of the path to the end of each curve. */ - public FloatArray getCurveLengths () { + /** Returns the length in the setup pose from the start of the path to the end of each curve. */ + public FloatArray getLengths () { return lengths; } From 8d19504bd7d3788380a0b09ff35358a221f1bf5a Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Thu, 9 Jun 2016 19:31:05 +0200 Subject: [PATCH 38/45] Fixed binary path loading. --- .../spine/PathConstraint.java | 28 ++++++++----------- .../spine/SkeletonBinary.java | 13 +++++---- .../esotericsoftware/spine/SkeletonJson.java | 3 +- .../spine/attachments/PathAttachment.java | 9 ++++-- 4 files changed, 27 insertions(+), 26 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java index a0253d870..1d519d6b2 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java @@ -144,18 +144,14 @@ public class PathConstraint implements Updatable { boolean percentSpacing) { Slot target = this.target; float position = this.position; - int verticesLength = path.getWorldVerticesLength(), curveCount = verticesLength / 6, lastCurve = NONE; 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().items; - float pathLength; - if (closed) { - curveCount--; - pathLength = lengths[curveCount]; - } else - pathLength = lengths[curveCount - 2]; + 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++) @@ -172,15 +168,15 @@ public class PathConstraint implements Updatable { if (p < 0) p += pathLength; curve = 0; } else if (p < 0) { - if (lastCurve != BEFORE) { - lastCurve = BEFORE; + if (prevCurve != BEFORE) { + prevCurve = BEFORE; path.computeWorldVertices(target, 2, 4, world, 0); } addBeforePosition(p, world, 0, out, o); continue; } else if (p > pathLength) { - if (lastCurve != AFTER) { - lastCurve = AFTER; + if (prevCurve != AFTER) { + prevCurve = AFTER; path.computeWorldVertices(target, verticesLength - 6, 4, world, 0); } addAfterPosition(p - pathLength, world, 0, out, o); @@ -199,8 +195,8 @@ public class PathConstraint implements Updatable { } break; } - if (curve != lastCurve) { - lastCurve = curve; + if (curve != prevCurve) { + prevCurve = curve; if (closed && curve == curveCount) { path.computeWorldVertices(target, verticesLength - 4, 4, world, 0); path.computeWorldVertices(target, 0, 4, world, 4); @@ -303,8 +299,8 @@ public class PathConstraint implements Updatable { } // Curve segment lengths. - if (curve != lastCurve) { - lastCurve = curve; + if (curve != prevCurve) { + prevCurve = curve; int ii = curve * 6; x1 = world[ii]; y1 = world[ii + 1]; 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 1a556c90c..36e54b601 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; @@ -138,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); @@ -442,7 +445,7 @@ public class SkeletonBinary { path.setWorldVerticesLength(vertexCount << 1); path.setVertices(vertices.vertices); path.setBones(vertices.bones); - path.getLengths().addAll(lengths); + path.setLengths(lengths); if (nonessential) Color.rgba8888ToColor(path.getColor(), color); return path; } @@ -632,6 +635,7 @@ public class SkeletonBinary { } timelines.add(timeline); duration = Math.max(duration, timeline.getFrames()[(frameCount - 1) * PathConstraintPositionTimeline.ENTRIES]); + break; } case PATH_MIX: { PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frameCount); @@ -642,6 +646,7 @@ public class SkeletonBinary { } timelines.add(timeline); duration = Math.max(duration, timeline.getFrames()[(frameCount - 1) * PathConstraintMixTimeline.ENTRIES]); + break; } } } @@ -744,11 +749,7 @@ public class SkeletonBinary { timelines.add(timeline); duration = Math.max(duration, timeline.getFrames()[eventCount - 1]); } - } catch ( - - IOException ex) - - { + } catch (IOException ex) { throw new SerializationException("Error reading skeleton file.", ex); } 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 95c0a2918..b966f3cc7 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -359,10 +359,11 @@ public class SkeletonJson { int vertexCount = map.getInt("vertexCount"); readVertices(map, path, vertexCount << 1); - float[] lengths = path.getLengths().setSize(vertexCount / 3); + 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) path.getColor().set(Color.valueOf(color)); 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 index 43c4f3e0a..229be6d47 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java @@ -32,11 +32,10 @@ package com.esotericsoftware.spine.attachments; import com.badlogic.gdx.graphics.Color; -import com.badlogic.gdx.utils.FloatArray; import com.esotericsoftware.spine.Slot; public class PathAttachment extends VertexAttachment { - final FloatArray lengths = new FloatArray(); + float[] lengths; boolean closed, constantSpeed; // Nonessential. @@ -71,10 +70,14 @@ public class PathAttachment extends VertexAttachment { } /** Returns the length in the setup pose from the start of the path to the end of each curve. */ - public FloatArray getLengths () { + public float[] getLengths () { return lengths; } + public void setLengths (float[] lengths) { + this.lengths = lengths; + } + public Color getColor () { return color; } From 52545f262b90b1e415dd50868dc6f1fcc56932ad Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Fri, 10 Jun 2016 00:27:46 +0200 Subject: [PATCH 39/45] Fixed update order for path weights. Not super happy with having to do this, but it seems necessary. badlogic/spine-internal#72 --- .../com/esotericsoftware/spine/Skeleton.java | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 deletions(-) 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 9d51e5ade..af01157d9 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java @@ -34,8 +34,11 @@ 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; public class Skeleton { @@ -183,7 +186,6 @@ public class Skeleton { updateCache.add(constraint); - sortReset(target.children); sortReset(parent.children); constrained.peek().sorted = true; } @@ -192,9 +194,19 @@ public class Skeleton { for (int i = 0, n = pathConstraints.size; i < n; i++) { PathConstraint constraint = pathConstraints.get(i); - // BOZO! - All bones any paths in the target slot are weighted to must come before the path constraint. - Bone target = constraint.target.bone; - sortBone(target); + Slot slot = constraint.target; + int slotIndex = slot.getData().index; + Bone slotBone = slot.bone; + if (skin != null) { + for (Entry entry : skin.attachments.entries()) + if (entry.key.slotIndex == slotIndex) sortPathConstraintAttachment(entry.value, slotBone); + } + for (int ii = 0, nn = data.skins.size; ii < nn; ii++) + for (Entry entry : data.skins.get(i).attachments.entries()) + if (entry.key.slotIndex == slotIndex) sortPathConstraintAttachment(entry.value, slotBone); + + Attachment attachment = slot.getAttachment(); + if (attachment instanceof PathAttachment) sortPathConstraintAttachment(attachment, slotBone); Array constrained = constraint.bones; int boneCount = constrained.size; @@ -203,7 +215,6 @@ public class Skeleton { updateCache.add(constraint); - sortReset(target.children); for (int ii = 0; ii < boneCount; ii++) sortReset(constrained.get(ii).children); for (int ii = 0; ii < boneCount; ii++) @@ -214,8 +225,7 @@ public class Skeleton { for (int i = 0, n = transformConstraints.size; i < n; i++) { TransformConstraint constraint = transformConstraints.get(i); - Bone target = constraint.target; - sortBone(target); + sortBone(constraint.target); // BOZO! - Update transform constraints to support multiple constrained bones. // Array constrained = constraint.bones; @@ -229,7 +239,6 @@ public class Skeleton { // for (int ii = 0; ii < boneCount; ii++) // reset(constrained.get(ii).children); sortReset(constraint.bone.children); // BOZO - Remove. - sortReset(target.children); // for (int ii = 0; ii < boneCount; ii++) // constrained.get(ii).sorted = true; constraint.bone.sorted = true; // BOZO - Remove. @@ -239,6 +248,18 @@ public class Skeleton { sortBone(bones.get(i)); } + 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; From 8b3b0169ad8353db9e2592ea9f2fea81d62bba3f Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Fri, 10 Jun 2016 00:27:56 +0200 Subject: [PATCH 40/45] Added Slot#getIndex. --- .../spine-libgdx/src/com/esotericsoftware/spine/SlotData.java | 4 ++++ 1 file changed, 4 insertions(+) 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 eea1c6620..5625b1b7d 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SlotData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SlotData.java @@ -50,6 +50,10 @@ public class SlotData { this.boneData = boneData; } + public int getIndex () { + return index; + } + public String getName () { return name; } From 92d7ef32f4830a4061dd2f3eee992cf35c2e8877 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Fri, 10 Jun 2016 03:21:54 +0200 Subject: [PATCH 41/45] Multiple constrained bones for transform constraints. --- .../spine/PathConstraint.java | 8 +- .../com/esotericsoftware/spine/Skeleton.java | 20 ++- .../spine/SkeletonBinary.java | 3 +- .../esotericsoftware/spine/SkeletonJson.java | 9 +- .../spine/TransformConstraint.java | 114 +++++++++--------- .../spine/TransformConstraintData.java | 14 +-- 6 files changed, 86 insertions(+), 82 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java index 1d519d6b2..b9c4b1460 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java @@ -27,14 +27,14 @@ public class PathConstraint implements Updatable { if (data == null) throw new IllegalArgumentException("data cannot be null."); if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); this.data = data; - position = data.position; - spacing = data.spacing; - rotateMix = data.rotateMix; - translateMix = data.translateMix; 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. */ 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 af01157d9..4840ba15b 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java @@ -227,21 +227,17 @@ public class Skeleton { sortBone(constraint.target); - // BOZO! - Update transform constraints to support multiple constrained bones. - // Array constrained = constraint.bones; - // int boneCount = constrained.size; - // for (int ii = 0; ii < boneCount; ii++) - // sortBone(constrained.get(ii)); - sortBone(constraint.bone); + 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++) - // reset(constrained.get(ii).children); - sortReset(constraint.bone.children); // BOZO - Remove. - // for (int ii = 0; ii < boneCount; ii++) - // constrained.get(ii).sorted = true; - constraint.bone.sorted = true; // BOZO - Remove. + 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++) 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 36e54b601..a24fe562f 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java @@ -217,7 +217,8 @@ public class SkeletonBinary { // Transform constraints. for (int i = 0, n = input.readInt(true); i < n; i++) { TransformConstraintData data = new TransformConstraintData(input.readString()); - data.bone = skeletonData.bones.get(input.readInt(true)); + 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; 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 b966f3cc7..5a49e17c3 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -179,9 +179,12 @@ public class SkeletonJson { for (JsonValue constraintMap = root.getChild("transform"); constraintMap != null; constraintMap = constraintMap.next) { TransformConstraintData data = new TransformConstraintData(constraintMap.getString("name")); - String boneName = constraintMap.getString("bone"); - data.bone = skeletonData.findBone(boneName); - if (data.bone == null) throw new SerializationException("Bone not found: " + boneName); + 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); 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 bf484eb36..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,10 +4,12 @@ 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; final Vector2 temp = new Vector2(); @@ -19,7 +21,9 @@ public class TransformConstraint implements Updatable { translateMix = data.translateMix; scaleMix = data.scaleMix; shearMix = data.shearMix; - bone = skeleton.findBone(data.bone.name); + bones = new Array(data.bones.size); + for (BoneData boneData : data.bones) + bones.add(skeleton.findBone(boneData.name)); target = skeleton.findBone(data.target.name); } @@ -28,12 +32,14 @@ public class TransformConstraint implements Updatable { 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); rotateMix = constraint.rotateMix; translateMix = constraint.translateMix; scaleMix = constraint.scaleMix; shearMix = constraint.shearMix; - bone = skeleton.bones.get(constraint.bone.data.index); - target = skeleton.bones.get(constraint.target.data.index); } public void apply () { @@ -41,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) + 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 (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 + 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(target.b * target.b + target.d * target.d); - s = bs > 0.00001f ? (bs + (ts - bs + data.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 + data.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(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(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 () { 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 4a20acd5b..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,9 +1,12 @@ 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; @@ -16,13 +19,8 @@ public class TransformConstraintData { return name; } - public BoneData getBone () { - return bone; - } - - public void setBone (BoneData bone) { - if (bone == null) throw new IllegalArgumentException("bone cannot be null."); - this.bone = bone; + public Array getBones () { + return bones; } public BoneData getTarget () { From ae4ac8210f5cf301cb8e44e343b9d1ccf76ae4be Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Fri, 10 Jun 2016 04:14:19 +0200 Subject: [PATCH 42/45] Check default skin, fixed crash. --- .../src/com/esotericsoftware/spine/Skeleton.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) 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 4840ba15b..1e614a608 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java @@ -197,13 +197,11 @@ public class Skeleton { Slot slot = constraint.target; int slotIndex = slot.getData().index; Bone slotBone = slot.bone; - if (skin != null) { - for (Entry entry : skin.attachments.entries()) - if (entry.key.slotIndex == slotIndex) sortPathConstraintAttachment(entry.value, slotBone); - } + 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++) - for (Entry entry : data.skins.get(i).attachments.entries()) - if (entry.key.slotIndex == slotIndex) sortPathConstraintAttachment(entry.value, slotBone); + sortPathConstraintAttachment(data.skins.get(ii), slotIndex, slotBone); Attachment attachment = slot.getAttachment(); if (attachment instanceof PathAttachment) sortPathConstraintAttachment(attachment, slotBone); @@ -244,6 +242,11 @@ public class Skeleton { 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(); From d5228d2549dbfb016b6c604226780f3687bb1ef5 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Fri, 10 Jun 2016 20:12:20 +0200 Subject: [PATCH 43/45] Fixed having > 8 linked meshes. --- spine-c/src/spine/SkeletonJson.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; } From 021dfe288b55f7258010bb99b7e63cb6dc271d42 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Sat, 11 Jun 2016 05:04:43 +0200 Subject: [PATCH 44/45] Renamed constant speed to accurate. --- .../src/com/esotericsoftware/spine/PathConstraint.java | 2 +- .../src/com/esotericsoftware/spine/SkeletonBinary.java | 4 ++-- .../src/com/esotericsoftware/spine/SkeletonJson.java | 2 +- .../spine/attachments/PathAttachment.java | 10 +++++----- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java index b9c4b1460..4b9d86521 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java @@ -148,7 +148,7 @@ public class PathConstraint implements Updatable { boolean closed = path.getClosed(); int verticesLength = path.getWorldVerticesLength(), curveCount = verticesLength / 6, prevCurve = NONE; - if (!path.getConstantSpeed()) { + if (!path.getAccurate()) { float[] lengths = path.getLengths(); curveCount -= closed ? 1 : 2; float pathLength = lengths[curveCount]; 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 a24fe562f..4577da8a7 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java @@ -431,7 +431,7 @@ public class SkeletonBinary { } case path: { boolean closed = input.readBoolean(); - boolean constantSpeed = input.readBoolean(); + boolean accurate = input.readBoolean(); int vertexCount = input.readInt(true); Vertices vertices = readVertices(input, vertexCount); float[] lengths = new float[vertexCount / 3]; @@ -442,7 +442,7 @@ public class SkeletonBinary { PathAttachment path = attachmentLoader.newPathAttachment(skin, name); if (path == null) return null; path.setClosed(closed); - path.setConstantSpeed(constantSpeed); + path.setAccurate(accurate); path.setWorldVerticesLength(vertexCount << 1); path.setVertices(vertices.vertices); path.setBones(vertices.bones); 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 5a49e17c3..29a808322 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -357,7 +357,7 @@ public class SkeletonJson { PathAttachment path = attachmentLoader.newPathAttachment(skin, name); if (path == null) return null; path.setClosed(map.getBoolean("closed", false)); - path.setConstantSpeed(map.getBoolean("constantSpeed", true)); + path.setAccurate(map.getBoolean("accurate", true)); int vertexCount = map.getInt("vertexCount"); readVertices(map, path, vertexCount << 1); 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 index 229be6d47..5fd3fd6fe 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java @@ -36,7 +36,7 @@ import com.esotericsoftware.spine.Slot; public class PathAttachment extends VertexAttachment { float[] lengths; - boolean closed, constantSpeed; + boolean closed, accurate; // Nonessential. final Color color = new Color(1, 0.5f, 0, 1); @@ -61,12 +61,12 @@ public class PathAttachment extends VertexAttachment { this.closed = closed; } - public boolean getConstantSpeed () { - return constantSpeed; + public boolean getAccurate () { + return accurate; } - public void setConstantSpeed (boolean constantSpeed) { - this.constantSpeed = constantSpeed; + public void setAccurate (boolean accurate) { + this.accurate = accurate; } /** Returns the length in the setup pose from the start of the path to the end of each curve. */ From 534ffc8d1d274b07828f9a1f7464e4ed41497ce7 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Sun, 12 Jun 2016 03:55:59 +0200 Subject: [PATCH 45/45] Back to constant speed. It's better. --- .../src/com/esotericsoftware/spine/PathConstraint.java | 2 +- .../src/com/esotericsoftware/spine/SkeletonBinary.java | 4 ++-- .../src/com/esotericsoftware/spine/SkeletonJson.java | 2 +- .../spine/attachments/PathAttachment.java | 10 +++++----- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java index 4b9d86521..b9c4b1460 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java @@ -148,7 +148,7 @@ public class PathConstraint implements Updatable { boolean closed = path.getClosed(); int verticesLength = path.getWorldVerticesLength(), curveCount = verticesLength / 6, prevCurve = NONE; - if (!path.getAccurate()) { + if (!path.getConstantSpeed()) { float[] lengths = path.getLengths(); curveCount -= closed ? 1 : 2; float pathLength = lengths[curveCount]; 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 4577da8a7..a24fe562f 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java @@ -431,7 +431,7 @@ public class SkeletonBinary { } case path: { boolean closed = input.readBoolean(); - boolean accurate = input.readBoolean(); + boolean constantSpeed = input.readBoolean(); int vertexCount = input.readInt(true); Vertices vertices = readVertices(input, vertexCount); float[] lengths = new float[vertexCount / 3]; @@ -442,7 +442,7 @@ public class SkeletonBinary { PathAttachment path = attachmentLoader.newPathAttachment(skin, name); if (path == null) return null; path.setClosed(closed); - path.setAccurate(accurate); + path.setConstantSpeed(constantSpeed); path.setWorldVerticesLength(vertexCount << 1); path.setVertices(vertices.vertices); path.setBones(vertices.bones); 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 29a808322..5a49e17c3 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -357,7 +357,7 @@ public class SkeletonJson { PathAttachment path = attachmentLoader.newPathAttachment(skin, name); if (path == null) return null; path.setClosed(map.getBoolean("closed", false)); - path.setAccurate(map.getBoolean("accurate", true)); + path.setConstantSpeed(map.getBoolean("constantSpeed", true)); int vertexCount = map.getInt("vertexCount"); readVertices(map, path, vertexCount << 1); 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 index 5fd3fd6fe..229be6d47 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java @@ -36,7 +36,7 @@ import com.esotericsoftware.spine.Slot; public class PathAttachment extends VertexAttachment { float[] lengths; - boolean closed, accurate; + boolean closed, constantSpeed; // Nonessential. final Color color = new Color(1, 0.5f, 0, 1); @@ -61,12 +61,12 @@ public class PathAttachment extends VertexAttachment { this.closed = closed; } - public boolean getAccurate () { - return accurate; + public boolean getConstantSpeed () { + return constantSpeed; } - public void setAccurate (boolean accurate) { - this.accurate = accurate; + 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. */