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.