Explorar o código

Value space timelines, multiple property IDs per timeline, rotation not limited -180/180, clean up.

* Timeline is an abstract base class rather than an interface.
* Timelines have a list of String property IDs rather than a single int ID.
* CurveTimeline is separated into percent and value timelines and the API is cleaned up.
* CurveTimeline stores Bezier curves more efficiently. Linear/stepped keys used to take up memory they didn't use (18 floats/key).
* Binary format knows how many keys are Bezier up front for more efficient loading.
* RotateTimeline is no longer limited to -180/180.
* ScaleTimeline and ShearTimeline no longer extend TranslateTimeline.
* PathConstraintSpacingTimeline no longer extends PathConstraintPositionTimeline.
NathanSweet %!s(int64=5) %!d(string=hai) anos
pai
achega
93ca505864

+ 1 - 1
spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AttachmentTimelineTests.java

@@ -68,7 +68,7 @@ public class AttachmentTimelineTests {
 		skeleton = new Skeleton(skeletonData);
 		skeleton = new Skeleton(skeletonData);
 		slot = skeleton.findSlot("slot");
 		slot = skeleton.findSlot("slot");
 
 
-		AttachmentTimeline timeline = new AttachmentTimeline(2);
+		AttachmentTimeline timeline = new AttachmentTimeline(2, 0);
 		timeline.setFrame(0, 0, "attachment1");
 		timeline.setFrame(0, 0, "attachment1");
 		timeline.setFrame(1, 0.5f, "attachment2");
 		timeline.setFrame(1, 0.5f, "attachment2");
 
 

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 341 - 309
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java


+ 18 - 25
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java

@@ -29,12 +29,12 @@
 
 
 package com.esotericsoftware.spine;
 package com.esotericsoftware.spine;
 
 
-import static com.esotericsoftware.spine.Animation.RotateTimeline.*;
+import static com.esotericsoftware.spine.Animation.ValueCurveTimeline.*;
 
 
 import com.badlogic.gdx.utils.Array;
 import com.badlogic.gdx.utils.Array;
 import com.badlogic.gdx.utils.FloatArray;
 import com.badlogic.gdx.utils.FloatArray;
 import com.badlogic.gdx.utils.IntArray;
 import com.badlogic.gdx.utils.IntArray;
-import com.badlogic.gdx.utils.IntSet;
+import com.badlogic.gdx.utils.ObjectSet;
 import com.badlogic.gdx.utils.Pool;
 import com.badlogic.gdx.utils.Pool;
 import com.badlogic.gdx.utils.Pool.Poolable;
 import com.badlogic.gdx.utils.Pool.Poolable;
 import com.badlogic.gdx.utils.SnapshotArray;
 import com.badlogic.gdx.utils.SnapshotArray;
@@ -90,7 +90,7 @@ public class AnimationState {
 	private final Array<Event> events = new Array();
 	private final Array<Event> events = new Array();
 	final SnapshotArray<AnimationStateListener> listeners = new SnapshotArray();
 	final SnapshotArray<AnimationStateListener> listeners = new SnapshotArray();
 	private final EventQueue queue = new EventQueue();
 	private final EventQueue queue = new EventQueue();
-	private final IntSet propertyIDs = new IntSet();
+	private final ObjectSet<String> propertyIds = new ObjectSet();
 	boolean animationsChanged;
 	boolean animationsChanged;
 	private float timeScale = 1;
 	private float timeScale = 1;
 
 
@@ -367,19 +367,13 @@ public class AnimationState {
 		} else {
 		} else {
 			r1 = blend == MixBlend.setup ? bone.data.rotation : bone.rotation;
 			r1 = blend == MixBlend.setup ? bone.data.rotation : bone.rotation;
 			if (time >= frames[frames.length - ENTRIES]) // Time is after last frame.
 			if (time >= frames[frames.length - ENTRIES]) // Time is after last frame.
-				r2 = bone.data.rotation + frames[frames.length + PREV_ROTATION];
+				r2 = bone.data.rotation + frames[frames.length + PREV_VALUE];
 			else {
 			else {
 				// Interpolate between the previous frame and the current frame.
 				// Interpolate between the previous frame and the current frame.
 				int frame = Animation.binarySearch(frames, time, ENTRIES);
 				int frame = Animation.binarySearch(frames, time, ENTRIES);
-				float prevRotation = frames[frame + PREV_ROTATION];
-				float frameTime = frames[frame];
-				float percent = timeline.getCurvePercent((frame >> 1) - 1,
-					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
-
-				r2 = frames[frame + ROTATION] - prevRotation;
-				r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360;
-				r2 = prevRotation + r2 * percent + bone.data.rotation;
-				r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360;
+				r2 = bone.data.rotation + timeline.getCurveValue((frame >> 1) - 1, time, //
+					frames[frame + PREV_TIME], frames[frame + PREV_VALUE], //
+					frames[frame], frames[frame + VALUE]);
 			}
 			}
 		}
 		}
 
 
@@ -409,8 +403,7 @@ public class AnimationState {
 			timelinesRotation[i] = total;
 			timelinesRotation[i] = total;
 		}
 		}
 		timelinesRotation[i + 1] = diff;
 		timelinesRotation[i + 1] = diff;
-		r1 += total * alpha;
-		bone.rotation = r1 - (16384 - (int)(16384.499999999996 - r1 / 360)) * 360;
+		bone.rotation = r1 + total * alpha;
 	}
 	}
 
 
 	private void queueEvents (TrackEntry entry, float animationTime) {
 	private void queueEvents (TrackEntry entry, float animationTime) {
@@ -700,7 +693,7 @@ public class AnimationState {
 		animationsChanged = false;
 		animationsChanged = false;
 
 
 		// Process in the order that animations are applied.
 		// Process in the order that animations are applied.
-		propertyIDs.clear(2048);
+		propertyIds.clear(2048);
 		for (int i = 0, n = tracks.size; i < n; i++) {
 		for (int i = 0, n = tracks.size; i < n; i++) {
 			TrackEntry entry = tracks.get(i);
 			TrackEntry entry = tracks.get(i);
 			if (entry == null) continue;
 			if (entry == null) continue;
@@ -713,7 +706,7 @@ public class AnimationState {
 		}
 		}
 
 
 		// Process in the reverse order that animations are applied.
 		// Process in the reverse order that animations are applied.
-		propertyIDs.clear(2048);
+		propertyIds.clear(2048);
 		for (int i = tracks.size - 1; i >= 0; i--) {
 		for (int i = tracks.size - 1; i >= 0; i--) {
 			TrackEntry entry = tracks.get(i);
 			TrackEntry entry = tracks.get(i);
 			while (entry != null) {
 			while (entry != null) {
@@ -730,11 +723,11 @@ public class AnimationState {
 		int[] timelineMode = entry.timelineMode.setSize(timelinesCount);
 		int[] timelineMode = entry.timelineMode.setSize(timelinesCount);
 		entry.timelineHoldMix.clear();
 		entry.timelineHoldMix.clear();
 		Object[] timelineHoldMix = entry.timelineHoldMix.setSize(timelinesCount);
 		Object[] timelineHoldMix = entry.timelineHoldMix.setSize(timelinesCount);
-		IntSet propertyIDs = this.propertyIDs;
+		ObjectSet<String> propertyIds = this.propertyIds;
 
 
 		if (to != null && to.holdPrevious) {
 		if (to != null && to.holdPrevious) {
 			for (int i = 0; i < timelinesCount; i++) {
 			for (int i = 0; i < timelinesCount; i++) {
-				propertyIDs.add(((Timeline)timelines[i]).getPropertyId());
+				propertyIds.addAll(((Timeline)timelines[i]).getPropertyIds());
 				timelineMode[i] = HOLD;
 				timelineMode[i] = HOLD;
 			}
 			}
 			return;
 			return;
@@ -743,15 +736,15 @@ public class AnimationState {
 		outer:
 		outer:
 		for (int i = 0; i < timelinesCount; i++) {
 		for (int i = 0; i < timelinesCount; i++) {
 			Timeline timeline = (Timeline)timelines[i];
 			Timeline timeline = (Timeline)timelines[i];
-			int id = timeline.getPropertyId();
-			if (!propertyIDs.add(id))
+			String[] ids = timeline.getPropertyIds();
+			if (!propertyIds.addAll(ids))
 				timelineMode[i] = SUBSEQUENT;
 				timelineMode[i] = SUBSEQUENT;
 			else if (to == null || timeline instanceof AttachmentTimeline || timeline instanceof DrawOrderTimeline
 			else if (to == null || timeline instanceof AttachmentTimeline || timeline instanceof DrawOrderTimeline
-				|| timeline instanceof EventTimeline || !to.animation.hasTimeline(id)) {
+				|| timeline instanceof EventTimeline || !to.animation.hasTimeline(ids)) {
 				timelineMode[i] = FIRST;
 				timelineMode[i] = FIRST;
 			} else {
 			} else {
 				for (TrackEntry next = to.mixingTo; next != null; next = next.mixingTo) {
 				for (TrackEntry next = to.mixingTo; next != null; next = next.mixingTo) {
-					if (next.animation.hasTimeline(id)) continue;
+					if (next.animation.hasTimeline(ids)) continue;
 					if (next.mixDuration > 0) {
 					if (next.mixDuration > 0) {
 						timelineMode[i] = HOLD_MIX;
 						timelineMode[i] = HOLD_MIX;
 						timelineHoldMix[i] = next;
 						timelineHoldMix[i] = next;
@@ -768,12 +761,12 @@ public class AnimationState {
 		Object[] timelines = entry.animation.timelines.items;
 		Object[] timelines = entry.animation.timelines.items;
 		int timelinesCount = entry.animation.timelines.size;
 		int timelinesCount = entry.animation.timelines.size;
 		int[] timelineMode = entry.timelineMode.items;
 		int[] timelineMode = entry.timelineMode.items;
-		IntSet propertyIDs = this.propertyIDs;
+		ObjectSet<String> propertyIds = this.propertyIds;
 
 
 		for (int i = 0; i < timelinesCount; i++) {
 		for (int i = 0; i < timelinesCount; i++) {
 			if (timelines[i] instanceof AttachmentTimeline) {
 			if (timelines[i] instanceof AttachmentTimeline) {
 				AttachmentTimeline timeline = (AttachmentTimeline)timelines[i];
 				AttachmentTimeline timeline = (AttachmentTimeline)timelines[i];
-				if (!propertyIDs.add(timeline.slotIndex)) timelineMode[i] |= NOT_LAST;
+				if (!propertyIds.addAll(timeline.getPropertyIds())) timelineMode[i] |= NOT_LAST;
 			}
 			}
 		}
 		}
 	}
 	}

+ 91 - 86
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java

@@ -43,7 +43,6 @@ import com.badlogic.gdx.utils.SerializationException;
 
 
 import com.esotericsoftware.spine.Animation.AttachmentTimeline;
 import com.esotericsoftware.spine.Animation.AttachmentTimeline;
 import com.esotericsoftware.spine.Animation.ColorTimeline;
 import com.esotericsoftware.spine.Animation.ColorTimeline;
-import com.esotericsoftware.spine.Animation.CurveTimeline;
 import com.esotericsoftware.spine.Animation.DeformTimeline;
 import com.esotericsoftware.spine.Animation.DeformTimeline;
 import com.esotericsoftware.spine.Animation.DrawOrderTimeline;
 import com.esotericsoftware.spine.Animation.DrawOrderTimeline;
 import com.esotericsoftware.spine.Animation.EventTimeline;
 import com.esotericsoftware.spine.Animation.EventTimeline;
@@ -51,6 +50,7 @@ import com.esotericsoftware.spine.Animation.IkConstraintTimeline;
 import com.esotericsoftware.spine.Animation.PathConstraintMixTimeline;
 import com.esotericsoftware.spine.Animation.PathConstraintMixTimeline;
 import com.esotericsoftware.spine.Animation.PathConstraintPositionTimeline;
 import com.esotericsoftware.spine.Animation.PathConstraintPositionTimeline;
 import com.esotericsoftware.spine.Animation.PathConstraintSpacingTimeline;
 import com.esotericsoftware.spine.Animation.PathConstraintSpacingTimeline;
+import com.esotericsoftware.spine.Animation.PercentCurveTimeline;
 import com.esotericsoftware.spine.Animation.RotateTimeline;
 import com.esotericsoftware.spine.Animation.RotateTimeline;
 import com.esotericsoftware.spine.Animation.ScaleTimeline;
 import com.esotericsoftware.spine.Animation.ScaleTimeline;
 import com.esotericsoftware.spine.Animation.ShearTimeline;
 import com.esotericsoftware.spine.Animation.ShearTimeline;
@@ -58,6 +58,7 @@ import com.esotericsoftware.spine.Animation.Timeline;
 import com.esotericsoftware.spine.Animation.TransformConstraintTimeline;
 import com.esotericsoftware.spine.Animation.TransformConstraintTimeline;
 import com.esotericsoftware.spine.Animation.TranslateTimeline;
 import com.esotericsoftware.spine.Animation.TranslateTimeline;
 import com.esotericsoftware.spine.Animation.TwoColorTimeline;
 import com.esotericsoftware.spine.Animation.TwoColorTimeline;
+import com.esotericsoftware.spine.Animation.ValueCurveTimeline;
 import com.esotericsoftware.spine.BoneData.TransformMode;
 import com.esotericsoftware.spine.BoneData.TransformMode;
 import com.esotericsoftware.spine.PathConstraintData.PositionMode;
 import com.esotericsoftware.spine.PathConstraintData.PositionMode;
 import com.esotericsoftware.spine.PathConstraintData.RotateMode;
 import com.esotericsoftware.spine.PathConstraintData.RotateMode;
@@ -574,7 +575,6 @@ public class SkeletonBinary {
 	private Animation readAnimation (SkeletonInput input, String name, SkeletonData skeletonData) {
 	private Animation readAnimation (SkeletonInput input, String name, SkeletonData skeletonData) {
 		Array<Timeline> timelines = new Array(32);
 		Array<Timeline> timelines = new Array(32);
 		float scale = this.scale;
 		float scale = this.scale;
-		float duration = 0;
 
 
 		try {
 		try {
 			// Slot timelines.
 			// Slot timelines.
@@ -582,43 +582,37 @@ public class SkeletonBinary {
 				int slotIndex = input.readInt(true);
 				int slotIndex = input.readInt(true);
 				for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) {
 				for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) {
 					int timelineType = input.readByte();
 					int timelineType = input.readByte();
-					int frameCount = input.readInt(true);
+					int frameCount = input.readInt(true), frameLast = frameCount - 1;
 					switch (timelineType) {
 					switch (timelineType) {
 					case SLOT_ATTACHMENT: {
 					case SLOT_ATTACHMENT: {
-						AttachmentTimeline timeline = new AttachmentTimeline(frameCount);
-						timeline.slotIndex = slotIndex;
+						AttachmentTimeline timeline = new AttachmentTimeline(frameCount, slotIndex);
 						for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
 						for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
 							timeline.setFrame(frameIndex, input.readFloat(), input.readStringRef());
 							timeline.setFrame(frameIndex, input.readFloat(), input.readStringRef());
 						timelines.add(timeline);
 						timelines.add(timeline);
-						duration = Math.max(duration, timeline.getFrames()[frameCount - 1]);
 						break;
 						break;
 					}
 					}
 					case SLOT_COLOR: {
 					case SLOT_COLOR: {
-						ColorTimeline timeline = new ColorTimeline(frameCount);
-						timeline.slotIndex = slotIndex;
-						for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
+						ColorTimeline timeline = new ColorTimeline(frameCount, input.readInt(true), slotIndex);
+						for (int frameIndex = 0, bezierIndex = 0; frameIndex < frameCount; frameIndex++) {
 							float time = input.readFloat();
 							float time = input.readFloat();
 							Color.rgba8888ToColor(tempColor1, input.readInt());
 							Color.rgba8888ToColor(tempColor1, input.readInt());
 							timeline.setFrame(frameIndex, time, tempColor1.r, tempColor1.g, tempColor1.b, tempColor1.a);
 							timeline.setFrame(frameIndex, time, tempColor1.r, tempColor1.g, tempColor1.b, tempColor1.a);
-							if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
+							if (frameIndex < frameLast) bezierIndex = readCurve(input, timeline, frameIndex, bezierIndex);
 						}
 						}
 						timelines.add(timeline);
 						timelines.add(timeline);
-						duration = Math.max(duration, timeline.getFrames()[(frameCount - 1) * ColorTimeline.ENTRIES]);
 						break;
 						break;
 					}
 					}
 					case SLOT_TWO_COLOR: {
 					case SLOT_TWO_COLOR: {
-						TwoColorTimeline timeline = new TwoColorTimeline(frameCount);
-						timeline.slotIndex = slotIndex;
-						for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
+						TwoColorTimeline timeline = new TwoColorTimeline(frameCount, input.readInt(true), slotIndex);
+						for (int frameIndex = 0, bezierIndex = 0; frameIndex < frameCount; frameIndex++) {
 							float time = input.readFloat();
 							float time = input.readFloat();
 							Color.rgba8888ToColor(tempColor1, input.readInt());
 							Color.rgba8888ToColor(tempColor1, input.readInt());
 							Color.rgb888ToColor(tempColor2, input.readInt());
 							Color.rgb888ToColor(tempColor2, input.readInt());
 							timeline.setFrame(frameIndex, time, tempColor1.r, tempColor1.g, tempColor1.b, tempColor1.a, tempColor2.r,
 							timeline.setFrame(frameIndex, time, tempColor1.r, tempColor1.g, tempColor1.b, tempColor1.a, tempColor2.r,
 								tempColor2.g, tempColor2.b);
 								tempColor2.g, tempColor2.b);
-							if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
+							if (frameIndex < frameLast) bezierIndex = readCurve(input, timeline, frameIndex, bezierIndex);
 						}
 						}
 						timelines.add(timeline);
 						timelines.add(timeline);
-						duration = Math.max(duration, timeline.getFrames()[(frameCount - 1) * TwoColorTimeline.ENTRIES]);
 						break;
 						break;
 					}
 					}
 					}
 					}
@@ -630,40 +624,44 @@ public class SkeletonBinary {
 				int boneIndex = input.readInt(true);
 				int boneIndex = input.readInt(true);
 				for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) {
 				for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) {
 					int timelineType = input.readByte();
 					int timelineType = input.readByte();
-					int frameCount = input.readInt(true);
+					int frameCount = input.readInt(true), frameLast = frameCount - 1;
 					switch (timelineType) {
 					switch (timelineType) {
 					case BONE_ROTATE: {
 					case BONE_ROTATE: {
-						RotateTimeline timeline = new RotateTimeline(frameCount);
-						timeline.boneIndex = boneIndex;
-						for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
-							timeline.setFrame(frameIndex, input.readFloat(), input.readFloat());
-							if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
+						RotateTimeline timeline = new RotateTimeline(frameCount, input.readInt(true), boneIndex);
+						float value = input.readFloat();
+						for (int frameIndex = 0, bezierIndex = 0; frameIndex < frameCount; frameIndex++) {
+							timeline.setFrame(frameIndex, input.readFloat(), value);
+							if (frameIndex < frameLast)
+								readCurve(input, timeline, frameIndex, bezierIndex, value, value = input.readFloat());
 						}
 						}
 						timelines.add(timeline);
 						timelines.add(timeline);
-						duration = Math.max(duration, timeline.getFrames()[(frameCount - 1) * RotateTimeline.ENTRIES]);
 						break;
 						break;
 					}
 					}
-					case BONE_TRANSLATE:
-					case BONE_SCALE:
-					case BONE_SHEAR: {
-						TranslateTimeline timeline;
-						float timelineScale = 1;
-						if (timelineType == BONE_SCALE)
-							timeline = new ScaleTimeline(frameCount);
-						else if (timelineType == BONE_SHEAR)
-							timeline = new ShearTimeline(frameCount);
-						else {
-							timeline = new TranslateTimeline(frameCount);
-							timelineScale = scale;
+					case BONE_TRANSLATE: {
+						TranslateTimeline timeline = new TranslateTimeline(frameCount, input.readInt(true), boneIndex);
+						for (int frameIndex = 0, bezierIndex = 0; frameIndex < frameCount; frameIndex++) {
+							timeline.setFrame(frameIndex, input.readFloat(), input.readFloat() * scale, input.readFloat() * scale);
+							if (frameIndex < frameLast) bezierIndex = readCurve(input, timeline, frameIndex, bezierIndex);
+						}
+						timelines.add(timeline);
+						break;
+					}
+					case BONE_SCALE: {
+						ScaleTimeline timeline = new ScaleTimeline(frameCount, input.readInt(true), boneIndex);
+						for (int frameIndex = 0, bezierIndex = 0; frameIndex < frameCount; frameIndex++) {
+							timeline.setFrame(frameIndex, input.readFloat(), input.readFloat(), input.readFloat());
+							if (frameIndex < frameLast) bezierIndex = readCurve(input, timeline, frameIndex, bezierIndex);
 						}
 						}
-						timeline.boneIndex = boneIndex;
-						for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
-							timeline.setFrame(frameIndex, input.readFloat(), input.readFloat() * timelineScale,
-								input.readFloat() * timelineScale);
-							if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
+						timelines.add(timeline);
+						break;
+					}
+					case BONE_SHEAR: {
+						ShearTimeline timeline = new ShearTimeline(frameCount, input.readInt(true), boneIndex);
+						for (int frameIndex = 0, bezierIndex = 0; frameIndex < frameCount; frameIndex++) {
+							timeline.setFrame(frameIndex, input.readFloat(), input.readFloat(), input.readFloat());
+							if (frameIndex < frameLast) bezierIndex = readCurve(input, timeline, frameIndex, bezierIndex);
 						}
 						}
 						timelines.add(timeline);
 						timelines.add(timeline);
-						duration = Math.max(duration, timeline.getFrames()[(frameCount - 1) * TranslateTimeline.ENTRIES]);
 						break;
 						break;
 					}
 					}
 					}
 					}
@@ -673,31 +671,27 @@ public class SkeletonBinary {
 			// IK constraint timelines.
 			// IK constraint timelines.
 			for (int i = 0, n = input.readInt(true); i < n; i++) {
 			for (int i = 0, n = input.readInt(true); i < n; i++) {
 				int index = input.readInt(true);
 				int index = input.readInt(true);
-				int frameCount = input.readInt(true);
-				IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount);
-				timeline.ikConstraintIndex = index;
-				for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
+				int frameCount = input.readInt(true), frameLast = frameCount - 1;
+				IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount, input.readInt(true), index);
+				for (int frameIndex = 0, bezierIndex = 0; frameIndex < frameCount; frameIndex++) {
 					timeline.setFrame(frameIndex, input.readFloat(), input.readFloat(), input.readFloat() * scale, input.readByte(),
 					timeline.setFrame(frameIndex, input.readFloat(), input.readFloat(), input.readFloat() * scale, input.readByte(),
 						input.readBoolean(), input.readBoolean());
 						input.readBoolean(), input.readBoolean());
-					if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
+					if (frameIndex < frameLast) bezierIndex = readCurve(input, timeline, frameIndex, bezierIndex);
 				}
 				}
 				timelines.add(timeline);
 				timelines.add(timeline);
-				duration = Math.max(duration, timeline.getFrames()[(frameCount - 1) * IkConstraintTimeline.ENTRIES]);
 			}
 			}
 
 
 			// Transform constraint timelines.
 			// Transform constraint timelines.
 			for (int i = 0, n = input.readInt(true); i < n; i++) {
 			for (int i = 0, n = input.readInt(true); i < n; i++) {
 				int index = input.readInt(true);
 				int index = input.readInt(true);
-				int frameCount = input.readInt(true);
-				TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount);
-				timeline.transformConstraintIndex = index;
-				for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
+				int frameCount = input.readInt(true), frameLast = frameCount - 1;
+				TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount, input.readInt(true), index);
+				for (int frameIndex = 0, bezierIndex = 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());
 						input.readFloat());
-					if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
+					if (frameIndex < frameLast) bezierIndex = readCurve(input, timeline, frameIndex, bezierIndex);
 				}
 				}
 				timelines.add(timeline);
 				timelines.add(timeline);
-				duration = Math.max(duration, timeline.getFrames()[(frameCount - 1) * TransformConstraintTimeline.ENTRIES]);
 			}
 			}
 
 
 			// Path constraint timelines.
 			// Path constraint timelines.
@@ -706,37 +700,38 @@ public class SkeletonBinary {
 				PathConstraintData data = skeletonData.pathConstraints.get(index);
 				PathConstraintData data = skeletonData.pathConstraints.get(index);
 				for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) {
 				for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) {
 					int timelineType = input.readByte();
 					int timelineType = input.readByte();
-					int frameCount = input.readInt(true);
+					int frameCount = input.readInt(true), frameLast = frameCount - 1;
 					switch (timelineType) {
 					switch (timelineType) {
-					case PATH_POSITION:
-					case PATH_SPACING: {
-						PathConstraintPositionTimeline timeline;
-						float timelineScale = 1;
-						if (timelineType == PATH_SPACING) {
-							timeline = new PathConstraintSpacingTimeline(frameCount);
-							if (data.spacingMode == SpacingMode.length || data.spacingMode == SpacingMode.fixed) timelineScale = scale;
-						} else {
-							timeline = new PathConstraintPositionTimeline(frameCount);
-							if (data.positionMode == PositionMode.fixed) timelineScale = scale;
+					case PATH_POSITION: {
+						PathConstraintSpacingTimeline timeline = new PathConstraintSpacingTimeline(frameCount, input.readInt(true),
+							index);
+						float timelineScale = data.spacingMode == SpacingMode.length || data.spacingMode == SpacingMode.fixed ? scale
+							: 1;
+						for (int frameIndex = 0, bezierIndex = 0; frameIndex < frameCount; frameIndex++) {
+							timeline.setFrame(frameIndex, input.readFloat(), input.readFloat() * timelineScale);
+							if (frameIndex < frameLast) bezierIndex = readCurve(input, timeline, frameIndex, bezierIndex);
 						}
 						}
-						timeline.pathConstraintIndex = index;
-						for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
+						timelines.add(timeline);
+						break;
+					}
+					case PATH_SPACING: {
+						PathConstraintPositionTimeline timeline = new PathConstraintPositionTimeline(frameCount, input.readInt(true),
+							index);
+						float timelineScale = data.positionMode == PositionMode.fixed ? scale : 1;
+						for (int frameIndex = 0, bezierIndex = 0; frameIndex < frameCount; frameIndex++) {
 							timeline.setFrame(frameIndex, input.readFloat(), input.readFloat() * timelineScale);
 							timeline.setFrame(frameIndex, input.readFloat(), input.readFloat() * timelineScale);
-							if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
+							if (frameIndex < frameLast) bezierIndex = readCurve(input, timeline, frameIndex, bezierIndex);
 						}
 						}
 						timelines.add(timeline);
 						timelines.add(timeline);
-						duration = Math.max(duration, timeline.getFrames()[(frameCount - 1) * PathConstraintPositionTimeline.ENTRIES]);
 						break;
 						break;
 					}
 					}
 					case PATH_MIX: {
 					case PATH_MIX: {
-						PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frameCount);
-						timeline.pathConstraintIndex = index;
-						for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
+						PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frameCount, input.readInt(true), index);
+						for (int frameIndex = 0, bezierIndex = 0; frameIndex < frameCount; frameIndex++) {
 							timeline.setFrame(frameIndex, input.readFloat(), input.readFloat(), input.readFloat());
 							timeline.setFrame(frameIndex, input.readFloat(), input.readFloat(), input.readFloat());
-							if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
+							if (frameIndex < frameLast) bezierIndex = readCurve(input, timeline, frameIndex, bezierIndex);
 						}
 						}
 						timelines.add(timeline);
 						timelines.add(timeline);
-						duration = Math.max(duration, timeline.getFrames()[(frameCount - 1) * PathConstraintMixTimeline.ENTRIES]);
 						break;
 						break;
 					}
 					}
 					}
 					}
@@ -754,12 +749,10 @@ public class SkeletonBinary {
 						float[] vertices = attachment.getVertices();
 						float[] vertices = attachment.getVertices();
 						int deformLength = weighted ? vertices.length / 3 * 2 : vertices.length;
 						int deformLength = weighted ? vertices.length / 3 * 2 : vertices.length;
 
 
-						int frameCount = input.readInt(true);
-						DeformTimeline timeline = new DeformTimeline(frameCount);
-						timeline.slotIndex = slotIndex;
-						timeline.attachment = attachment;
+						int frameCount = input.readInt(true), frameLast = frameCount - 1;
+						DeformTimeline timeline = new DeformTimeline(frameCount, input.readInt(true), slotIndex, attachment);
 
 
-						for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
+						for (int frameIndex = 0, bezierIndex = 0; frameIndex < frameCount; frameIndex++) {
 							float time = input.readFloat();
 							float time = input.readFloat();
 							float[] deform;
 							float[] deform;
 							int end = input.readInt(true);
 							int end = input.readInt(true);
@@ -783,10 +776,9 @@ public class SkeletonBinary {
 							}
 							}
 
 
 							timeline.setFrame(frameIndex, time, deform);
 							timeline.setFrame(frameIndex, time, deform);
-							if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
+							if (frameIndex < frameLast) bezierIndex = readCurve(input, timeline, frameIndex, bezierIndex);
 						}
 						}
 						timelines.add(timeline);
 						timelines.add(timeline);
-						duration = Math.max(duration, timeline.getFrames()[frameCount - 1]);
 					}
 					}
 				}
 				}
 			}
 			}
@@ -821,7 +813,6 @@ public class SkeletonBinary {
 					timeline.setFrame(i, time, drawOrder);
 					timeline.setFrame(i, time, drawOrder);
 				}
 				}
 				timelines.add(timeline);
 				timelines.add(timeline);
-				duration = Math.max(duration, timeline.getFrames()[drawOrderCount - 1]);
 			}
 			}
 
 
 			// Event timeline.
 			// Event timeline.
@@ -842,29 +833,43 @@ public class SkeletonBinary {
 					timeline.setFrame(i, event);
 					timeline.setFrame(i, event);
 				}
 				}
 				timelines.add(timeline);
 				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);
 			throw new SerializationException("Error reading skeleton file.", ex);
 		}
 		}
 
 
 		timelines.shrink();
 		timelines.shrink();
+		float duration = 0;
+		for (int i = 0, n = timelines.size; i < n; i++)
+			duration = Math.max(duration, timelines.get(i).getDuration());
 		return new Animation(name, timelines, duration);
 		return new Animation(name, timelines, duration);
 	}
 	}
 
 
-	private void readCurve (SkeletonInput input, int frameIndex, CurveTimeline timeline) throws IOException {
+	int readCurve (SkeletonInput input, PercentCurveTimeline timeline, int frameIndex, int bezierIndex) throws IOException {
 		switch (input.readByte()) {
 		switch (input.readByte()) {
 		case CURVE_STEPPED:
 		case CURVE_STEPPED:
 			timeline.setStepped(frameIndex);
 			timeline.setStepped(frameIndex);
 			break;
 			break;
 		case CURVE_BEZIER:
 		case CURVE_BEZIER:
-			setCurve(timeline, frameIndex, input.readFloat(), input.readFloat(), input.readFloat(), input.readFloat());
+			timeline.setBezier(frameIndex, bezierIndex++, input.readFloat(), input.readFloat(), input.readFloat(),
+				input.readFloat());
 			break;
 			break;
 		}
 		}
+		return bezierIndex;
 	}
 	}
 
 
-	void setCurve (CurveTimeline timeline, int frameIndex, float cx1, float cy1, float cx2, float cy2) {
-		timeline.setCurve(frameIndex, cx1, cy1, cx2, cy2);
+	int readCurve (SkeletonInput input, ValueCurveTimeline timeline, int frameIndex, int bezierIndex, float value1, float value2)
+		throws IOException {
+		switch (input.readByte()) {
+		case CURVE_STEPPED:
+			timeline.setStepped(frameIndex);
+			break;
+		case CURVE_BEZIER:
+			timeline.setBezier(frameIndex, bezierIndex++, value1, input.readFloat(), input.readFloat(), input.readFloat(),
+				input.readFloat(), value2);
+			break;
+		}
+		return bezierIndex;
 	}
 	}
 
 
 	static class Vertices {
 	static class Vertices {

+ 4 - 0
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonData.java

@@ -55,6 +55,10 @@ public class SkeletonData {
 
 
 	// --- Bones.
 	// --- Bones.
 
 
+	public SkeletonData () {
+		super();
+	}
+
 	/** The skeleton's bones, sorted parent first. The root bone is always the first bone. */
 	/** The skeleton's bones, sorted parent first. The root bone is always the first bone. */
 	public Array<BoneData> getBones () {
 	public Array<BoneData> getBones () {
 		return bones;
 		return bones;

+ 127 - 119
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java

@@ -43,7 +43,6 @@ import com.badlogic.gdx.utils.SerializationException;
 
 
 import com.esotericsoftware.spine.Animation.AttachmentTimeline;
 import com.esotericsoftware.spine.Animation.AttachmentTimeline;
 import com.esotericsoftware.spine.Animation.ColorTimeline;
 import com.esotericsoftware.spine.Animation.ColorTimeline;
-import com.esotericsoftware.spine.Animation.CurveTimeline;
 import com.esotericsoftware.spine.Animation.DeformTimeline;
 import com.esotericsoftware.spine.Animation.DeformTimeline;
 import com.esotericsoftware.spine.Animation.DrawOrderTimeline;
 import com.esotericsoftware.spine.Animation.DrawOrderTimeline;
 import com.esotericsoftware.spine.Animation.EventTimeline;
 import com.esotericsoftware.spine.Animation.EventTimeline;
@@ -51,6 +50,7 @@ import com.esotericsoftware.spine.Animation.IkConstraintTimeline;
 import com.esotericsoftware.spine.Animation.PathConstraintMixTimeline;
 import com.esotericsoftware.spine.Animation.PathConstraintMixTimeline;
 import com.esotericsoftware.spine.Animation.PathConstraintPositionTimeline;
 import com.esotericsoftware.spine.Animation.PathConstraintPositionTimeline;
 import com.esotericsoftware.spine.Animation.PathConstraintSpacingTimeline;
 import com.esotericsoftware.spine.Animation.PathConstraintSpacingTimeline;
+import com.esotericsoftware.spine.Animation.PercentCurveTimeline;
 import com.esotericsoftware.spine.Animation.RotateTimeline;
 import com.esotericsoftware.spine.Animation.RotateTimeline;
 import com.esotericsoftware.spine.Animation.ScaleTimeline;
 import com.esotericsoftware.spine.Animation.ScaleTimeline;
 import com.esotericsoftware.spine.Animation.ShearTimeline;
 import com.esotericsoftware.spine.Animation.ShearTimeline;
@@ -58,6 +58,7 @@ import com.esotericsoftware.spine.Animation.Timeline;
 import com.esotericsoftware.spine.Animation.TransformConstraintTimeline;
 import com.esotericsoftware.spine.Animation.TransformConstraintTimeline;
 import com.esotericsoftware.spine.Animation.TranslateTimeline;
 import com.esotericsoftware.spine.Animation.TranslateTimeline;
 import com.esotericsoftware.spine.Animation.TwoColorTimeline;
 import com.esotericsoftware.spine.Animation.TwoColorTimeline;
+import com.esotericsoftware.spine.Animation.ValueCurveTimeline;
 import com.esotericsoftware.spine.BoneData.TransformMode;
 import com.esotericsoftware.spine.BoneData.TransformMode;
 import com.esotericsoftware.spine.PathConstraintData.PositionMode;
 import com.esotericsoftware.spine.PathConstraintData.PositionMode;
 import com.esotericsoftware.spine.PathConstraintData.RotateMode;
 import com.esotericsoftware.spine.PathConstraintData.RotateMode;
@@ -500,7 +501,6 @@ public class SkeletonJson {
 	private void readAnimation (JsonValue map, String name, SkeletonData skeletonData) {
 	private void readAnimation (JsonValue map, String name, SkeletonData skeletonData) {
 		float scale = this.scale;
 		float scale = this.scale;
 		Array<Timeline> timelines = new Array();
 		Array<Timeline> timelines = new Array();
-		float duration = 0;
 
 
 		// Slot timelines.
 		// Slot timelines.
 		for (JsonValue slotMap = map.getChild("slots"); slotMap != null; slotMap = slotMap.next) {
 		for (JsonValue slotMap = map.getChild("slots"); slotMap != null; slotMap = slotMap.next) {
@@ -508,45 +508,34 @@ public class SkeletonJson {
 			if (slot == null) throw new SerializationException("Slot not found: " + slotMap.name);
 			if (slot == null) throw new SerializationException("Slot not found: " + slotMap.name);
 			for (JsonValue timelineMap = slotMap.child; timelineMap != null; timelineMap = timelineMap.next) {
 			for (JsonValue timelineMap = slotMap.child; timelineMap != null; timelineMap = timelineMap.next) {
 				String timelineName = timelineMap.name;
 				String timelineName = timelineMap.name;
-				if (timelineName.equals("attachment")) {
-					AttachmentTimeline timeline = new AttachmentTimeline(timelineMap.size);
-					timeline.slotIndex = slot.index;
+				int frameIndex = 0, bezierIndex = 0;
 
 
-					int frameIndex = 0;
-					for (JsonValue valueMap = timelineMap.child; valueMap != null; valueMap = valueMap.next)
-						timeline.setFrame(frameIndex++, valueMap.getFloat("time", 0), valueMap.getString("name"));
+				if (timelineName.equals("attachment")) {
+					AttachmentTimeline timeline = new AttachmentTimeline(timelineMap.size, slot.index);
+					for (JsonValue valueMap = timelineMap.child; valueMap != null; valueMap = valueMap.next, frameIndex++)
+						timeline.setFrame(frameIndex, valueMap.getFloat("time", 0), valueMap.getString("name"));
 					timelines.add(timeline);
 					timelines.add(timeline);
-					duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() - 1]);
 
 
 				} else if (timelineName.equals("color")) {
 				} else if (timelineName.equals("color")) {
-					ColorTimeline timeline = new ColorTimeline(timelineMap.size);
-					timeline.slotIndex = slot.index;
-
-					int frameIndex = 0;
-					for (JsonValue valueMap = timelineMap.child; valueMap != null; valueMap = valueMap.next) {
+					ColorTimeline timeline = new ColorTimeline(timelineMap.size, 0, slot.index);
+					for (JsonValue valueMap = timelineMap.child; valueMap != null; valueMap = valueMap.next, frameIndex++) {
 						Color color = Color.valueOf(valueMap.getString("color"));
 						Color color = Color.valueOf(valueMap.getString("color"));
 						timeline.setFrame(frameIndex, valueMap.getFloat("time", 0), color.r, color.g, color.b, color.a);
 						timeline.setFrame(frameIndex, valueMap.getFloat("time", 0), color.r, color.g, color.b, color.a);
-						readCurve(valueMap, timeline, frameIndex);
-						frameIndex++;
+						bezierIndex = readCurve(valueMap, timeline, frameIndex, bezierIndex);
 					}
 					}
+					timeline.shrink(bezierIndex);
 					timelines.add(timeline);
 					timelines.add(timeline);
-					duration = Math.max(duration, timeline.getFrames()[(timeline.getFrameCount() - 1) * ColorTimeline.ENTRIES]);
 
 
 				} else if (timelineName.equals("twoColor")) {
 				} else if (timelineName.equals("twoColor")) {
-					TwoColorTimeline timeline = new TwoColorTimeline(timelineMap.size);
-					timeline.slotIndex = slot.index;
-
-					int frameIndex = 0;
-					for (JsonValue valueMap = timelineMap.child; valueMap != null; valueMap = valueMap.next) {
-						Color light = Color.valueOf(valueMap.getString("light"));
-						Color dark = Color.valueOf(valueMap.getString("dark"));
-						timeline.setFrame(frameIndex, valueMap.getFloat("time", 0), light.r, light.g, light.b, light.a, dark.r, dark.g,
-							dark.b);
-						readCurve(valueMap, timeline, frameIndex);
-						frameIndex++;
+					TwoColorTimeline timeline = new TwoColorTimeline(timelineMap.size, 0, slot.index);
+					for (JsonValue valueMap = timelineMap.child; valueMap != null; valueMap = valueMap.next, frameIndex++) {
+						Color light = Color.valueOf(valueMap.getString("light")), dark = Color.valueOf(valueMap.getString("dark"));
+						timeline.setFrame(frameIndex, valueMap.getFloat("time", 0), light.r, light.g, light.b, light.a, //
+							dark.r, dark.g, dark.b);
+						bezierIndex = readCurve(valueMap, timeline, frameIndex, bezierIndex);
 					}
 					}
+					timeline.shrink(bezierIndex);
 					timelines.add(timeline);
 					timelines.add(timeline);
-					duration = Math.max(duration, timeline.getFrames()[(timeline.getFrameCount() - 1) * TwoColorTimeline.ENTRIES]);
 
 
 				} else
 				} else
 					throw new RuntimeException("Invalid timeline type for a slot: " + timelineName + " (" + slotMap.name + ")");
 					throw new RuntimeException("Invalid timeline type for a slot: " + timelineName + " (" + slotMap.name + ")");
@@ -558,43 +547,53 @@ public class SkeletonJson {
 			BoneData bone = skeletonData.findBone(boneMap.name);
 			BoneData bone = skeletonData.findBone(boneMap.name);
 			if (bone == null) throw new SerializationException("Bone not found: " + boneMap.name);
 			if (bone == null) throw new SerializationException("Bone not found: " + boneMap.name);
 			for (JsonValue timelineMap = boneMap.child; timelineMap != null; timelineMap = timelineMap.next) {
 			for (JsonValue timelineMap = boneMap.child; timelineMap != null; timelineMap = timelineMap.next) {
+				JsonValue valueMap = timelineMap.child;
+				if (valueMap == null) continue;
 				String timelineName = timelineMap.name;
 				String timelineName = timelineMap.name;
+				int frameIndex = 0, bezierIndex = 0;
+
 				if (timelineName.equals("rotate")) {
 				if (timelineName.equals("rotate")) {
-					RotateTimeline timeline = new RotateTimeline(timelineMap.size);
-					timeline.boneIndex = bone.index;
-
-					int frameIndex = 0;
-					for (JsonValue valueMap = timelineMap.child; valueMap != null; valueMap = valueMap.next) {
-						timeline.setFrame(frameIndex, valueMap.getFloat("time", 0), valueMap.getFloat("angle", 0));
-						readCurve(valueMap, timeline, frameIndex);
-						frameIndex++;
+					RotateTimeline timeline = new RotateTimeline(timelineMap.size, 0, bone.index);
+					float rotation = valueMap.getFloat("angle", 0);
+					for (;; frameIndex++) {
+						timeline.setFrame(frameIndex, valueMap.getFloat("time", 0), rotation);
+						valueMap = valueMap.next;
+						if (valueMap == null) break;
+						bezierIndex = readCurve(valueMap, timeline, frameIndex, bezierIndex, rotation,
+							rotation = valueMap.getFloat("angle", 0));
+					}
+					timeline.shrink(bezierIndex);
+					timelines.add(timeline);
+
+				} else if (timelineName.equals("translate")) {
+					TranslateTimeline timeline = new TranslateTimeline(timelineMap.size, 0, bone.index);
+					for (; valueMap != null; valueMap = valueMap.next, frameIndex++) {
+						float x = valueMap.getFloat("x", 0), y = valueMap.getFloat("y", 0);
+						timeline.setFrame(frameIndex, valueMap.getFloat("time", 0), x * scale, y * scale);
+						bezierIndex = readCurve(valueMap, timeline, frameIndex, bezierIndex);
 					}
 					}
+					timeline.shrink(bezierIndex);
 					timelines.add(timeline);
 					timelines.add(timeline);
-					duration = Math.max(duration, timeline.getFrames()[(timeline.getFrameCount() - 1) * RotateTimeline.ENTRIES]);
-
-				} else if (timelineName.equals("translate") || timelineName.equals("scale") || timelineName.equals("shear")) {
-					TranslateTimeline timeline;
-					float timelineScale = 1, defaultValue = 0;
-					if (timelineName.equals("scale")) {
-						timeline = new ScaleTimeline(timelineMap.size);
-						defaultValue = 1;
-					} else if (timelineName.equals("shear"))
-						timeline = new ShearTimeline(timelineMap.size);
-					else {
-						timeline = new TranslateTimeline(timelineMap.size);
-						timelineScale = scale;
+
+				} else if (timelineName.equals("scale")) {
+					ScaleTimeline timeline = new ScaleTimeline(timelineMap.size, 0, bone.index);
+					for (; valueMap != null; valueMap = valueMap.next, frameIndex++) {
+						float x = valueMap.getFloat("x", 1), y = valueMap.getFloat("y", 1);
+						timeline.setFrame(frameIndex, valueMap.getFloat("time", 0), x, y);
+						bezierIndex = readCurve(valueMap, timeline, frameIndex, bezierIndex);
 					}
 					}
-					timeline.boneIndex = bone.index;
-
-					int frameIndex = 0;
-					for (JsonValue valueMap = timelineMap.child; valueMap != null; valueMap = valueMap.next) {
-						float x = valueMap.getFloat("x", defaultValue), y = valueMap.getFloat("y", defaultValue);
-						timeline.setFrame(frameIndex, valueMap.getFloat("time", 0), x * timelineScale, y * timelineScale);
-						readCurve(valueMap, timeline, frameIndex);
-						frameIndex++;
+					timeline.shrink(bezierIndex);
+					timelines.add(timeline);
+
+				} else if (timelineName.equals("shear")) {
+					ShearTimeline timeline = new ShearTimeline(timelineMap.size, 0, bone.index);
+					for (; valueMap != null; valueMap = valueMap.next, frameIndex++) {
+						float x = valueMap.getFloat("x", 0), y = valueMap.getFloat("y", 0);
+						timeline.setFrame(frameIndex, valueMap.getFloat("time", 0), x, y);
+						bezierIndex = readCurve(valueMap, timeline, frameIndex, bezierIndex);
 					}
 					}
+					timeline.shrink(bezierIndex);
 					timelines.add(timeline);
 					timelines.add(timeline);
-					duration = Math.max(duration, timeline.getFrames()[(timeline.getFrameCount() - 1) * TranslateTimeline.ENTRIES]);
 
 
 				} else
 				} else
 					throw new RuntimeException("Invalid timeline type for a bone: " + timelineName + " (" + boneMap.name + ")");
 					throw new RuntimeException("Invalid timeline type for a bone: " + timelineName + " (" + boneMap.name + ")");
@@ -604,35 +603,32 @@ public class SkeletonJson {
 		// IK constraint timelines.
 		// IK constraint timelines.
 		for (JsonValue constraintMap = map.getChild("ik"); constraintMap != null; constraintMap = constraintMap.next) {
 		for (JsonValue constraintMap = map.getChild("ik"); constraintMap != null; constraintMap = constraintMap.next) {
 			IkConstraintData constraint = skeletonData.findIkConstraint(constraintMap.name);
 			IkConstraintData constraint = skeletonData.findIkConstraint(constraintMap.name);
-			IkConstraintTimeline timeline = new IkConstraintTimeline(constraintMap.size);
-			timeline.ikConstraintIndex = skeletonData.getIkConstraints().indexOf(constraint, true);
-			int frameIndex = 0;
-			for (JsonValue valueMap = constraintMap.child; valueMap != null; valueMap = valueMap.next) {
+			IkConstraintTimeline timeline = new IkConstraintTimeline(constraintMap.size, 0,
+				skeletonData.getIkConstraints().indexOf(constraint, true));
+			int frameIndex = 0, bezierIndex = 0;
+			for (JsonValue valueMap = constraintMap.child; valueMap != null; valueMap = valueMap.next, frameIndex++) {
 				timeline.setFrame(frameIndex, valueMap.getFloat("time", 0), valueMap.getFloat("mix", 1),
 				timeline.setFrame(frameIndex, valueMap.getFloat("time", 0), valueMap.getFloat("mix", 1),
 					valueMap.getFloat("softness", 0) * scale, valueMap.getBoolean("bendPositive", true) ? 1 : -1,
 					valueMap.getFloat("softness", 0) * scale, valueMap.getBoolean("bendPositive", true) ? 1 : -1,
 					valueMap.getBoolean("compress", false), valueMap.getBoolean("stretch", false));
 					valueMap.getBoolean("compress", false), valueMap.getBoolean("stretch", false));
-				readCurve(valueMap, timeline, frameIndex);
-				frameIndex++;
+				bezierIndex = readCurve(valueMap, timeline, frameIndex, bezierIndex);
 			}
 			}
+			timeline.shrink(bezierIndex);
 			timelines.add(timeline);
 			timelines.add(timeline);
-			duration = Math.max(duration, timeline.getFrames()[(timeline.getFrameCount() - 1) * IkConstraintTimeline.ENTRIES]);
 		}
 		}
 
 
 		// Transform constraint timelines.
 		// Transform constraint timelines.
 		for (JsonValue constraintMap = map.getChild("transform"); constraintMap != null; constraintMap = constraintMap.next) {
 		for (JsonValue constraintMap = map.getChild("transform"); constraintMap != null; constraintMap = constraintMap.next) {
 			TransformConstraintData constraint = skeletonData.findTransformConstraint(constraintMap.name);
 			TransformConstraintData constraint = skeletonData.findTransformConstraint(constraintMap.name);
-			TransformConstraintTimeline timeline = new TransformConstraintTimeline(constraintMap.size);
-			timeline.transformConstraintIndex = skeletonData.getTransformConstraints().indexOf(constraint, true);
-			int frameIndex = 0;
-			for (JsonValue valueMap = constraintMap.child; valueMap != null; valueMap = valueMap.next) {
+			TransformConstraintTimeline timeline = new TransformConstraintTimeline(constraintMap.size, 0,
+				skeletonData.getTransformConstraints().indexOf(constraint, true));
+			int frameIndex = 0, bezierIndex = 0;
+			for (JsonValue valueMap = constraintMap.child; valueMap != null; valueMap = valueMap.next, frameIndex++) {
 				timeline.setFrame(frameIndex, valueMap.getFloat("time", 0), valueMap.getFloat("rotateMix", 1),
 				timeline.setFrame(frameIndex, valueMap.getFloat("time", 0), valueMap.getFloat("rotateMix", 1),
 					valueMap.getFloat("translateMix", 1), valueMap.getFloat("scaleMix", 1), valueMap.getFloat("shearMix", 1));
 					valueMap.getFloat("translateMix", 1), valueMap.getFloat("scaleMix", 1), valueMap.getFloat("shearMix", 1));
-				readCurve(valueMap, timeline, frameIndex);
-				frameIndex++;
+				bezierIndex = readCurve(valueMap, timeline, frameIndex, bezierIndex);
 			}
 			}
+			timeline.shrink(bezierIndex);
 			timelines.add(timeline);
 			timelines.add(timeline);
-			duration = Math.max(duration,
-				timeline.getFrames()[(timeline.getFrameCount() - 1) * TransformConstraintTimeline.ENTRIES]);
 		}
 		}
 
 
 		// Path constraint timelines.
 		// Path constraint timelines.
@@ -642,39 +638,37 @@ public class SkeletonJson {
 			int index = skeletonData.pathConstraints.indexOf(data, true);
 			int index = skeletonData.pathConstraints.indexOf(data, true);
 			for (JsonValue timelineMap = constraintMap.child; timelineMap != null; timelineMap = timelineMap.next) {
 			for (JsonValue timelineMap = constraintMap.child; timelineMap != null; timelineMap = timelineMap.next) {
 				String timelineName = timelineMap.name;
 				String timelineName = timelineMap.name;
-				if (timelineName.equals("position") || timelineName.equals("spacing")) {
-					PathConstraintPositionTimeline timeline;
-					float timelineScale = 1;
-					if (timelineName.equals("spacing")) {
-						timeline = new PathConstraintSpacingTimeline(timelineMap.size);
-						if (data.spacingMode == SpacingMode.length || data.spacingMode == SpacingMode.fixed) timelineScale = scale;
-					} else {
-						timeline = new PathConstraintPositionTimeline(timelineMap.size);
-						if (data.positionMode == PositionMode.fixed) timelineScale = scale;
+				int frameIndex = 0, bezierIndex = 0;
+
+				if (timelineName.equals("position")) {
+					PathConstraintPositionTimeline timeline = new PathConstraintPositionTimeline(timelineMap.size, 0, index);
+					float timelineScale = data.positionMode == PositionMode.fixed ? scale : 1;
+					for (JsonValue valueMap = timelineMap.child; valueMap != null; valueMap = valueMap.next, frameIndex++) {
+						timeline.setFrame(frameIndex, valueMap.getFloat("time", 0), valueMap.getFloat(timelineName, 0) * timelineScale);
+						bezierIndex = readCurve(valueMap, timeline, frameIndex, bezierIndex);
 					}
 					}
-					timeline.pathConstraintIndex = index;
-					int frameIndex = 0;
-					for (JsonValue valueMap = timelineMap.child; valueMap != null; valueMap = valueMap.next) {
+					timeline.shrink(bezierIndex);
+					timelines.add(timeline);
+
+				} else if (timelineName.equals("spacing")) {
+					PathConstraintSpacingTimeline timeline = new PathConstraintSpacingTimeline(timelineMap.size, 0, index);
+					float timelineScale = data.spacingMode == SpacingMode.length || data.spacingMode == SpacingMode.fixed ? scale : 1;
+					for (JsonValue valueMap = timelineMap.child; valueMap != null; valueMap = valueMap.next, frameIndex++) {
 						timeline.setFrame(frameIndex, valueMap.getFloat("time", 0), valueMap.getFloat(timelineName, 0) * timelineScale);
 						timeline.setFrame(frameIndex, valueMap.getFloat("time", 0), valueMap.getFloat(timelineName, 0) * timelineScale);
-						readCurve(valueMap, timeline, frameIndex);
-						frameIndex++;
+						bezierIndex = readCurve(valueMap, timeline, frameIndex, bezierIndex);
 					}
 					}
+					timeline.shrink(bezierIndex);
 					timelines.add(timeline);
 					timelines.add(timeline);
-					duration = Math.max(duration,
-						timeline.getFrames()[(timeline.getFrameCount() - 1) * PathConstraintPositionTimeline.ENTRIES]);
+
 				} else if (timelineName.equals("mix")) {
 				} else if (timelineName.equals("mix")) {
-					PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(timelineMap.size);
-					timeline.pathConstraintIndex = index;
-					int frameIndex = 0;
-					for (JsonValue valueMap = timelineMap.child; valueMap != null; valueMap = valueMap.next) {
+					PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(timelineMap.size, 0, index);
+					for (JsonValue valueMap = timelineMap.child; valueMap != null; valueMap = valueMap.next, frameIndex++) {
 						timeline.setFrame(frameIndex, valueMap.getFloat("time", 0), valueMap.getFloat("rotateMix", 1),
 						timeline.setFrame(frameIndex, valueMap.getFloat("time", 0), valueMap.getFloat("rotateMix", 1),
 							valueMap.getFloat("translateMix", 1));
 							valueMap.getFloat("translateMix", 1));
-						readCurve(valueMap, timeline, frameIndex);
-						frameIndex++;
+						bezierIndex = readCurve(valueMap, timeline, frameIndex, bezierIndex);
 					}
 					}
+					timeline.shrink(bezierIndex);
 					timelines.add(timeline);
 					timelines.add(timeline);
-					duration = Math.max(duration,
-						timeline.getFrames()[(timeline.getFrameCount() - 1) * PathConstraintMixTimeline.ENTRIES]);
 				}
 				}
 			}
 			}
 		}
 		}
@@ -693,12 +687,9 @@ public class SkeletonJson {
 					float[] vertices = attachment.getVertices();
 					float[] vertices = attachment.getVertices();
 					int deformLength = weighted ? vertices.length / 3 * 2 : vertices.length;
 					int deformLength = weighted ? vertices.length / 3 * 2 : vertices.length;
 
 
-					DeformTimeline timeline = new DeformTimeline(timelineMap.size);
-					timeline.slotIndex = slot.index;
-					timeline.attachment = attachment;
-
-					int frameIndex = 0;
-					for (JsonValue valueMap = timelineMap.child; valueMap != null; valueMap = valueMap.next) {
+					DeformTimeline timeline = new DeformTimeline(timelineMap.size, 0, slot.index, attachment);
+					int frameIndex = 0, bezierIndex = 0;
+					for (JsonValue valueMap = timelineMap.child; valueMap != null; valueMap = valueMap.next, frameIndex++) {
 						float[] deform;
 						float[] deform;
 						JsonValue verticesValue = valueMap.get("vertices");
 						JsonValue verticesValue = valueMap.get("vertices");
 						if (verticesValue == null)
 						if (verticesValue == null)
@@ -718,11 +709,10 @@ public class SkeletonJson {
 						}
 						}
 
 
 						timeline.setFrame(frameIndex, valueMap.getFloat("time", 0), deform);
 						timeline.setFrame(frameIndex, valueMap.getFloat("time", 0), deform);
-						readCurve(valueMap, timeline, frameIndex);
-						frameIndex++;
+						bezierIndex = readCurve(valueMap, timeline, frameIndex, bezierIndex);
 					}
 					}
+					timeline.shrink(bezierIndex);
 					timelines.add(timeline);
 					timelines.add(timeline);
-					duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() - 1]);
 				}
 				}
 			}
 			}
 		}
 		}
@@ -734,7 +724,7 @@ public class SkeletonJson {
 			DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrdersMap.size);
 			DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrdersMap.size);
 			int slotCount = skeletonData.slots.size;
 			int slotCount = skeletonData.slots.size;
 			int frameIndex = 0;
 			int frameIndex = 0;
-			for (JsonValue drawOrderMap = drawOrdersMap.child; drawOrderMap != null; drawOrderMap = drawOrderMap.next) {
+			for (JsonValue drawOrderMap = drawOrdersMap.child; drawOrderMap != null; drawOrderMap = drawOrderMap.next, frameIndex++) {
 				int[] drawOrder = null;
 				int[] drawOrder = null;
 				JsonValue offsets = drawOrderMap.get("offsets");
 				JsonValue offsets = drawOrderMap.get("offsets");
 				if (offsets != null) {
 				if (offsets != null) {
@@ -759,10 +749,9 @@ public class SkeletonJson {
 					for (int i = slotCount - 1; i >= 0; i--)
 					for (int i = slotCount - 1; i >= 0; i--)
 						if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex];
 						if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex];
 				}
 				}
-				timeline.setFrame(frameIndex++, drawOrderMap.getFloat("time", 0), drawOrder);
+				timeline.setFrame(frameIndex, drawOrderMap.getFloat("time", 0), drawOrder);
 			}
 			}
 			timelines.add(timeline);
 			timelines.add(timeline);
-			duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() - 1]);
 		}
 		}
 
 
 		// Event timeline.
 		// Event timeline.
@@ -770,7 +759,7 @@ public class SkeletonJson {
 		if (eventsMap != null) {
 		if (eventsMap != null) {
 			EventTimeline timeline = new EventTimeline(eventsMap.size);
 			EventTimeline timeline = new EventTimeline(eventsMap.size);
 			int frameIndex = 0;
 			int frameIndex = 0;
-			for (JsonValue eventMap = eventsMap.child; eventMap != null; eventMap = eventMap.next) {
+			for (JsonValue eventMap = eventsMap.child; eventMap != null; eventMap = eventMap.next, frameIndex++) {
 				EventData eventData = skeletonData.findEvent(eventMap.getString("name"));
 				EventData eventData = skeletonData.findEvent(eventMap.getString("name"));
 				if (eventData == null) throw new SerializationException("Event not found: " + eventMap.getString("name"));
 				if (eventData == null) throw new SerializationException("Event not found: " + eventMap.getString("name"));
 				Event event = new Event(eventMap.getFloat("time", 0), eventData);
 				Event event = new Event(eventMap.getFloat("time", 0), eventData);
@@ -781,23 +770,42 @@ public class SkeletonJson {
 					event.volume = eventMap.getFloat("volume", eventData.volume);
 					event.volume = eventMap.getFloat("volume", eventData.volume);
 					event.balance = eventMap.getFloat("balance", eventData.balance);
 					event.balance = eventMap.getFloat("balance", eventData.balance);
 				}
 				}
-				timeline.setFrame(frameIndex++, event);
+				timeline.setFrame(frameIndex, event);
 			}
 			}
 			timelines.add(timeline);
 			timelines.add(timeline);
-			duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() - 1]);
 		}
 		}
 
 
 		timelines.shrink();
 		timelines.shrink();
+		float duration = 0;
+		for (int i = 0, n = timelines.size; i < n; i++)
+			duration = Math.max(duration, timelines.get(i).getDuration());
 		skeletonData.animations.add(new Animation(name, timelines, duration));
 		skeletonData.animations.add(new Animation(name, timelines, duration));
 	}
 	}
 
 
-	void readCurve (JsonValue map, CurveTimeline timeline, int frameIndex) {
+	int readCurve (JsonValue map, PercentCurveTimeline timeline, int frameIndex, int bezierIndex) {
 		JsonValue curve = map.get("curve");
 		JsonValue curve = map.get("curve");
-		if (curve == null) return;
-		if (curve.isString())
-			timeline.setStepped(frameIndex);
-		else
-			timeline.setCurve(frameIndex, curve.asFloat(), map.getFloat("c2", 0), map.getFloat("c3", 1), map.getFloat("c4", 1));
+		if (curve != null) {
+			if (curve.isString())
+				timeline.setStepped(frameIndex);
+			else {
+				timeline.setBezier(frameIndex, bezierIndex++, curve.asFloat(), map.getFloat("c2", 0), map.getFloat("c3", 1),
+					map.getFloat("c4", 1));
+			}
+		}
+		return bezierIndex;
+	}
+
+	int readCurve (JsonValue map, ValueCurveTimeline timeline, int frameIndex, int bezierIndex, float value1, float value2) {
+		JsonValue curve = map.get("curve");
+		if (curve != null) {
+			if (curve.isString())
+				timeline.setStepped(frameIndex);
+			else {
+				timeline.setBezier(frameIndex, bezierIndex++, value1, curve.asFloat(), map.getFloat("c2", 0), map.getFloat("c3", 1),
+					map.getFloat("c4", 1), value2);
+			}
+		}
+		return bezierIndex;
 	}
 	}
 
 
 	static class LinkedMesh {
 	static class LinkedMesh {

+ 1 - 1
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/VertexAttachment.java

@@ -42,7 +42,7 @@ import com.esotericsoftware.spine.Slot;
 abstract public class VertexAttachment extends Attachment {
 abstract public class VertexAttachment extends Attachment {
 	static private int nextID;
 	static private int nextID;
 
 
-	private final int id = (nextID() & 65535) << 11;
+	private final int id = nextID();
 	int[] bones;
 	int[] bones;
 	float[] vertices;
 	float[] vertices;
 	int worldVerticesLength;
 	int worldVerticesLength;

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio