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 0a47d4fed..67c318842 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -41,7 +41,7 @@ import com.badlogic.gdx.utils.ObjectSet; import com.esotericsoftware.spine.attachments.Attachment; import com.esotericsoftware.spine.attachments.VertexAttachment; -/** A simple container for a list of timelines and a name. */ +/** Stores a list of timelines to animate a skeleton's pose over time. */ public class Animation { final String name; Array timelines; @@ -69,14 +69,15 @@ public class Animation { timelineIds.addAll(timeline.getPropertyIds()); } - /** Return true if this animation contains a timeline with any of the specified property IDs. **/ + /** Returns true if this animation contains a timeline with any of the specified property IDs. **/ public boolean hasTimeline (String[] propertyIds) { for (String id : propertyIds) if (timelineIds.contains(id)) return true; return false; } - /** The duration of the animation in seconds, which is the highest time of all keys in the timeline. */ + /** The duration of the animation in seconds, which is usually the highest time of all frames in the timeline. The duration is + * used to know when it has completed and when it should loop back to the start. */ public float getDuration () { return duration; } @@ -85,11 +86,26 @@ public class Animation { this.duration = duration; } - /** Applies all the animation's timelines to the specified skeleton. + /** Applies the animation's timelines to the specified skeleton. *

* See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, MixBlend, MixDirection)}. - * @param loop If true, the animation repeats after {@link #getDuration()}. - * @param events May be null to ignore fired events. */ + * @param skeleton The skeleton the animation is being applied to. This provides access to the bones, slots, and other skeleton + * components the timelines may change. + * @param lastTime The last time in seconds this animation was applied. Some timelines trigger only at specific times rather + * than every frame. Pass -1 the first time an animation is applied to ensure frame 0 is triggered. + * @param time The time in seconds the skeleton is being posed for. Most timelines find the frame before and the frame after + * this time and interpolate between the frame values. If beyond the {@link #getDuration()} and loop is + * true then the animation will repeat, else the last frame will be applied. + * @param loop If true, the animation repeats after the {@link #getDuration()}. + * @param events If any events are fired, they are added to this list. Can be null to ignore fired events or if no timelines + * fire events. + * @param alpha 0 applies the current or setup values (depending on blend). 1 applies the timeline values. Between + * 0 and 1 applies values between the current or setup values and the timeline values. By adjusting + * alpha over time, an animation can be mixed in or out. alpha can also be useful to apply + * animations on top of each other (layering). + * @param blend Controls how mixing is applied when alpha < 1. + * @param direction Indicates whether the timelines are mixing in or out. Used by timelines which perform instant transitions, + * such as {@link DrawOrderTimeline} or {@link AttachmentTimeline}. */ public void apply (Skeleton skeleton, float lastTime, float time, boolean loop, Array events, float alpha, MixBlend blend, MixDirection direction) { if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); @@ -99,9 +115,9 @@ public class Animation { if (lastTime > 0) lastTime %= duration; } - Array timelines = this.timelines; - for (int i = 0, n = timelines.size; i < n; i++) - timelines.get(i).apply(skeleton, lastTime, time, events, alpha, blend, direction); + Object[] timelines = this.timelines.items; + for (int i = 0, n = this.timelines.size; i < n; i++) + ((Timeline)timelines[i]).apply(skeleton, lastTime, time, events, alpha, blend, direction); } /** The animation's name, which is unique across all animations in the skeleton. */ @@ -114,101 +130,55 @@ public class Animation { } /** Binary search using a stride of 1. - * @param target >= the first value. + * @param time >= the first value. * @return The index of the first value <= to the target. */ - static int search (float[] values, float target) { - int n = values.length; + static int search (float[] frames, float time) { + int n = frames.length; for (int i = 1; i < n; i++) - if (values[i] > target) return i - 1; + if (frames[i] > time) return i - 1; return n - 1; } - /** Binary search using a stride of 2. - * @param target >= the first value and < the last value. - * @return The index / 2 of the first value <= to the target. */ - static int search2 (float[] values, float target) { - int last = values.length - 4; - for (int i = 2; i <= last; i += 2) - if (values[i] > target) return (i >> 1) - 1; - return last >> 1; - } - /** Binary search using the specified stride. - * @param target >= the first and < the last value. - * @return The index / step of the first value <= to the target. */ - static int searchN (float[] values, float target, int step) { - int last = values.length - (step << 1); - for (int i = step; i <= last; i += step) - if (values[i] > target) return i / step - 1; - return last / step; + * @param time >= the first value. + * @return The index of the first value <= to the target. */ + static int search (float[] frames, float time, int step) { + int n = frames.length; + for (int i = step; i < n; i += step) + if (frames[i] > time) return i - step; + return n - step; } - static int search_binary (float[] values, float target) { - int low = 0, high = values.length - 1, current; - while (true) { - if (low == high) return low; - current = ((low + high) >>> 1) + 1; - if (values[current] <= target) - low = current; - else - high = current - 1; - } - } - - static int search2_binary (float[] values, float target) { - int low = 0, high = (values.length >> 1) - 2, current; - while (true) { - if (low == high) return low; - current = ((low + high) >>> 1) + 1; - if (values[current << 1] <= target) - low = current; - else - high = current - 1; - } - } - - static int searchN_binary (float[] values, float target, int step) { - int low = 0, high = values.length / step - 2, current; - while (true) { - if (low >= high) return low; - current = ((low + high) >>> 1) + 1; - if (values[current * step] <= target) - low = current; - else - high = current - 1; - } - } - - /** Controls how a timeline value is mixed with the setup pose value or current pose value when a timeline's alpha - * < 1. + /** Controls how timeline values are mixed with setup pose values or current pose values when a timeline is applied with + * alpha < 1. *

* See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, MixBlend, MixDirection)}. */ static public enum MixBlend { - /** Transitions from the setup value to the timeline value (the current value is not used). Before the first key, the setup - * value is set. */ + /** Transitions from the setup value to the timeline value (the current value is not used). Before the first frame, the + * setup value is set. */ setup, - /** Transitions from the current value to the timeline value. Before the first key, transitions from the current value to + /** Transitions from the current value to the timeline value. Before the first frame, transitions from the current value to * the setup value. Timelines which perform instant transitions, such as {@link DrawOrderTimeline} or - * {@link AttachmentTimeline}, use the setup value before the first key. + * {@link AttachmentTimeline}, use the setup value before the first frame. *

* first is intended for the first animations applied, not for animations layered on top of those. */ first, - /** Transitions from the current value to the timeline value. No change is made before the first key (the current value is - * kept until the first key). + /** Transitions from the current value to the timeline value. No change is made before the first frame (the current value is + * kept until the first frame). *

* replace is intended for animations layered on top of others, not for the first animations applied. */ replace, - /** Transitions from the current value to the current value plus the timeline value. No change is made before the first key - * (the current value is kept until the first key). + /** Transitions from the current value to the current value plus the timeline value. No change is made before the first + * frame (the current value is kept until the first frame). *

* add is intended for animations layered on top of others, not for the first animations applied. Properties - * keyed by additive animations must be set manually or by another animation before applying the additive animations, else - * the property values will increase continually. */ + * set by additive animations must be set manually or by another animation before applying the additive animations, else the + * property values will increase each time the additive animations are applied. */ add } /** Indicates whether a timeline's alpha is mixing out over time toward 0 (the setup or current pose value) or - * mixing in toward 1 (the timeline's value). + * mixing in toward 1 (the timeline's value). Some timelines use this to decide how values are applied. *

* See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, MixBlend, MixDirection)}. */ static public enum MixDirection { @@ -217,7 +187,7 @@ public class Animation { static enum Property { rotate, translateX, translateY, scaleX, scaleY, shearX, shearY, // - rgb, a, rgb2, // + rgba, rgb2, // attachment, deform, // event, drawOrder, // ikConstraint, transformConstraint, // @@ -229,12 +199,11 @@ public class Animation { private final String[] propertyIds; final float[] frames; - /** @param frameEntries The number of entries stored per frame. - * @param propertyIds Unique identifiers for the properties the timeline modifies. */ - public Timeline (int frameCount, int frameEntries, String... propertyIds) { + /** @param propertyIds Unique identifiers for the properties the timeline modifies. */ + public Timeline (int frameCount, String... propertyIds) { if (propertyIds == null) throw new IllegalArgumentException("propertyIds cannot be null."); this.propertyIds = propertyIds; - frames = new float[frameCount * frameEntries]; + frames = new float[frameCount * getFrameEntries()]; } /** Uniquely encodes both the type of this timeline and the skeleton property that it affects. */ @@ -242,26 +211,35 @@ public class Animation { return propertyIds; } - /** The time in seconds and any other values for each key frame. */ + /** The time in seconds and any other values for each frame. */ public float[] getFrames () { return frames; } - public float getDuration () { - return frames[frames.length - frames.length / getFrameCount()]; + /** The number of entries stored per frame. */ + public int getFrameEntries () { + return 1; } - /** The number of key frames for this timeline. */ - abstract public int getFrameCount (); + /** The number of frames for this timeline. */ + public int getFrameCount () { + return frames.length / getFrameEntries(); + } + + public float getDuration () { + return frames[frames.length - getFrameEntries()]; + } /** Applies this timeline to the skeleton. - * @param skeleton The skeleton the timeline is being applied to. This provides access to the bones, slots, and other - * skeleton components the timeline may change. - * @param lastTime The time this timeline was last applied. Timelines such as {@link EventTimeline} trigger only at specific - * times rather than every frame. In that case, the timeline triggers everything between lastTime - * (exclusive) and time (inclusive). - * @param time The time within the animation. Most timelines find the key before and the key after this time so they can - * interpolate between the keys. + * @param skeleton The skeleton to which the timeline is being applied. This provides access to the bones, slots, and other + * skeleton components that the timeline may change. + * @param lastTime The last time in seconds this timeline was applied. Timelines such as {@link EventTimeline} trigger only + * at specific times rather than every frame. In that case, the timeline triggers everything between + * lastTime (exclusive) and time (inclusive). Pass -1 the first time an animation is + * applied to ensure frame 0 is triggered. + * @param time The time in seconds that the skeleton is being posed for. Most timelines find the frame before and the frame + * after this time and interpolate between the frame values. If beyond the last frame, the last frame will be + * applied. * @param events If any events are fired, they are added to this list. Can be null to ignore fired events or if the timeline * does not fire events. * @param alpha 0 applies the current or setup value (depending on blend). 1 applies the timeline value. @@ -277,187 +255,79 @@ public class Animation { /** An interface for timelines which change the property of a bone. */ static public interface BoneTimeline { - /** The index of the bone in {@link Skeleton#getBones()} that will be changed. */ + /** The index of the bone in {@link Skeleton#getBones()} that will be changed when this timeline is applied. */ public int getBoneIndex (); } /** An interface for timelines which change the property of a slot. */ static public interface SlotTimeline { - /** The index of the slot in {@link Skeleton#getSlots()} that will be changed. */ + /** The index of the slot in {@link Skeleton#getSlots()} that will be changed when this timeline is applied. */ public int getSlotIndex (); } - /** The base class for timelines that use interpolation between key frames. */ + /** The base class for timelines that interpolate between frame values using stepped, linear, or a Bezier curve. */ static public abstract class CurveTimeline extends Timeline { - static public final int LINEAR = 0, STEPPED = 1, BEZIER = 2; - static final int BEZIER_SIZE = 18; + static public final int LINEAR = 0, STEPPED = 1, BEZIER = 2, BEZIER_SIZE = 18; float[] curves; - /** @param frameEntries The number of entries stored per frame. - * @param bezierCount The maximum number of frames that will use Bezier curves. See {@link #shrink(int)}. + /** @param bezierCount The maximum number of Bezier curves. See {@link #shrink(int)}. * @param propertyIds Unique identifiers for the properties the timeline modifies. */ - public CurveTimeline (int frameCount, int frameEntries, int bezierCount, String... propertyIds) { - super(frameCount, frameEntries, propertyIds); - curves = new float[frameCount - 1 + bezierCount * BEZIER_SIZE]; + public CurveTimeline (int frameCount, int bezierCount, String... propertyIds) { + super(frameCount, propertyIds); + curves = new float[frameCount + bezierCount * BEZIER_SIZE]; + curves[frameCount - 1] = STEPPED; } - /** Sets the specified key frame to linear interpolation. - * @param frameIndex Between 0 and frameCount - 1. */ - public void setLinear (int frameIndex) { - curves[frameIndex] = LINEAR; + /** Sets the specified frame to linear interpolation. + * @param frame Between 0 and frameCount - 1. */ + public void setLinear (int frame) { + curves[frame] = LINEAR; } - /** Sets the specified key frame to stepped interpolation. - * @param frameIndex Between 0 and frameCount - 1. */ - public void setStepped (int frameIndex) { - curves[frameIndex] = STEPPED; + /** Sets the specified frame to stepped interpolation. + * @param frame Between 0 and frameCount - 1. */ + public void setStepped (int frame) { + curves[frame] = STEPPED; } - int setBezier (int frameIndex, int bezierIndex) { - int index = getFrameCount() - 1 + bezierIndex * BEZIER_SIZE; - curves[frameIndex] = BEZIER + index; - return index; + /** Returns the interpolation type for the specified frame. + * @param frame Between 0 and frameCount - 1. + * @return {@link #LINEAR}, {@link #STEPPED}, or {@link #BEZIER} + the index of the Bezier segments. */ + public int getCurveType (int frame) { + return (int)curves[frame]; } - /** Returns the interpolation type for the specified key frame. - * @param frameIndex Between 0 and frameCount - 1. - * @return {@link #LINEAR}, {@link #STEPPED}, or {@link #BEZIER}. */ - public int getCurveType (int frameIndex) { - return Math.min((int)curves[frameIndex], BEZIER); - } - - /** Shrinks the storage for Bezier curves, for use when bezierCount specified in the constructor was larger + /** Shrinks the storage for Bezier curves, for use when bezierCount (specified in the constructor) was larger * than the actual number of Bezier curves. */ public void shrink (int bezierCount) { - int size = getFrameCount() - 1 + bezierCount * BEZIER_SIZE; + int size = getFrameCount() + bezierCount * BEZIER_SIZE; if (curves.length > size) { float[] newCurves = new float[size]; arraycopy(curves, 0, newCurves, 0, size); curves = newCurves; } } - } - /** The base class for timelines that use interpolation between key frames for one or more properties using a percentage of the - * difference between key frame values. */ - static public abstract class PercentCurveTimeline extends CurveTimeline { - /** @param bezierCount The maximum number of frames that will use Bezier curves. See {@link #shrink(int)}. - * @param propertyIds Unique identifiers for the properties the timeline modifies. */ - public PercentCurveTimeline (int frameCount, int frameEntries, int bezierCount, String... propertyIds) { - super(frameCount, frameEntries, bezierCount, propertyIds); - } - - /** Sets the specified key frame to Bezier interpolation. - * @param frameIndex Between 0 and frameCount - 1. - * @param cx1 The time for the first Bezier handle. - * @param cy1 The percentage of the difference between the key frame's values for the first Bezier handle. - * @param cx2 The time of the second Bezier handle. - * @param cy2 The percentage of the difference between the key frame's values for the second Bezier handle. */ - public void setBezier (int frameIndex, int bezierIndex, float time1, float cx1, float cy1, float cx2, float cy2, - float time2) { - int i = setBezier(frameIndex, bezierIndex); - float[] curves = this.curves; - float tmpx = (time1 - cx1 * 2 + cx2) * 0.03f, tmpy = cy2 * 0.03f - cy1 * 0.06f; - float dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006f, dddy = (cy1 - cy2 + 0.33333333f) * 0.018f; - float ddx = tmpx * 2 + dddx, ddy = tmpy * 2 + dddy; - float dx = (cx1 - time1) * 0.3f + tmpx + dddx * 0.16666667f, dy = cy1 * 0.3f + tmpy + dddy * 0.16666667f; - float x = time1 + dx, y = dy; - for (int n = i + BEZIER_SIZE; i < n; i += 2) { - curves[i] = x; - curves[i + 1] = y; - dx += ddx; - dy += ddy; - ddx += dddx; - ddy += dddy; - x += dx; - y += dy; - } - } - - /** Returns the interpolated percentage for the specified key frame and times. - * @param frameIndex Between 0 and frameCount - 1. */ - public float getCurvePercent (int frameIndex, float time, int timeIndex, int entryCount) { - float[] curves = this.curves; - int i = (int)curves[frameIndex]; - if (i < BEZIER) { - if (i == STEPPED) return 0; - float x = frames[timeIndex]; - return (time - x) / (frames[timeIndex + entryCount] - x); - } - i -= BEZIER; - if (curves[i] > time) { - float x = frames[timeIndex]; - return curves[i + 1] * (time - x) / (curves[i] - x); - } - int n = i + BEZIER_SIZE; - for (i += 2; i < n; i += 2) { - if (curves[i] >= time) { - float x = curves[i - 2], y = curves[i - 1]; - return y + (curves[i + 1] - y) * (time - x) / (curves[i] - x); - } - } - float x = curves[n - 2], y = curves[n - 1]; - return y + (1 - y) * (time - x) / (frames[timeIndex + entryCount] - x); - } - } - - /** The base class for a {@link PercentCurveTimeline} which changes two float properties. */ - static public abstract class PercentCurveTimeline2 extends PercentCurveTimeline { - static public final int ENTRIES = 3; - static final int VALUE1 = 1, VALUE2 = 2, NEXT_VALUE1 = 4, NEXT_VALUE2 = 5; - - /** @param bezierCount The maximum number of frames that will use Bezier curves. See {@link #shrink(int)}. - * @param propertyIds Unique identifiers for the properties the timeline modifies. */ - public PercentCurveTimeline2 (int frameCount, int bezierCount, String... propertyIds) { - super(frameCount, ENTRIES, bezierCount, propertyIds); - } - - public int getFrameCount () { - return frames.length / ENTRIES; - } - - /** Sets the time in seconds and the values for the specified key frame. */ - public void setFrame (int frameIndex, float time, float value1, float value2) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + VALUE1] = value1; - frames[frameIndex + VALUE2] = value2; - } - } - - /** The base class for timelines that use interpolation between key frames for a single property using the key frames' value - * space. */ - static public abstract class ValueCurveTimeline extends CurveTimeline { - static public final int ENTRIES = 2; - static final int VALUE = 1, NEXT_VALUE = 3; - - /** @param bezierCount The maximum number of frames that will use Bezier curves. See {@link #shrink(int)}. - * @param propertyIds Unique identifiers for the properties the timeline modifies. */ - public ValueCurveTimeline (int frameCount, int bezierCount, String... propertyIds) { - super(frameCount, 2, bezierCount, propertyIds); - } - - public int getFrameCount () { - return frames.length >> 1; - } - - public void setFrame (int frameIndex, float time, float value) { - frameIndex <<= 1; - frames[frameIndex] = time; - frames[frameIndex + VALUE] = value; - } - - /** Sets the specified key frame to Bezier interpolation. - * @param frameIndex Between 0 and frameCount - 1. + /** Stores the segments for the specified Bezier curve. For timelines that modify multiple values, there may be more than + * one curve per frame. + * @param bezier The ordinal of this Bezier curve for this timeline, between 0 and bezierCount - 1 (specified + * in the constructor). + * @param frame Between 0 and frameCount - 1. + * @param value The index of the value for this frame that this curve is used for. + * @param time1 The time for the first key. + * @param value1 The value for the first key. * @param cx1 The time for the first Bezier handle. * @param cy1 The value for the first Bezier handle. * @param cx2 The time of the second Bezier handle. - * @param cy2 The value for the second Bezier handle. */ - public void setBezier (int frameIndex, int bezierIndex, float time1, float value1, float cx1, float cy1, float cx2, + * @param cy2 The value for the second Bezier handle. + * @param time2 The time for the second key. + * @param value2 The value for the second key. */ + public void setBezier (int bezier, int frame, int value, float time1, float value1, float cx1, float cy1, float cx2, float cy2, float time2, float value2) { - int i = setBezier(frameIndex, bezierIndex); float[] curves = this.curves; + int i = getFrameCount() + bezier * BEZIER_SIZE; + if (value == 0) curves[frame] = BEZIER + i; float tmpx = (time1 - cx1 * 2 + cx2) * 0.03f, tmpy = (value1 - cy1 * 2 + cy2) * 0.03f; float dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006f, dddy = ((cy1 - cy2) * 3 - value1 + value2) * 0.006f; float ddx = tmpx * 2 + dddx, ddy = tmpy * 2 + dddy; @@ -475,40 +345,104 @@ public class Animation { } } - /** Returns the interpolated value for the specified time. */ - public float getCurveValue (float time) { - float[] frames = this.frames; - if (time >= frames[frames.length - ENTRIES]) return frames[frames.length - 1]; - int frameIndex = search2(frames, time); + /** Returns the Bezier interpolated value for the specified time. + * @param frameIndex The index into {@link #getFrames()} for the values of the frame before time. + * @param valueOffset The offset from frameIndex to the value this curve is used for. + * @param i The index of the Bezier segments. See {@link #getCurveType(int)}. */ + public float getBezierValue (float time, int frameIndex, int valueOffset, int i) { float[] curves = this.curves; - int i = (int)curves[frameIndex]; - if (i < BEZIER) { - int frame = frameIndex << 1; - if (i == STEPPED) return frames[frame + VALUE]; - float x = frames[frame], y = frames[frame + VALUE]; - return y + (frames[frame + NEXT_VALUE] - y) * (time - x) / (frames[frame + ENTRIES] - x); - } - i -= BEZIER; if (curves[i] > time) { - int frame = frameIndex << 1; - float x = frames[frame], y = frames[frame + VALUE]; - return y + (curves[i + 1] - y) * (time - x) / (curves[i] - x); + float x = frames[frameIndex], y = frames[frameIndex + valueOffset]; + return y + (time - x) / (curves[i] - x) * (curves[i + 1] - y); } int n = i + BEZIER_SIZE; for (i += 2; i < n; i += 2) { if (curves[i] >= time) { float x = curves[i - 2], y = curves[i - 1]; - return y + (curves[i + 1] - y) * (time - x) / (curves[i] - x); + return y + (time - x) / (curves[i] - x) * (curves[i + 1] - y); } } + frameIndex += getFrameEntries(); float x = curves[n - 2], y = curves[n - 1]; - int frame = frameIndex << 1; - return y + (frames[frame + NEXT_VALUE] - y) * (time - x) / (frames[frame + ENTRIES] - x); + return y + (time - x) / (frames[frameIndex] - x) * (frames[frameIndex + valueOffset] - y); + } + } + + /** The base class for a {@link CurveTimeline} that sets one property. */ + static public abstract class CurveTimeline1 extends CurveTimeline { + static public final int ENTRIES = 2; + static final int VALUE = 1; + + /** @param bezierCount The maximum number of Bezier curves. See {@link #shrink(int)}. + * @param propertyIds Unique identifiers for the properties the timeline modifies. */ + public CurveTimeline1 (int frameCount, int bezierCount, String... propertyIds) { + super(frameCount, bezierCount, propertyIds); + } + + public int getFrameEntries () { + return ENTRIES; + } + + /** Sets the time and value for the specified frame. + * @param frame Between 0 and frameCount. + * @param time The frame time in seconds. */ + public void setFrame (int frame, float time, float value) { + frame <<= 1; + frames[frame] = time; + frames[frame + VALUE] = value; + } + + /** Returns the interpolated value for the specified time. */ + public float getCurveValue (float time) { + float[] frames = this.frames; + int i = frames.length - 2; + for (int ii = 2; ii <= i; ii += 2) { + if (frames[ii] > time) { + i = ii - 2; + break; + } + } + + int curveType = (int)curves[i >> 1]; + switch (curveType) { + case LINEAR: + float before = frames[i], value = frames[i + VALUE]; + return value + (time - before) / (frames[i + ENTRIES] - before) * (frames[i + ENTRIES + VALUE] - value); + case STEPPED: + return frames[i + VALUE]; + } + return getBezierValue(time, i, VALUE, curveType - BEZIER); + } + } + + /** The base class for a {@link CurveTimeline} which sets two properties. */ + static public abstract class CurveTimeline2 extends CurveTimeline { + static public final int ENTRIES = 3; + static final int VALUE1 = 1, VALUE2 = 2; + + /** @param bezierCount The maximum number of Bezier curves. See {@link #shrink(int)}. + * @param propertyIds Unique identifiers for the properties the timeline modifies. */ + public CurveTimeline2 (int frameCount, int bezierCount, String... propertyIds) { + super(frameCount, bezierCount, propertyIds); + } + + public int getFrameEntries () { + return ENTRIES; + } + + /** Sets the time and values for the specified frame. + * @param frame Between 0 and frameCount. + * @param time The frame time in seconds. */ + public void setFrame (int frame, float time, float value1, float value2) { + frame *= ENTRIES; + frames[frame] = time; + frames[frame + VALUE1] = value1; + frames[frame + VALUE2] = value2; } } /** Changes a bone's local {@link Bone#getRotation()}. */ - static public class RotateTimeline extends ValueCurveTimeline implements BoneTimeline { + static public class RotateTimeline extends CurveTimeline1 implements BoneTimeline { final int boneIndex; public RotateTimeline (int frameCount, int bezierCount, int boneIndex) { @@ -525,7 +459,7 @@ public class Animation { Bone bone = skeleton.bones.get(boneIndex); if (!bone.active) return; - float[] frames = this.frames; + if (time < frames[0]) { // Time is before first frame. switch (blend) { case setup: @@ -553,7 +487,7 @@ public class Animation { } /** Changes a bone's local {@link Bone#getX()} and {@link Bone#getY()}. */ - static public class TranslateTimeline extends PercentCurveTimeline2 implements BoneTimeline { + static public class TranslateTimeline extends CurveTimeline2 implements BoneTimeline { final int boneIndex; public TranslateTimeline (int frameCount, int bezierCount, int boneIndex) { @@ -572,6 +506,7 @@ public class Animation { Bone bone = skeleton.bones.get(boneIndex); if (!bone.active) return; + float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. switch (blend) { @@ -587,17 +522,25 @@ public class Animation { } float x, y; - if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. - x = frames[frames.length - ENTRIES + VALUE1]; - y = frames[frames.length - ENTRIES + VALUE2]; - } else { - int frameIndex = searchN(frames, time, ENTRIES), frame = frameIndex * ENTRIES; - float percent = getCurvePercent(frameIndex, time, frame, ENTRIES); - x = frames[frame + VALUE1]; - y = frames[frame + VALUE2]; - x += (frames[frame + NEXT_VALUE1] - x) * percent; - y += (frames[frame + NEXT_VALUE2] - y) * percent; + int i = search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) { + case LINEAR: + float before = frames[i]; + x = frames[i + VALUE1]; + y = frames[i + VALUE2]; + float t = (time - before) / (frames[i + ENTRIES] - before); + x += (frames[i + ENTRIES + VALUE1] - x) * t; + y += (frames[i + ENTRIES + VALUE2] - y) * t; + break; + case STEPPED: + x = frames[i + VALUE1]; + y = frames[i + VALUE2]; + break; + default: + x = getBezierValue(time, i, VALUE1, curveType - BEZIER); + y = getBezierValue(time, i, VALUE2, curveType + BEZIER_SIZE - BEZIER); } + switch (blend) { case setup: bone.x = bone.data.x + x * alpha; @@ -616,7 +559,7 @@ public class Animation { } /** Changes a bone's local {@link Bone#getScaleX()} and {@link Bone#getScaleY()}. */ - static public class ScaleTimeline extends PercentCurveTimeline2 implements BoneTimeline { + static public class ScaleTimeline extends CurveTimeline2 implements BoneTimeline { final int boneIndex; public ScaleTimeline (int frameCount, int bezierCount, int boneIndex) { @@ -635,6 +578,7 @@ public class Animation { Bone bone = skeleton.bones.get(boneIndex); if (!bone.active) return; + float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. switch (blend) { @@ -650,17 +594,25 @@ public class Animation { } float x, y; - if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. - x = frames[frames.length - ENTRIES + VALUE1] * bone.data.scaleX; - y = frames[frames.length - ENTRIES + VALUE2] * bone.data.scaleY; - } else { - int frameIndex = searchN(frames, time, ENTRIES), frame = frameIndex * ENTRIES; - float percent = getCurvePercent(frameIndex, time, frame, ENTRIES); - x = frames[frame + VALUE1]; - y = frames[frame + VALUE2]; - x = (x + (frames[frame + NEXT_VALUE1] - x) * percent) * bone.data.scaleX; - y = (y + (frames[frame + NEXT_VALUE2] - y) * percent) * bone.data.scaleY; + int i = search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) { + case LINEAR: + float before = frames[i]; + x = frames[i + VALUE1]; + y = frames[i + VALUE2]; + float t = (time - before) / (frames[i + ENTRIES] - before); + x += (frames[i + ENTRIES + VALUE1] - x) * t; + y += (frames[i + ENTRIES + VALUE2] - y) * t; + break; + case STEPPED: + x = frames[i + VALUE1]; + y = frames[i + VALUE2]; + break; + default: + x = getBezierValue(time, i, VALUE1, curveType - BEZIER); + y = getBezierValue(time, i, VALUE2, curveType + BEZIER_SIZE - BEZIER); } + if (alpha == 1) { if (blend == add) { bone.scaleX += x - bone.data.scaleX; @@ -720,7 +672,7 @@ public class Animation { } /** Changes a bone's local {@link Bone#getShearX()} and {@link Bone#getShearY()}. */ - static public class ShearTimeline extends PercentCurveTimeline2 implements BoneTimeline { + static public class ShearTimeline extends CurveTimeline2 implements BoneTimeline { final int boneIndex; public ShearTimeline (int frameCount, int bezierCount, int boneIndex) { @@ -739,6 +691,7 @@ public class Animation { Bone bone = skeleton.bones.get(boneIndex); if (!bone.active) return; + float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. switch (blend) { @@ -754,17 +707,25 @@ public class Animation { } float x, y; - if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. - x = frames[frames.length - ENTRIES + VALUE1] * bone.data.scaleX; - y = frames[frames.length - ENTRIES + VALUE2] * bone.data.scaleY; - } else { - int frameIndex = searchN(frames, time, ENTRIES), frame = frameIndex * ENTRIES; - float percent = getCurvePercent(frameIndex, time, frame, ENTRIES); - x = frames[frame + VALUE1]; - y = frames[frame + VALUE2]; - x = x + (frames[frame + NEXT_VALUE1] - x) * percent; - y = y + (frames[frame + NEXT_VALUE2] - y) * percent; + int i = search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) { + case LINEAR: + float before = frames[i]; + x = frames[i + VALUE1]; + y = frames[i + VALUE2]; + float t = (time - before) / (frames[i + ENTRIES] - before); + x += (frames[i + ENTRIES + VALUE1] - x) * t; + y += (frames[i + ENTRIES + VALUE2] - y) * t; + break; + case STEPPED: + x = frames[i + VALUE1]; + y = frames[i + VALUE2]; + break; + default: + x = getBezierValue(time, i, VALUE1, curveType - BEZIER); + y = getBezierValue(time, i, VALUE2, curveType + BEZIER_SIZE - BEZIER); } + switch (blend) { case setup: bone.shearX = bone.data.shearX + x * alpha; @@ -783,36 +744,36 @@ public class Animation { } /** Changes a slot's {@link Slot#getColor()}. */ - static public class ColorTimeline extends PercentCurveTimeline implements SlotTimeline { + static public class ColorTimeline extends CurveTimeline implements SlotTimeline { static public final int ENTRIES = 5; static private final int R = 1, G = 2, B = 3, A = 4; - static private final int NEXT_R = 6, NEXT_G = 7, NEXT_B = 8, NEXT_A = 9; final int slotIndex; public ColorTimeline (int frameCount, int bezierCount, int slotIndex) { - super(frameCount, ENTRIES, bezierCount, // - Property.rgb.ordinal() + "|" + slotIndex, // - Property.a.ordinal() + "|" + slotIndex); + super(frameCount, bezierCount, // + Property.rgba.ordinal() + "|" + slotIndex); this.slotIndex = slotIndex; } - public int getFrameCount () { - return frames.length / ENTRIES; + public int getFrameEntries () { + return ENTRIES; } public int getSlotIndex () { return slotIndex; } - /** Sets the time in seconds, red, green, blue, and alpha for the specified key frame. */ - public void setFrame (int frameIndex, float time, float r, float g, float b, float a) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + R] = r; - frames[frameIndex + G] = g; - frames[frameIndex + B] = b; - frames[frameIndex + A] = a; + /** Sets the time and color for the specified frame. + * @param frame Between 0 and frameCount. + * @param time The frame time in seconds. */ + public void setFrame (int frame, float time, float r, float g, float b, float a) { + frame *= ENTRIES; + frames[frame] = time; + frames[frame + R] = r; + frames[frame + G] = g; + frames[frame + B] = b; + frames[frame + A] = a; } public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixBlend blend, @@ -820,6 +781,7 @@ public class Animation { Slot slot = skeleton.slots.get(slotIndex); if (!slot.bone.active) return; + float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. switch (blend) { @@ -835,24 +797,33 @@ public class Animation { } float r, g, b, a; - if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. - int i = frames.length - ENTRIES; + int i = search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) { + case LINEAR: + float before = frames[i]; r = frames[i + R]; g = frames[i + G]; b = frames[i + B]; a = frames[i + A]; - } else { - int frameIndex = searchN(frames, time, ENTRIES), frame = frameIndex * ENTRIES; - float percent = getCurvePercent(frameIndex, time, frame, ENTRIES); - r = frames[frame + R]; - g = frames[frame + G]; - b = frames[frame + B]; - a = frames[frame + A]; - r += (frames[frame + NEXT_R] - r) * percent; - g += (frames[frame + NEXT_G] - g) * percent; - b += (frames[frame + NEXT_B] - b) * percent; - a += (frames[frame + NEXT_A] - a) * percent; + float t = (time - before) / (frames[i + ENTRIES] - before); + r += (frames[i + ENTRIES + R] - r) * t; + g += (frames[i + ENTRIES + G] - g) * t; + b += (frames[i + ENTRIES + B] - b) * t; + a += (frames[i + ENTRIES + A] - a) * t; + break; + case STEPPED: + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + a = frames[i + A]; + break; + default: + r = getBezierValue(time, i, R, curveType - BEZIER); + g = getBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); + b = getBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); + a = getBezierValue(time, i, A, curveType + BEZIER_SIZE * 3 - BEZIER); } + if (alpha == 1) slot.color.set(r, g, b, a); else { @@ -864,42 +835,42 @@ public class Animation { } /** Changes a slot's {@link Slot#getColor()} and {@link Slot#getDarkColor()} for two color tinting. */ - static public class TwoColorTimeline extends PercentCurveTimeline implements SlotTimeline { + static public class TwoColorTimeline extends CurveTimeline implements SlotTimeline { static public final int ENTRIES = 8; static private final int R = 1, G = 2, B = 3, A = 4, R2 = 5, G2 = 6, B2 = 7; - static private final int NEXT_R = 9, NEXT_G = 10, NEXT_B = 11, NEXT_A = 12, NEXT_R2 = 13, NEXT_G2 = 14, NEXT_B2 = 15; final int slotIndex; public TwoColorTimeline (int frameCount, int bezierCount, int slotIndex) { - super(frameCount, ENTRIES, bezierCount, // - Property.rgb.ordinal() + "|" + slotIndex, // - Property.a.ordinal() + "|" + slotIndex, // + super(frameCount, bezierCount, // + Property.rgba.ordinal() + "|" + slotIndex, // Property.rgb2.ordinal() + "|" + slotIndex); this.slotIndex = slotIndex; } - public int getFrameCount () { - return frames.length >> 3; + public int getFrameEntries () { + return ENTRIES; } - /** The index of the slot in {@link Skeleton#getSlots()} that will be changed. The {@link Slot#getDarkColor()} must not be - * null. */ + /** The index of the slot in {@link Skeleton#getSlots()} that will be changed when this timeline is applied. The + * {@link Slot#getDarkColor()} must not be null. */ public int getSlotIndex () { return slotIndex; } - /** Sets the time in seconds, light, and dark colors for the specified key frame. */ - public void setFrame (int frameIndex, float time, float r, float g, float b, float a, float r2, float g2, float b2) { - frameIndex <<= 3; - frames[frameIndex] = time; - frames[frameIndex + R] = r; - frames[frameIndex + G] = g; - frames[frameIndex + B] = b; - frames[frameIndex + A] = a; - frames[frameIndex + R2] = r2; - frames[frameIndex + G2] = g2; - frames[frameIndex + B2] = b2; + /** Sets the time, light color, and dark color for the specified frame. + * @param frame Between 0 and frameCount. + * @param time The frame time in seconds. */ + public void setFrame (int frame, float time, float r, float g, float b, float a, float r2, float g2, float b2) { + frame <<= 3; + frames[frame] = time; + frames[frame + R] = r; + frames[frame + G] = g; + frames[frame + B] = b; + frames[frame + A] = a; + frames[frame + R2] = r2; + frames[frame + G2] = g2; + frames[frame + B2] = b2; } public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixBlend blend, @@ -907,6 +878,7 @@ public class Animation { Slot slot = skeleton.slots.get(slotIndex); if (!slot.bone.active) return; + float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. switch (blend) { @@ -924,8 +896,10 @@ public class Animation { } float r, g, b, a, r2, g2, b2; - if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. - int i = frames.length - ENTRIES; + int i = search(frames, time, ENTRIES), curveType = (int)curves[i >> 3]; + switch (curveType) { + case LINEAR: + float before = frames[i]; r = frames[i + R]; g = frames[i + G]; b = frames[i + B]; @@ -933,24 +907,34 @@ public class Animation { r2 = frames[i + R2]; g2 = frames[i + G2]; b2 = frames[i + B2]; - } else { - int frameIndex = searchN(frames, time, ENTRIES), frame = frameIndex * ENTRIES; - float percent = getCurvePercent(frameIndex, time, frame, ENTRIES); - r = frames[frame + R]; - g = frames[frame + G]; - b = frames[frame + B]; - a = frames[frame + A]; - r2 = frames[frame + R2]; - g2 = frames[frame + G2]; - b2 = frames[frame + B2]; - r += (frames[frame + NEXT_R] - r) * percent; - g += (frames[frame + NEXT_G] - g) * percent; - b += (frames[frame + NEXT_B] - b) * percent; - a += (frames[frame + NEXT_A] - a) * percent; - r2 += (frames[frame + NEXT_R2] - r2) * percent; - g2 += (frames[frame + NEXT_G2] - g2) * percent; - b2 += (frames[frame + NEXT_B2] - b2) * percent; + float t = (time - before) / (frames[i + ENTRIES] - before); + r += (frames[i + ENTRIES + R] - r) * t; + g += (frames[i + ENTRIES + G] - g) * t; + b += (frames[i + ENTRIES + B] - b) * t; + a += (frames[i + ENTRIES + A] - a) * t; + r2 += (frames[i + ENTRIES + R2] - r2) * t; + g2 += (frames[i + ENTRIES + G2] - g2) * t; + b2 += (frames[i + ENTRIES + B2] - b2) * t; + break; + case STEPPED: + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + a = frames[i + A]; + r2 = frames[i + R2]; + g2 = frames[i + G2]; + b2 = frames[i + B2]; + break; + default: + r = getBezierValue(time, i, R, curveType - BEZIER); + g = getBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); + b = getBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); + a = getBezierValue(time, i, A, curveType + BEZIER_SIZE * 3 - BEZIER); + r2 = getBezierValue(time, i, R2, curveType + BEZIER_SIZE * 4 - BEZIER); + g2 = getBezierValue(time, i, G2, curveType + BEZIER_SIZE * 5 - BEZIER); + b2 = getBezierValue(time, i, B2, curveType + BEZIER_SIZE * 6 - BEZIER); } + if (alpha == 1) { slot.color.set(r, g, b, a); slot.darkColor.set(r2, g2, b2, 1); @@ -972,28 +956,26 @@ public class Animation { final String[] attachmentNames; public AttachmentTimeline (int frameCount, int slotIndex) { - super(frameCount, 1, Property.attachment.ordinal() + "|" + slotIndex); + super(frameCount, Property.attachment.ordinal() + "|" + slotIndex); this.slotIndex = slotIndex; attachmentNames = new String[frameCount]; } - public int getFrameCount () { - return frames.length; - } - public int getSlotIndex () { return slotIndex; } - /** The attachment name for each key frame. May contain null values to clear the attachment. */ + /** The attachment name for each frame. May contain null values to clear the attachment. */ public String[] getAttachmentNames () { return attachmentNames; } - /** Sets the time in seconds and the attachment name for the specified key frame. */ - public void setFrame (int frameIndex, float time, String attachmentName) { - frames[frameIndex] = time; - attachmentNames[frameIndex] = attachmentName; + /** Sets the time and attachment name for the specified frame. + * @param frame Between 0 and frameCount. + * @param time The frame time in seconds. */ + public void setFrame (int frame, float time, String attachmentName) { + frames[frame] = time; + attachmentNames[frame] = attachmentName; } public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixBlend blend, @@ -1001,6 +983,7 @@ public class Animation { Slot slot = skeleton.slots.get(slotIndex); if (!slot.bone.active) return; + if (direction == out && blend == setup) { String attachmentName = slot.data.attachmentName; slot.setAttachment(attachmentName == null ? null : skeleton.getAttachment(slotIndex, attachmentName)); @@ -1022,41 +1005,93 @@ public class Animation { } /** Changes a slot's {@link Slot#getDeform()} to deform a {@link VertexAttachment}. */ - static public class DeformTimeline extends PercentCurveTimeline implements SlotTimeline { + static public class DeformTimeline extends CurveTimeline implements SlotTimeline { final int slotIndex; final VertexAttachment attachment; - private final float[][] frameVertices; + private final float[][] vertices; public DeformTimeline (int frameCount, int bezierCount, int slotIndex, VertexAttachment attachment) { - super(frameCount, 1, bezierCount, Property.deform.ordinal() + "|" + slotIndex + "|" + attachment.getId()); + super(frameCount, bezierCount, Property.deform.ordinal() + "|" + slotIndex + "|" + attachment.getId()); this.slotIndex = slotIndex; this.attachment = attachment; - frameVertices = new float[frameCount][]; - } - - public int getFrameCount () { - return frames.length; + vertices = new float[frameCount][]; } public int getSlotIndex () { return slotIndex; } - /** The attachment that will be deformed. */ + /** The attachment that will be deformed. + *

+ * See {@link VertexAttachment#getDeformAttachment()}. */ public VertexAttachment getAttachment () { return attachment; } - /** The vertices for each key frame. */ + /** The vertices for each frame. */ public float[][] getVertices () { - return frameVertices; + return vertices; } - /** Sets the time in seconds and the vertices for the specified key frame. + /** Sets the time and vertices for the specified frame. + * @param frame Between 0 and frameCount. + * @param time The frame time in seconds. * @param vertices Vertex positions for an unweighted VertexAttachment, or deform offsets if it has weights. */ - public void setFrame (int frameIndex, float time, float[] vertices) { - frames[frameIndex] = time; - frameVertices[frameIndex] = vertices; + public void setFrame (int frame, float time, float[] vertices) { + frames[frame] = time; + this.vertices[frame] = vertices; + } + + /** @param value1 Ignored. + * @param value2 Ignored. */ + public void setBezier (int bezier, int frame, int value, float time1, float value1, float cx1, float cy1, float cx2, + float cy2, float time2, float value2) { + float[] curves = this.curves; + int i = getFrameCount() + bezier * BEZIER_SIZE; + if (value == 0) curves[frame] = BEZIER + i; + float tmpx = (time1 - cx1 * 2 + cx2) * 0.03f, tmpy = cy2 * 0.03f - cy1 * 0.06f; + float dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006f, dddy = (cy1 - cy2 + 0.33333333f) * 0.018f; + float ddx = tmpx * 2 + dddx, ddy = tmpy * 2 + dddy; + float dx = (cx1 - time1) * 0.3f + tmpx + dddx * 0.16666667f, dy = cy1 * 0.3f + tmpy + dddy * 0.16666667f; + float x = time1 + dx, y = dy; + for (int n = i + BEZIER_SIZE; i < n; i += 2) { + curves[i] = x; + curves[i + 1] = y; + dx += ddx; + dy += ddy; + ddx += dddx; + ddy += dddy; + x += dx; + y += dy; + } + } + + /** Returns the interpolated percentage for the specified time. + * @param frame The frame before time. */ + private float getCurvePercent (float time, int frame) { + float[] curves = this.curves; + int i = (int)curves[frame]; + switch (i) { + case LINEAR: + float x = frames[frame]; + return (time - x) / (frames[frame + getFrameEntries()] - x); + case STEPPED: + return 0; + } + i -= BEZIER; + if (curves[i] > time) { + float x = frames[frame]; + return curves[i + 1] * (time - x) / (curves[i] - x); + } + int n = i + BEZIER_SIZE; + for (i += 2; i < n; i += 2) { + if (curves[i] >= time) { + float x = curves[i - 2], y = curves[i - 1]; + return y + (time - x) / (curves[i] - x) * (curves[i + 1] - y); + } + } + float x = curves[n - 2], y = curves[n - 1]; + return y + (1 - y) * (time - x) / (frames[frame + getFrameEntries()] - x); } public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixBlend blend, @@ -1071,8 +1106,8 @@ public class Animation { FloatArray deformArray = slot.getDeform(); if (deformArray.size == 0) blend = setup; - float[][] frameVertices = this.frameVertices; - int vertexCount = frameVertices[0].length; + float[][] vertices = this.vertices; + int vertexCount = vertices[0].length; float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. @@ -1105,7 +1140,7 @@ public class Animation { float[] deform = deformArray.setSize(vertexCount); if (time >= frames[frames.length - 1]) { // Time is after last frame. - float[] lastVertices = frameVertices[frames.length - 1]; + float[] lastVertices = vertices[frames.length - 1]; if (alpha == 1) { if (blend == add) { VertexAttachment vertexAttachment = (VertexAttachment)slotAttachment; @@ -1165,9 +1200,9 @@ public class Animation { } int frame = search(frames, time); - float percent = getCurvePercent(frame, time, frame, 1); - float[] prevVertices = frameVertices[frame]; - float[] nextVertices = frameVertices[frame + 1]; + float percent = getCurvePercent(time, frame); + float[] prevVertices = vertices[frame]; + float[] nextVertices = vertices[frame + 1]; if (alpha == 1) { if (blend == add) { @@ -1244,26 +1279,25 @@ public class Animation { /** Fires an {@link Event} when specific animation times are reached. */ static public class EventTimeline extends Timeline { + static private final String[] propertyIds = {Integer.toString(Property.event.ordinal())}; + private final Event[] events; public EventTimeline (int frameCount) { - super(frameCount, 1, Integer.toString(Property.event.ordinal())); + super(frameCount, propertyIds); events = new Event[frameCount]; } - public int getFrameCount () { - return frames.length; - } - - /** The event for each key frame. */ + /** The event for each frame. */ public Event[] getEvents () { return events; } - /** Sets the time in seconds and the event for the specified key frame. */ - public void setFrame (int frameIndex, Event event) { - frames[frameIndex] = event.time; - events[frameIndex] = event; + /** Sets the time and event for the specified frame. + * @param frame Between 0 and frameCount. */ + public void setFrame (int frame, Event event) { + frames[frame] = event.time; + events[frame] = event; } /** Fires events for frames > lastTime and <= time. */ @@ -1271,6 +1305,7 @@ public class Animation { MixDirection direction) { if (firedEvents == null) return; + float[] frames = this.frames; int frameCount = frames.length; @@ -1281,46 +1316,46 @@ public class Animation { return; if (time < frames[0]) return; // Time is before first frame. - int frame; + int i; if (lastTime < frames[0]) - frame = 0; + i = 0; else { - frame = search(frames, lastTime) + 1; - float frameTime = frames[frame]; - while (frame > 0) { // Fire multiple events with the same frame. - if (frames[frame - 1] != frameTime) break; - frame--; + i = search(frames, lastTime) + 1; + float frameTime = frames[i]; + while (i > 0) { // Fire multiple events with the same frame. + if (frames[i - 1] != frameTime) break; + i--; } } - for (; frame < frameCount && time >= frames[frame]; frame++) - firedEvents.add(events[frame]); + for (; i < frameCount && time >= frames[i]; i++) + firedEvents.add(events[i]); } } /** Changes a skeleton's {@link Skeleton#getDrawOrder()}. */ static public class DrawOrderTimeline extends Timeline { + static private final String[] propertyIds = {Integer.toString(Property.drawOrder.ordinal())}; + private final int[][] drawOrders; public DrawOrderTimeline (int frameCount) { - super(frameCount, 1, Integer.toString(Property.drawOrder.ordinal())); + super(frameCount, propertyIds); drawOrders = new int[frameCount][]; } - public int getFrameCount () { - return frames.length; - } - - /** The draw order for each key frame. See {@link #setFrame(int, float, int[])}. */ + /** The draw order for each frame. See {@link #setFrame(int, float, int[])}. */ public int[][] getDrawOrders () { return drawOrders; } - /** Sets the time in seconds and the draw order for the specified key frame. - * @param drawOrder For each slot in {@link Skeleton#slots}, the index of the new draw order. May be null to use setup pose - * draw order. */ - public void setFrame (int frameIndex, float time, int[] drawOrder) { - frames[frameIndex] = time; - drawOrders[frameIndex] = drawOrder; + /** Sets the time and draw order for the specified frame. + * @param frame Between 0 and frameCount. + * @param time The frame time in seconds. + * @param drawOrder For each slot in {@link Skeleton#slots}, the index of the slot in the new draw order. May be null to use + * setup pose draw order. */ + public void setFrame (int frame, float time, int[] drawOrder) { + frames[frame] = time; + drawOrders[frame] = drawOrder; } public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixBlend blend, @@ -1351,37 +1386,39 @@ public class Animation { /** Changes an IK constraint's {@link IkConstraint#getMix()}, {@link IkConstraint#getSoftness()}, * {@link IkConstraint#getBendDirection()}, {@link IkConstraint#getStretch()}, and {@link IkConstraint#getCompress()}. */ - static public class IkConstraintTimeline extends PercentCurveTimeline { + static public class IkConstraintTimeline extends CurveTimeline { static public final int ENTRIES = 6; static private final int MIX = 1, SOFTNESS = 2, BEND_DIRECTION = 3, COMPRESS = 4, STRETCH = 5; - static private final int NEXT_MIX = 7, NEXT_SOFTNESS = 8; final int ikConstraintIndex; public IkConstraintTimeline (int frameCount, int bezierCount, int ikConstraintIndex) { - super(frameCount, ENTRIES, bezierCount, Property.ikConstraint.ordinal() + "|" + ikConstraintIndex); + super(frameCount, bezierCount, Property.ikConstraint.ordinal() + "|" + ikConstraintIndex); this.ikConstraintIndex = ikConstraintIndex; } - public int getFrameCount () { - return frames.length / ENTRIES; + public int getFrameEntries () { + return ENTRIES; } - /** The index of the IK constraint slot in {@link Skeleton#getIkConstraints()} that will be changed. */ + /** The index of the IK constraint slot in {@link Skeleton#getIkConstraints()} that will be changed when this timeline is + * applied. */ public int getIkConstraintIndex () { return ikConstraintIndex; } - /** Sets the time in seconds, mix, softness, bend direction, compress, and stretch for the specified key frame. */ - public void setFrame (int frameIndex, float time, float mix, float softness, int bendDirection, boolean compress, + /** Sets the time, mix, softness, bend direction, compress, and stretch for the specified frame. + * @param frame Between 0 and frameCount. + * @param time The frame time in seconds. */ + public void setFrame (int frame, float time, float mix, float softness, int bendDirection, boolean compress, boolean stretch) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + MIX] = mix; - frames[frameIndex + SOFTNESS] = softness; - frames[frameIndex + BEND_DIRECTION] = bendDirection; - frames[frameIndex + COMPRESS] = compress ? 1 : 0; - frames[frameIndex + STRETCH] = stretch ? 1 : 0; + frame *= ENTRIES; + frames[frame] = time; + frames[frame + MIX] = mix; + frames[frame + SOFTNESS] = softness; + frames[frame + BEND_DIRECTION] = bendDirection; + frames[frame + COMPRESS] = compress ? 1 : 0; + frames[frame + STRETCH] = stretch ? 1 : 0; } public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixBlend blend, @@ -1389,6 +1426,7 @@ public class Animation { IkConstraint constraint = skeleton.ikConstraints.get(ikConstraintIndex); if (!constraint.active) return; + float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. switch (blend) { @@ -1409,59 +1447,45 @@ public class Animation { return; } - if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. - int i = frames.length - ENTRIES; - if (blend == setup) { - constraint.mix = constraint.data.mix + (frames[i + MIX] - constraint.data.mix) * alpha; - constraint.softness = constraint.data.softness + (frames[i + SOFTNESS] - constraint.data.softness) * alpha; - if (direction == out) { - constraint.bendDirection = constraint.data.bendDirection; - constraint.compress = constraint.data.compress; - constraint.stretch = constraint.data.stretch; - } else { - constraint.bendDirection = (int)frames[i + BEND_DIRECTION]; - constraint.compress = frames[i + COMPRESS] != 0; - constraint.stretch = frames[i + STRETCH] != 0; - } - } else { - constraint.mix += (frames[i + MIX] - constraint.mix) * alpha; - constraint.softness += (frames[i + SOFTNESS] - constraint.softness) * alpha; - if (direction == in) { - constraint.bendDirection = (int)frames[i + BEND_DIRECTION]; - constraint.compress = frames[i + COMPRESS] != 0; - constraint.stretch = frames[i + STRETCH] != 0; - } - } - return; + float mix, softness; + int i = search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) { + case LINEAR: + float before = frames[i]; + mix = frames[i + MIX]; + softness = frames[i + SOFTNESS]; + float t = (time - before) / (frames[i + ENTRIES] - before); + mix += (frames[i + ENTRIES + MIX] - mix) * t; + softness += (frames[i + ENTRIES + SOFTNESS] - softness) * t; + break; + case STEPPED: + mix = frames[i + MIX]; + softness = frames[i + SOFTNESS]; + break; + default: + mix = getBezierValue(time, i, MIX, curveType - BEZIER); + softness = getBezierValue(time, i, SOFTNESS, curveType + BEZIER_SIZE - BEZIER); } - int frameIndex = searchN(frames, time, ENTRIES), frame = frameIndex * ENTRIES; - float percent = getCurvePercent(frameIndex, time, frame, ENTRIES); - float mix = frames[frame + MIX]; - float softness = frames[frame + SOFTNESS]; - if (blend == setup) { - constraint.mix = constraint.data.mix - + (mix + (frames[frame + NEXT_MIX] - mix) * percent - constraint.data.mix) * alpha; - constraint.softness = constraint.data.softness - + (softness + (frames[frame + NEXT_SOFTNESS] - softness) * percent - constraint.data.softness) * alpha; + constraint.mix = constraint.data.mix + (mix - constraint.data.mix) * alpha; + constraint.softness = constraint.data.softness + (softness - constraint.data.softness) * alpha; if (direction == out) { constraint.bendDirection = constraint.data.bendDirection; constraint.compress = constraint.data.compress; constraint.stretch = constraint.data.stretch; } else { - constraint.bendDirection = (int)frames[frame + BEND_DIRECTION]; - constraint.compress = frames[frame + COMPRESS] != 0; - constraint.stretch = frames[frame + STRETCH] != 0; + constraint.bendDirection = (int)frames[i + BEND_DIRECTION]; + constraint.compress = frames[i + COMPRESS] != 0; + constraint.stretch = frames[i + STRETCH] != 0; } } else { - constraint.mix += (mix + (frames[frame + NEXT_MIX] - mix) * percent - constraint.mix) * alpha; - constraint.softness += (softness + (frames[frame + NEXT_SOFTNESS] - softness) * percent - constraint.softness) - * alpha; + constraint.mix += (mix - constraint.mix) * alpha; + constraint.softness += (softness - constraint.softness) * alpha; if (direction == in) { - constraint.bendDirection = (int)frames[frame + BEND_DIRECTION]; - constraint.compress = frames[frame + COMPRESS] != 0; - constraint.stretch = frames[frame + STRETCH] != 0; + constraint.bendDirection = (int)frames[i + BEND_DIRECTION]; + constraint.compress = frames[i + COMPRESS] != 0; + constraint.stretch = frames[i + STRETCH] != 0; } } } @@ -1469,35 +1493,37 @@ public class Animation { /** Changes a transform constraint's {@link TransformConstraint#getRotateMix()}, {@link TransformConstraint#getTranslateMix()}, * {@link TransformConstraint#getScaleMix()}, and {@link TransformConstraint#getShearMix()}. */ - static public class TransformConstraintTimeline extends PercentCurveTimeline { + static public class TransformConstraintTimeline extends CurveTimeline { static public final int ENTRIES = 5; static private final int ROTATE = 1, TRANSLATE = 2, SCALE = 3, SHEAR = 4; - static private final int NEXT_ROTATE = 6, NEXT_TRANSLATE = 7, NEXT_SCALE = 8, NEXT_SHEAR = 9; final int transformConstraintIndex; public TransformConstraintTimeline (int frameCount, int bezierCount, int transformConstraintIndex) { - super(frameCount, ENTRIES, bezierCount, Property.transformConstraint.ordinal() + "|" + transformConstraintIndex); + super(frameCount, bezierCount, Property.transformConstraint.ordinal() + "|" + transformConstraintIndex); this.transformConstraintIndex = transformConstraintIndex; } - public int getFrameCount () { - return frames.length / ENTRIES; + public int getFrameEntries () { + return ENTRIES; } - /** The index of the transform constraint slot in {@link Skeleton#getTransformConstraints()} that will be changed. */ + /** The index of the transform constraint slot in {@link Skeleton#getTransformConstraints()} that will be changed when this + * timeline is applied. */ public int getTransformConstraintIndex () { return transformConstraintIndex; } - /** The time in seconds, rotate mix, translate mix, scale mix, and shear mix for the specified key frame. */ - public void setFrame (int frameIndex, float time, float rotateMix, float translateMix, float scaleMix, float shearMix) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + ROTATE] = rotateMix; - frames[frameIndex + TRANSLATE] = translateMix; - frames[frameIndex + SCALE] = scaleMix; - frames[frameIndex + SHEAR] = shearMix; + /** Sets the time, rotate mix, translate mix, scale mix, and shear mix for the specified frame. + * @param frame Between 0 and frameCount. + * @param time The frame time in seconds. */ + public void setFrame (int frame, float time, float rotateMix, float translateMix, float scaleMix, float shearMix) { + frame *= ENTRIES; + frames[frame] = time; + frames[frame + ROTATE] = rotateMix; + frames[frame + TRANSLATE] = translateMix; + frames[frame + SCALE] = scaleMix; + frames[frame + SHEAR] = shearMix; } public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixBlend blend, @@ -1505,6 +1531,7 @@ public class Animation { TransformConstraint constraint = skeleton.transformConstraints.get(transformConstraintIndex); if (!constraint.active) return; + float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. TransformConstraintData data = constraint.data; @@ -1525,24 +1552,33 @@ public class Animation { } float rotate, translate, scale, shear; - if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. - int i = frames.length - ENTRIES; + int i = search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) { + case LINEAR: + float before = frames[i]; rotate = frames[i + ROTATE]; translate = frames[i + TRANSLATE]; scale = frames[i + SCALE]; shear = frames[i + SHEAR]; - } else { - int frameIndex = searchN(frames, time, ENTRIES), frame = frameIndex * ENTRIES; - float percent = getCurvePercent(frameIndex, time, frame, ENTRIES); - rotate = frames[frame + ROTATE]; - translate = frames[frame + TRANSLATE]; - scale = frames[frame + SCALE]; - shear = frames[frame + SHEAR]; - rotate += (frames[frame + NEXT_ROTATE] - rotate) * percent; - translate += (frames[frame + NEXT_TRANSLATE] - translate) * percent; - scale += (frames[frame + NEXT_SCALE] - scale) * percent; - shear += (frames[frame + NEXT_SHEAR] - shear) * percent; + float t = (time - before) / (frames[i + ENTRIES] - before); + rotate += (frames[i + ENTRIES + ROTATE] - rotate) * t; + translate += (frames[i + ENTRIES + TRANSLATE] - translate) * t; + scale += (frames[i + ENTRIES + SCALE] - scale) * t; + shear += (frames[i + ENTRIES + SHEAR] - shear) * t; + break; + case STEPPED: + rotate = frames[i + ROTATE]; + translate = frames[i + TRANSLATE]; + scale = frames[i + SCALE]; + shear = frames[i + SHEAR]; + break; + default: + rotate = getBezierValue(time, i, ROTATE, curveType - BEZIER); + translate = getBezierValue(time, i, TRANSLATE, curveType + BEZIER_SIZE - BEZIER); + scale = getBezierValue(time, i, TRANSLATE, curveType + BEZIER_SIZE * 2 - BEZIER); + shear = getBezierValue(time, i, TRANSLATE, curveType + BEZIER_SIZE * 3 - BEZIER); } + if (blend == setup) { TransformConstraintData data = constraint.data; constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha; @@ -1559,7 +1595,7 @@ public class Animation { } /** Changes a path constraint's {@link PathConstraint#getPosition()}. */ - static public class PathConstraintPositionTimeline extends ValueCurveTimeline { + static public class PathConstraintPositionTimeline extends CurveTimeline1 { final int pathConstraintIndex; public PathConstraintPositionTimeline (int frameCount, int bezierCount, int pathConstraintIndex) { @@ -1567,7 +1603,8 @@ public class Animation { this.pathConstraintIndex = pathConstraintIndex; } - /** The index of the path constraint slot in {@link Skeleton#getPathConstraints()} that will be changed. */ + /** The index of the path constraint slot in {@link Skeleton#getPathConstraints()} that will be changed when this timeline + * is applied. */ public int getPathConstraintIndex () { return pathConstraintIndex; } @@ -1577,7 +1614,7 @@ public class Animation { PathConstraint constraint = skeleton.pathConstraints.get(pathConstraintIndex); if (!constraint.active) return; - float[] frames = this.frames; + if (time < frames[0]) { // Time is before first frame. switch (blend) { case setup: @@ -1598,7 +1635,7 @@ public class Animation { } /** Changes a path constraint's {@link PathConstraint#getSpacing()}. */ - static public class PathConstraintSpacingTimeline extends ValueCurveTimeline { + static public class PathConstraintSpacingTimeline extends CurveTimeline1 { final int pathConstraintIndex; public PathConstraintSpacingTimeline (int frameCount, int bezierCount, int pathConstraintIndex) { @@ -1606,7 +1643,8 @@ public class Animation { this.pathConstraintIndex = pathConstraintIndex; } - /** The index of the path constraint slot in {@link Skeleton#getPathConstraints()} that will be changed. */ + /** The index of the path constraint slot in {@link Skeleton#getPathConstraints()} that will be changed when this timeline + * is applied. */ public int getPathConstraintIndex () { return pathConstraintIndex; } @@ -1616,7 +1654,7 @@ public class Animation { PathConstraint constraint = skeleton.pathConstraints.get(pathConstraintIndex); if (!constraint.active) return; - float[] frames = this.frames; + if (time < frames[0]) { // Time is before first frame. switch (blend) { case setup: @@ -1638,7 +1676,7 @@ public class Animation { /** Changes a transform constraint's {@link PathConstraint#getRotateMix()} and * {@link TransformConstraint#getTranslateMix()}. */ - static public class PathConstraintMixTimeline extends PercentCurveTimeline2 { + static public class PathConstraintMixTimeline extends CurveTimeline2 { final int pathConstraintIndex; public PathConstraintMixTimeline (int frameCount, int bezierCount, int pathConstraintIndex) { @@ -1646,7 +1684,8 @@ public class Animation { this.pathConstraintIndex = pathConstraintIndex; } - /** The index of the path constraint slot in {@link Skeleton#getPathConstraints()} that will be changed. */ + /** The index of the path constraint slot in {@link Skeleton#getPathConstraints()} that will be changed when this timeline + * is applied. */ public int getPathConstraintIndex () { return pathConstraintIndex; } @@ -1656,6 +1695,7 @@ public class Animation { PathConstraint constraint = skeleton.pathConstraints.get(pathConstraintIndex); if (!constraint.active) return; + float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. switch (blend) { @@ -1671,16 +1711,23 @@ public class Animation { } float rotate, translate; - if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. - rotate = frames[frames.length - ENTRIES + VALUE1]; - translate = frames[frames.length - ENTRIES + VALUE2]; - } else { - int frameIndex = searchN(frames, time, ENTRIES), frame = frameIndex * ENTRIES; - float percent = getCurvePercent(frameIndex, time, frame, ENTRIES); - rotate = frames[frame + VALUE1]; - translate = frames[frame + VALUE2]; - rotate += (frames[frame + NEXT_VALUE1] - rotate) * percent; - translate += (frames[frame + NEXT_VALUE2] - translate) * percent; + int i = search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) { + case LINEAR: + float before = frames[i]; + rotate = frames[i + VALUE1]; + translate = frames[i + VALUE2]; + float t = (time - before) / (frames[i + ENTRIES] - before); + rotate += (frames[i + ENTRIES + VALUE1] - rotate) * t; + translate += (frames[i + ENTRIES + VALUE2] - translate) * t; + break; + case STEPPED: + rotate = frames[i + VALUE1]; + translate = frames[i + VALUE2]; + break; + default: + rotate = getBezierValue(time, i, VALUE1, curveType - BEZIER); + translate = getBezierValue(time, i, VALUE2, curveType + BEZIER_SIZE - BEZIER); } if (blend == setup) { 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 1957700a5..e6347dd4e 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java @@ -43,6 +43,9 @@ 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.CurveTimeline1; +import com.esotericsoftware.spine.Animation.CurveTimeline2; import com.esotericsoftware.spine.Animation.DeformTimeline; import com.esotericsoftware.spine.Animation.DrawOrderTimeline; import com.esotericsoftware.spine.Animation.EventTimeline; @@ -50,8 +53,6 @@ import com.esotericsoftware.spine.Animation.IkConstraintTimeline; import com.esotericsoftware.spine.Animation.PathConstraintMixTimeline; import com.esotericsoftware.spine.Animation.PathConstraintPositionTimeline; import com.esotericsoftware.spine.Animation.PathConstraintSpacingTimeline; -import com.esotericsoftware.spine.Animation.PercentCurveTimeline; -import com.esotericsoftware.spine.Animation.PercentCurveTimeline2; import com.esotericsoftware.spine.Animation.RotateTimeline; import com.esotericsoftware.spine.Animation.ScaleTimeline; import com.esotericsoftware.spine.Animation.ShearTimeline; @@ -59,7 +60,6 @@ import com.esotericsoftware.spine.Animation.Timeline; import com.esotericsoftware.spine.Animation.TransformConstraintTimeline; import com.esotericsoftware.spine.Animation.TranslateTimeline; import com.esotericsoftware.spine.Animation.TwoColorTimeline; -import com.esotericsoftware.spine.Animation.ValueCurveTimeline; import com.esotericsoftware.spine.BoneData.TransformMode; import com.esotericsoftware.spine.PathConstraintData.PositionMode; import com.esotericsoftware.spine.PathConstraintData.RotateMode; @@ -100,11 +100,9 @@ public class SkeletonBinary { static public final int CURVE_STEPPED = 1; static public final int CURVE_BEZIER = 2; - static private final Color tempColor1 = new Color(), tempColor2 = new Color(); - private final AttachmentLoader attachmentLoader; private float scale = 1; - private Array linkedMeshes = new Array(); + private final Array linkedMeshes = new Array(); public SkeletonBinary (TextureAtlas atlas) { attachmentLoader = new AtlasAttachmentLoader(atlas); @@ -585,19 +583,39 @@ public class SkeletonBinary { switch (timelineType) { case SLOT_ATTACHMENT: { AttachmentTimeline timeline = new AttachmentTimeline(frameCount, slotIndex); - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) - timeline.setFrame(frameIndex, input.readFloat(), input.readStringRef()); + for (int frame = 0; frame < frameCount; frame++) + timeline.setFrame(frame, input.readFloat(), input.readStringRef()); timelines.add(timeline); break; } case SLOT_COLOR: { ColorTimeline timeline = new ColorTimeline(frameCount, input.readInt(true), slotIndex); float time = input.readFloat(); - for (int frameIndex = 0, bezierIndex = 0;; frameIndex++) { - Color.rgba8888ToColor(tempColor1, input.readInt()); - timeline.setFrame(frameIndex, time, tempColor1.r, tempColor1.g, tempColor1.b, tempColor1.a); - if (frameIndex == frameLast) break; - bezierIndex = readCurve(input, timeline, frameIndex, bezierIndex, time, time = input.readFloat()); + int c1 = input.readInt(); + float r = ((c1 & 0xff000000) >> 24) / 255f, g = ((c1 & 0x00ff0000) >> 16) / 255f; + float b = ((c1 & 0x0000ff00) >> 8) / 255f, a = (c1 & 0x000000ff) / 255f; + for (int frame = 0, bezier = 0;; frame++) { + timeline.setFrame(frame, time, r, g, b, a); + if (frame == frameLast) break; + float time2 = input.readFloat(); + c1 = input.readInt(); + float r2 = ((c1 & 0xff000000) >> 24) / 255f, g2 = ((c1 & 0x00ff0000) >> 16) / 255f; + float b2 = ((c1 & 0x0000ff00) >> 8) / 255f, a2 = (c1 & 0x000000ff) / 255f; + switch (input.readByte()) { + case CURVE_STEPPED: + timeline.setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, r, r2); + setBezier(input, timeline, bezier++, frame, 1, time, time2, g, g2); + setBezier(input, timeline, bezier++, frame, 2, time, time2, b, b2); + setBezier(input, timeline, bezier++, frame, 3, time, time2, a, a2); + } + time = time2; + r = r2; + g = g2; + b = b2; + a = a2; } timelines.add(timeline); break; @@ -605,13 +623,42 @@ public class SkeletonBinary { case SLOT_TWO_COLOR: { TwoColorTimeline timeline = new TwoColorTimeline(frameCount, input.readInt(true), slotIndex); float time = input.readFloat(); - for (int frameIndex = 0, bezierIndex = 0;; frameIndex++) { - Color.rgba8888ToColor(tempColor1, input.readInt()); - Color.rgb888ToColor(tempColor2, input.readInt()); - timeline.setFrame(frameIndex, time, tempColor1.r, tempColor1.g, tempColor1.b, tempColor1.a, tempColor2.r, - tempColor2.g, tempColor2.b); - if (frameIndex == frameLast) break; - bezierIndex = readCurve(input, timeline, frameIndex, bezierIndex, time, time = input.readFloat()); + int c1 = input.readInt(), c2 = input.readInt(); + float r = ((c1 & 0xff000000) >> 24) / 255f, g = ((c1 & 0x00ff0000) >> 16) / 255f; + float b = ((c1 & 0x0000ff00) >> 8) / 255f, a = (c1 & 0x000000ff) / 255f; + float r2 = ((c2 & 0xff000000) >> 24) / 255f, g2 = ((c2 & 0x00ff0000) >> 16) / 255f; + float b2 = ((c2 & 0x0000ff00) >> 8) / 255f; + for (int frame = 0, bezier = 0;; frame++) { + timeline.setFrame(frame, time, r, g, b, a, r2, g2, b2); + if (frame == frameLast) break; + float time2 = input.readFloat(); + c1 = input.readInt(); + c2 = input.readInt(); + float nr = ((c1 & 0xff000000) >> 24) / 255f, ng = ((c1 & 0x00ff0000) >> 16) / 255f; + float nb = ((c1 & 0x0000ff00) >> 8) / 255f, na = (c1 & 0x000000ff) / 255f; + float nr2 = ((c2 & 0xff000000) >> 24) / 255f, ng2 = ((c2 & 0x00ff0000) >> 16) / 255f; + float nb2 = ((c2 & 0x0000ff00) >> 8) / 255f; + switch (input.readByte()) { + case CURVE_STEPPED: + timeline.setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, r, nr); + setBezier(input, timeline, bezier++, frame, 1, time, time2, g, ng); + setBezier(input, timeline, bezier++, frame, 2, time, time2, b, nb); + setBezier(input, timeline, bezier++, frame, 3, time, time2, a, na); + setBezier(input, timeline, bezier++, frame, 4, time, time2, r2, nr2); + setBezier(input, timeline, bezier++, frame, 5, time, time2, g2, ng2); + setBezier(input, timeline, bezier++, frame, 6, time, time2, b2, nb2); + } + time = time2; + r = nr; + g = ng; + b = nb; + a = na; + r2 = nr2; + g2 = ng2; + b2 = nb2; } timelines.add(timeline); break; @@ -625,23 +672,18 @@ public class SkeletonBinary { int boneIndex = input.readInt(true); for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) { switch (input.readByte()) { - case BONE_ROTATE: { + case BONE_ROTATE: timelines.add(readTimeline(input, new RotateTimeline(input.readInt(true), input.readInt(true), boneIndex), 1)); break; - } - case BONE_TRANSLATE: { + case BONE_TRANSLATE: timelines .add(readTimeline(input, new TranslateTimeline(input.readInt(true), input.readInt(true), boneIndex), scale)); break; - } - case BONE_SCALE: { + case BONE_SCALE: timelines.add(readTimeline(input, new ScaleTimeline(input.readInt(true), input.readInt(true), boneIndex), 1)); break; - } - case BONE_SHEAR: { + case BONE_SHEAR: timelines.add(readTimeline(input, new ShearTimeline(input.readInt(true), input.readInt(true), boneIndex), 1)); - break; - } } } } @@ -650,12 +692,22 @@ public class SkeletonBinary { for (int i = 0, n = input.readInt(true); i < n; i++) { int index = input.readInt(true), frameCount = input.readInt(true), frameLast = frameCount - 1; IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount, input.readInt(true), index); - float time = input.readFloat(); - for (int frameIndex = 0, bezierIndex = 0;; frameIndex++) { - timeline.setFrame(frameIndex, time, input.readFloat(), input.readFloat() * scale, input.readByte(), - input.readBoolean(), input.readBoolean()); - if (frameIndex == frameLast) break; - bezierIndex = readCurve(input, timeline, frameIndex, bezierIndex, time, time = input.readFloat()); + float time = input.readFloat(), mix = input.readFloat(), softness = input.readFloat() * scale; + for (int frame = 0, bezier = 0;; frame++) { + timeline.setFrame(frame, time, mix, softness, input.readByte(), input.readBoolean(), input.readBoolean()); + if (frame == frameLast) break; + float time2 = input.readFloat(), mix2 = input.readFloat() * scale, softness2 = input.readFloat() * scale; + switch (input.readByte()) { + case CURVE_STEPPED: + timeline.setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, mix, mix2); + setBezier(input, timeline, bezier++, frame, 1, time, time2, softness, softness2); + } + time = time2; + mix = mix2; + softness = softness2; } timelines.add(timeline); } @@ -664,11 +716,28 @@ public class SkeletonBinary { for (int i = 0, n = input.readInt(true); i < n; i++) { int index = input.readInt(true), frameCount = input.readInt(true), frameLast = frameCount - 1; TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount, input.readInt(true), index); - float time = input.readFloat(); - for (int frameIndex = 0, bezierIndex = 0;; frameIndex++) { - timeline.setFrame(frameIndex, time, input.readFloat(), input.readFloat(), input.readFloat(), input.readFloat()); - if (frameIndex == frameLast) break; - bezierIndex = readCurve(input, timeline, frameIndex, bezierIndex, time, time = input.readFloat()); + float time = input.readFloat(), rotateMix = input.readFloat(), translateMix = input.readFloat(), + scaleMix = input.readFloat(), shearMix = input.readFloat(); + for (int frame = 0, bezier = 0;; frame++) { + timeline.setFrame(frame, time, rotateMix, translateMix, scaleMix, shearMix); + if (frame == frameLast) break; + float time2 = input.readFloat(), rotateMix2 = input.readFloat(), translateMix2 = input.readFloat(), + scaleMix2 = input.readFloat(), shearMix2 = input.readFloat(); + switch (input.readByte()) { + case CURVE_STEPPED: + timeline.setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, rotateMix, rotateMix2); + setBezier(input, timeline, bezier++, frame, 1, time, time2, translateMix, translateMix2); + setBezier(input, timeline, bezier++, frame, 2, time, time2, scaleMix, scaleMix2); + setBezier(input, timeline, bezier++, frame, 3, time, time2, shearMix, shearMix2); + } + time = time2; + rotateMix = rotateMix2; + translateMix = translateMix2; + scaleMix = scaleMix2; + shearMix = shearMix2; } timelines.add(timeline); } @@ -679,23 +748,19 @@ public class SkeletonBinary { PathConstraintData data = skeletonData.pathConstraints.get(index); for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) { switch (input.readByte()) { - case PATH_POSITION: { + case PATH_POSITION: timelines .add(readTimeline(input, new PathConstraintSpacingTimeline(input.readInt(true), input.readInt(true), index), data.spacingMode == SpacingMode.length || data.spacingMode == SpacingMode.fixed ? scale : 1)); break; - } - case PATH_SPACING: { + case PATH_SPACING: timelines .add(readTimeline(input, new PathConstraintPositionTimeline(input.readInt(true), input.readInt(true), index), data.positionMode == PositionMode.fixed ? scale : 1)); break; - } - case PATH_MIX: { + case PATH_MIX: timelines .add(readTimeline(input, new PathConstraintMixTimeline(input.readInt(true), input.readInt(true), index), 1)); - break; - } } } } @@ -709,13 +774,13 @@ public class SkeletonBinary { VertexAttachment attachment = (VertexAttachment)skin.getAttachment(slotIndex, input.readStringRef()); boolean weighted = attachment.getBones() != null; float[] vertices = attachment.getVertices(); - int deformLength = weighted ? vertices.length / 3 * 2 : vertices.length; + int deformLength = weighted ? (vertices.length / 3) << 1 : vertices.length; int frameCount = input.readInt(true), frameLast = frameCount - 1; DeformTimeline timeline = new DeformTimeline(frameCount, input.readInt(true), slotIndex, attachment); float time = input.readFloat(); - for (int frameIndex = 0, bezierIndex = 0;; frameIndex++) { + for (int frame = 0, bezier = 0;; frame++) { float[] deform; int end = input.readInt(true); if (end == 0) @@ -736,9 +801,17 @@ public class SkeletonBinary { deform[v] += vertices[v]; } } - timeline.setFrame(frameIndex, time, deform); - if (frameIndex == frameLast) break; - bezierIndex = readCurve(input, timeline, frameIndex, bezierIndex, time, time = input.readFloat()); + timeline.setFrame(frame, time, deform); + if (frame == frameLast) break; + float time2 = input.readFloat(); + switch (input.readByte()) { + case CURVE_STEPPED: + timeline.setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, 0, 1); + } + time = time2; } timelines.add(timeline); } @@ -803,53 +876,50 @@ public class SkeletonBinary { return new Animation(name, timelines, duration); } - private Timeline readTimeline (SkeletonInput input, ValueCurveTimeline timeline, float scale) throws IOException { + private Timeline readTimeline (SkeletonInput input, CurveTimeline1 timeline, float scale) throws IOException { float time = input.readFloat(), value = input.readFloat() * scale; - for (int frameIndex = 0, bezierIndex = 0, frameLast = timeline.getFrameCount() - 1;; frameIndex++) { - timeline.setFrame(frameIndex, time, value); - if (frameIndex == frameLast) break; - bezierIndex = readCurve(input, timeline, frameIndex, bezierIndex, time, value, time = input.readFloat(), - value = input.readFloat() * scale); + for (int frame = 0, bezier = 0, frameLast = timeline.getFrameCount() - 1;; frame++) { + timeline.setFrame(frame, time, value); + if (frame == frameLast) break; + float time2 = input.readFloat(), value2 = input.readFloat() * scale; + switch (input.readByte()) { + case CURVE_STEPPED: + timeline.setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, value, value2); + } + time = time2; + value = value2; } return timeline; } - private Timeline readTimeline (SkeletonInput input, PercentCurveTimeline2 timeline, float scale) throws IOException { - float time = input.readFloat(); - for (int frameIndex = 0, bezierIndex = 0, frameLast = timeline.getFrameCount() - 1;; frameIndex++) { - timeline.setFrame(frameIndex, time, input.readFloat() * scale, input.readFloat() * scale); - if (frameIndex == frameLast) break; - bezierIndex = readCurve(input, timeline, frameIndex, bezierIndex, time, time = input.readFloat()); + private Timeline readTimeline (SkeletonInput input, CurveTimeline2 timeline, float scale) throws IOException { + float time = input.readFloat(), value1 = input.readFloat() * scale, value2 = input.readFloat() * scale; + for (int frame = 0, bezier = 0, frameLast = timeline.getFrameCount() - 1;; frame++) { + timeline.setFrame(frame, time, value1, value2); + if (frame == frameLast) break; + float time2 = input.readFloat(), nvalue1 = input.readFloat() * scale, nvalue2 = input.readFloat() * scale; + switch (input.readByte()) { + case CURVE_STEPPED: + timeline.setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, value1, nvalue1); + setBezier(input, timeline, bezier++, frame, 1, time, time2, value2, nvalue2); + } + time = time2; + value1 = nvalue1; + value2 = nvalue2; } return timeline; } - int readCurve (SkeletonInput input, PercentCurveTimeline timeline, int frameIndex, int bezierIndex, float time1, float time2) - throws IOException { - switch (input.readByte()) { - case CURVE_STEPPED: - timeline.setStepped(frameIndex); - break; - case CURVE_BEZIER: - timeline.setBezier(frameIndex, bezierIndex++, time1, input.readFloat(), input.readFloat(), input.readFloat(), - input.readFloat(), time2); - break; - } - return bezierIndex; - } - - int readCurve (SkeletonInput input, ValueCurveTimeline timeline, int frameIndex, int bezierIndex, float time1, float value1, - float time2, float value2) throws IOException { - switch (input.readByte()) { - case CURVE_STEPPED: - timeline.setStepped(frameIndex); - break; - case CURVE_BEZIER: - timeline.setBezier(frameIndex, bezierIndex++, time1, value1, input.readFloat(), input.readFloat(), input.readFloat(), - input.readFloat(), time2, value2); - break; - } - return bezierIndex; + void setBezier (SkeletonInput input, CurveTimeline timeline, int bezier, int frame, int value, float time1, float time2, + float value1, float value2) throws IOException { + timeline.setBezier(bezier, frame, value, time1, value1, input.readFloat(), input.readFloat(), input.readFloat(), + input.readFloat(), time2, value2); } static class Vertices { 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 39ccc29a1..cbec778d1 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -43,6 +43,9 @@ 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.CurveTimeline1; +import com.esotericsoftware.spine.Animation.CurveTimeline2; import com.esotericsoftware.spine.Animation.DeformTimeline; import com.esotericsoftware.spine.Animation.DrawOrderTimeline; import com.esotericsoftware.spine.Animation.EventTimeline; @@ -50,8 +53,6 @@ import com.esotericsoftware.spine.Animation.IkConstraintTimeline; import com.esotericsoftware.spine.Animation.PathConstraintMixTimeline; import com.esotericsoftware.spine.Animation.PathConstraintPositionTimeline; import com.esotericsoftware.spine.Animation.PathConstraintSpacingTimeline; -import com.esotericsoftware.spine.Animation.PercentCurveTimeline; -import com.esotericsoftware.spine.Animation.PercentCurveTimeline2; import com.esotericsoftware.spine.Animation.RotateTimeline; import com.esotericsoftware.spine.Animation.ScaleTimeline; import com.esotericsoftware.spine.Animation.ShearTimeline; @@ -59,7 +60,6 @@ import com.esotericsoftware.spine.Animation.Timeline; import com.esotericsoftware.spine.Animation.TransformConstraintTimeline; import com.esotericsoftware.spine.Animation.TranslateTimeline; import com.esotericsoftware.spine.Animation.TwoColorTimeline; -import com.esotericsoftware.spine.Animation.ValueCurveTimeline; import com.esotericsoftware.spine.BoneData.TransformMode; import com.esotericsoftware.spine.PathConstraintData.PositionMode; import com.esotericsoftware.spine.PathConstraintData.RotateMode; @@ -84,7 +84,7 @@ import com.esotericsoftware.spine.attachments.VertexAttachment; public class SkeletonJson { private final AttachmentLoader attachmentLoader; private float scale = 1; - private Array linkedMeshes = new Array(); + private final Array linkedMeshes = new Array(); public SkeletonJson (TextureAtlas atlas) { attachmentLoader = new AtlasAttachmentLoader(atlas); @@ -415,7 +415,7 @@ public class SkeletonJson { mesh.setRegionUVs(uvs); mesh.updateUVs(); - if (map.has("hull")) mesh.setHullLength(map.require("hull").asInt() * 2); + if (map.has("hull")) mesh.setHullLength(map.require("hull").asInt() << 1); if (map.has("edges")) mesh.setEdges(map.require("edges").asShortArray()); return mesh; } @@ -512,39 +512,94 @@ public class SkeletonJson { if (timelineName.equals("attachment")) { AttachmentTimeline timeline = new AttachmentTimeline(timelineMap.size, slot.index); - for (int frameIndex = 0; keyMap != null; keyMap = keyMap.next, frameIndex++) - timeline.setFrame(frameIndex, keyMap.getFloat("time", 0), keyMap.getString("name")); + for (int frame = 0; keyMap != null; keyMap = keyMap.next, frame++) + timeline.setFrame(frame, keyMap.getFloat("time", 0), keyMap.getString("name")); timelines.add(timeline); } else if (timelineName.equals("color")) { - ColorTimeline timeline = new ColorTimeline(timelineMap.size, timelineMap.size, slot.index); - float time = timelineMap.child.getFloat("time", 0); - for (int frameIndex = 0, bezierIndex = 0;; frameIndex++) { - Color color = Color.valueOf(keyMap.getString("color")); - timeline.setFrame(frameIndex, time, color.r, color.g, color.b, color.a); + ColorTimeline timeline = new ColorTimeline(timelineMap.size, timelineMap.size << 2, slot.index); + float time = keyMap.getFloat("time", 0); + String color = keyMap.getString("color"); + float r = Integer.parseInt(color.substring(0, 2), 16) / 255f; + float g = Integer.parseInt(color.substring(2, 4), 16) / 255f; + float b = Integer.parseInt(color.substring(4, 6), 16) / 255f; + float a = Integer.parseInt(color.substring(6, 8), 16) / 255f; + for (int frame = 0, bezier = 0;; frame++) { + timeline.setFrame(frame, time, r, g, b, a); JsonValue nextMap = keyMap.next; if (nextMap == null) { - timeline.shrink(bezierIndex); + timeline.shrink(bezier); break; } - bezierIndex = readCurve(keyMap, timeline, frameIndex, bezierIndex, time, time = nextMap.getFloat("time", 0)); + float time2 = nextMap.getFloat("time", 0); + color = nextMap.getString("color"); + float nr = Integer.parseInt(color.substring(0, 2), 16) / 255f; + float ng = Integer.parseInt(color.substring(2, 4), 16) / 255f; + float nb = Integer.parseInt(color.substring(4, 6), 16) / 255f; + float na = Integer.parseInt(color.substring(6, 8), 16) / 255f; + JsonValue curve = keyMap.get("curve"); + if (curve != null) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng); + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb); + bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, a, na); + } + time = time2; + r = nr; + g = ng; + b = nb; + a = na; keyMap = nextMap; } timelines.add(timeline); } else if (timelineName.equals("twoColor")) { - TwoColorTimeline timeline = new TwoColorTimeline(timelineMap.size, timelineMap.size, slot.index); - float time = timelineMap.child.getFloat("time", 0); - for (int frameIndex = 0, bezierIndex = 0;; frameIndex++) { - Color light = Color.valueOf(keyMap.getString("light")), dark = Color.valueOf(keyMap.getString("dark")); - timeline.setFrame(frameIndex, time, light.r, light.g, light.b, light.a, // - dark.r, dark.g, dark.b); + TwoColorTimeline timeline = new TwoColorTimeline(timelineMap.size, timelineMap.size * 7, slot.index); + float time = keyMap.getFloat("time", 0); + String color = keyMap.getString("light"); + float r = Integer.parseInt(color.substring(0, 2), 16) / 255f; + float g = Integer.parseInt(color.substring(2, 4), 16) / 255f; + float b = Integer.parseInt(color.substring(4, 6), 16) / 255f; + float a = Integer.parseInt(color.substring(6, 8), 16) / 255f; + color = keyMap.getString("dark"); + float r2 = Integer.parseInt(color.substring(0, 2), 16) / 255f; + float g2 = Integer.parseInt(color.substring(2, 4), 16) / 255f; + float b2 = Integer.parseInt(color.substring(4, 6), 16) / 255f; + for (int frame = 0, bezier = 0;; frame++) { + timeline.setFrame(frame, time, r, g, b, a, r2, g2, b2); JsonValue nextMap = keyMap.next; if (nextMap == null) { - timeline.shrink(bezierIndex); + timeline.shrink(bezier); break; } - bezierIndex = readCurve(keyMap, timeline, frameIndex, bezierIndex, time, time = nextMap.getFloat("time", 0)); + float time2 = nextMap.getFloat("time", 0); + color = nextMap.getString("light"); + float nr = Integer.parseInt(color.substring(0, 2), 16) / 255f; + float ng = Integer.parseInt(color.substring(2, 4), 16) / 255f; + float nb = Integer.parseInt(color.substring(4, 6), 16) / 255f; + float na = Integer.parseInt(color.substring(6, 8), 16) / 255f; + color = nextMap.getString("dark"); + float nr2 = Integer.parseInt(color.substring(8, 10), 16) / 255f; + float ng2 = Integer.parseInt(color.substring(10, 12), 16) / 255f; + float nb2 = Integer.parseInt(color.substring(12, 14), 16) / 255f; + JsonValue curve = keyMap.get("curve"); + if (curve != null) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng); + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb); + bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, a, na); + bezier = readCurve(curve, timeline, bezier, frame, 4, time, time2, r2, nr2); + bezier = readCurve(curve, timeline, bezier, frame, 5, time, time2, g2, ng2); + bezier = readCurve(curve, timeline, bezier, frame, 6, time, time2, b2, nb2); + } + time = time2; + r = nr; + g = ng; + b = nb; + a = na; + r2 = nr2; + g2 = ng2; + b2 = nb2; keyMap = nextMap; } timelines.add(timeline); @@ -566,13 +621,15 @@ public class SkeletonJson { if (timelineName.equals("rotate")) timelines.add(readTimeline(keyMap, new RotateTimeline(timelineMap.size, timelineMap.size, bone.index), 0, 1)); else if (timelineName.equals("translate")) { - timelines - .add(readTimeline(keyMap, new TranslateTimeline(timelineMap.size, timelineMap.size, bone.index), 0, scale)); - } else if (timelineName.equals("scale")) - timelines.add(readTimeline(keyMap, new ScaleTimeline(timelineMap.size, timelineMap.size, bone.index), 1, 1)); - else if (timelineName.equals("shear")) - timelines.add(readTimeline(keyMap, new ShearTimeline(timelineMap.size, timelineMap.size, bone.index), 0, 1)); - else + TranslateTimeline timeline = new TranslateTimeline(timelineMap.size, timelineMap.size << 1, bone.index); + timelines.add(readTimeline(keyMap, timeline, "x", "y", 0, scale)); + } else if (timelineName.equals("scale")) { + ScaleTimeline timeline = new ScaleTimeline(timelineMap.size, timelineMap.size << 1, bone.index); + timelines.add(readTimeline(keyMap, timeline, "x", "y", 1, 1)); + } else if (timelineName.equals("shear")) { + ShearTimeline timeline = new ShearTimeline(timelineMap.size, timelineMap.size << 1, bone.index); + timelines.add(readTimeline(keyMap, timeline, "x", "y", 0, 1)); + } else throw new RuntimeException("Invalid timeline type for a bone: " + timelineName + " (" + boneMap.name + ")"); } } @@ -582,19 +639,28 @@ public class SkeletonJson { JsonValue keyMap = timelineMap.child; if (keyMap == null) continue; IkConstraintData constraint = skeletonData.findIkConstraint(timelineMap.name); - IkConstraintTimeline timeline = new IkConstraintTimeline(timelineMap.size, timelineMap.size, + IkConstraintTimeline timeline = new IkConstraintTimeline(timelineMap.size, timelineMap.size << 1, skeletonData.getIkConstraints().indexOf(constraint, true)); float time = keyMap.getFloat("time", 0); - for (int frameIndex = 0, bezierIndex = 0;; frameIndex++) { - timeline.setFrame(frameIndex, time, keyMap.getFloat("mix", 1), keyMap.getFloat("softness", 0) * scale, - keyMap.getBoolean("bendPositive", true) ? 1 : -1, keyMap.getBoolean("compress", false), - keyMap.getBoolean("stretch", false)); + float mix = keyMap.getFloat("mix", 1), softness = keyMap.getFloat("softness", 0) * scale; + for (int frame = 0, bezier = 0;; frame++) { + timeline.setFrame(frame, time, mix, softness, keyMap.getBoolean("bendPositive", true) ? 1 : -1, + keyMap.getBoolean("compress", false), keyMap.getBoolean("stretch", false)); JsonValue nextMap = keyMap.next; if (nextMap == null) { - timeline.shrink(bezierIndex); + timeline.shrink(bezier); break; } - bezierIndex = readCurve(keyMap, timeline, frameIndex, bezierIndex, time, time = nextMap.getFloat("time", 0)); + float time2 = nextMap.getFloat("time", 0); + float mix2 = nextMap.getFloat("mix", 1), softness2 = nextMap.getFloat("softness", 0) * scale; + JsonValue curve = keyMap.get("curve"); + if (curve != null) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mix, mix2); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, softness, softness2); + } + time = time2; + mix = mix2; + softness = softness2; keyMap = nextMap; } timelines.add(timeline); @@ -605,18 +671,33 @@ public class SkeletonJson { JsonValue keyMap = timelineMap.child; if (keyMap == null) continue; TransformConstraintData constraint = skeletonData.findTransformConstraint(timelineMap.name); - TransformConstraintTimeline timeline = new TransformConstraintTimeline(timelineMap.size, timelineMap.size, + TransformConstraintTimeline timeline = new TransformConstraintTimeline(timelineMap.size, timelineMap.size << 2, skeletonData.getTransformConstraints().indexOf(constraint, true)); float time = keyMap.getFloat("time", 0); - for (int frameIndex = 0, bezierIndex = 0;; frameIndex++) { - timeline.setFrame(frameIndex, time, keyMap.getFloat("rotateMix", 1), keyMap.getFloat("translateMix", 1), - keyMap.getFloat("scaleMix", 1), keyMap.getFloat("shearMix", 1)); + float rotateMix = keyMap.getFloat("rotateMix", 1), translateMix = keyMap.getFloat("translateMix", 1); + float scaleMix = keyMap.getFloat("scaleMix", 1), shearMix = keyMap.getFloat("shearMix", 1); + for (int frame = 0, bezier = 0;; frame++) { + timeline.setFrame(frame, time, rotateMix, translateMix, scaleMix, shearMix); JsonValue nextMap = keyMap.next; if (nextMap == null) { - timeline.shrink(bezierIndex); + timeline.shrink(bezier); break; } - bezierIndex = readCurve(keyMap, timeline, frameIndex, bezierIndex, time, time = nextMap.getFloat("time", 0)); + float time2 = nextMap.getFloat("time", 0); + float rotateMix2 = nextMap.getFloat("rotateMix", 1), translateMix2 = nextMap.getFloat("translateMix", 1); + float scaleMix2 = nextMap.getFloat("scaleMix", 1), shearMix2 = nextMap.getFloat("shearMix", 1); + JsonValue curve = keyMap.get("curve"); + if (curve != null) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, rotateMix, rotateMix2); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, translateMix, translateMix2); + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, scaleMix, scaleMix2); + bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, shearMix, shearMix2); + } + time = time2; + rotateMix = rotateMix2; + translateMix = translateMix2; + scaleMix = scaleMix2; + shearMix = shearMix2; keyMap = nextMap; } timelines.add(timeline); @@ -631,16 +712,16 @@ public class SkeletonJson { JsonValue keyMap = timelineMap.child; if (keyMap == null) continue; String timelineName = timelineMap.name; - if (timelineName.equals("position")) { - timelines.add(readTimeline(keyMap, new PathConstraintPositionTimeline(timelineMap.size, timelineMap.size, index), - 0, data.positionMode == PositionMode.fixed ? scale : 1)); + CurveTimeline1 timeline = new PathConstraintPositionTimeline(timelineMap.size, timelineMap.size, index); + timelines.add(readTimeline(keyMap, timeline, 0, data.positionMode == PositionMode.fixed ? scale : 1)); } else if (timelineName.equals("spacing")) { - timelines.add(readTimeline(keyMap, new PathConstraintSpacingTimeline(timelineMap.size, timelineMap.size, index), 0, + CurveTimeline1 timeline = new PathConstraintSpacingTimeline(timelineMap.size, timelineMap.size, index); + timelines.add(readTimeline(keyMap, timeline, 0, data.spacingMode == SpacingMode.length || data.spacingMode == SpacingMode.fixed ? scale : 1)); } else if (timelineName.equals("mix")) { - timelines - .add(readTimeline(keyMap, new PathConstraintMixTimeline(timelineMap.size, timelineMap.size, index), 1, 1)); + CurveTimeline2 timeline = new PathConstraintMixTimeline(timelineMap.size, timelineMap.size << 1, index); + timelines.add(readTimeline(keyMap, timeline, "rotateMix", "translateMix", 1, 1)); } } } @@ -660,11 +741,11 @@ public class SkeletonJson { 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; + int deformLength = weighted ? (vertices.length / 3) << 1 : vertices.length; DeformTimeline timeline = new DeformTimeline(timelineMap.size, timelineMap.size, slot.index, attachment); float time = keyMap.getFloat("time", 0); - for (int frameIndex = 0, bezierIndex = 0;; frameIndex++) { + for (int frame = 0, bezier = 0;; frame++) { float[] deform; JsonValue verticesValue = keyMap.get("vertices"); if (verticesValue == null) @@ -683,16 +764,19 @@ public class SkeletonJson { } } - timeline.setFrame(frameIndex, time, deform); + timeline.setFrame(frame, time, deform); JsonValue nextMap = keyMap.next; if (nextMap == null) { - timeline.shrink(bezierIndex); + timeline.shrink(bezier); break; } - bezierIndex = readCurve(keyMap, timeline, frameIndex, bezierIndex, time, time = nextMap.getFloat("time", 0)); + float time2 = nextMap.getFloat("time", 0); + JsonValue curve = keyMap.get("curve"); + if (curve != null) bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, 0, 1); + time = time2; keyMap = nextMap; } - //timelines.add(timeline); + timelines.add(timeline); } } } @@ -703,8 +787,8 @@ public class SkeletonJson { if (drawOrdersMap != null) { DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrdersMap.size); int slotCount = skeletonData.slots.size; - int frameIndex = 0; - for (JsonValue drawOrderMap = drawOrdersMap.child; drawOrderMap != null; drawOrderMap = drawOrderMap.next, frameIndex++) { + int frame = 0; + for (JsonValue drawOrderMap = drawOrdersMap.child; drawOrderMap != null; drawOrderMap = drawOrderMap.next, frame++) { int[] drawOrder = null; JsonValue offsets = drawOrderMap.get("offsets"); if (offsets != null) { @@ -729,7 +813,7 @@ public class SkeletonJson { for (int i = slotCount - 1; i >= 0; i--) if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; } - timeline.setFrame(frameIndex, drawOrderMap.getFloat("time", 0), drawOrder); + timeline.setFrame(frame, drawOrderMap.getFloat("time", 0), drawOrder); } timelines.add(timeline); } @@ -738,8 +822,8 @@ public class SkeletonJson { JsonValue eventsMap = map.get("events"); if (eventsMap != null) { EventTimeline timeline = new EventTimeline(eventsMap.size); - int frameIndex = 0; - for (JsonValue eventMap = eventsMap.child; eventMap != null; eventMap = eventMap.next, frameIndex++) { + int frame = 0; + for (JsonValue eventMap = eventsMap.child; eventMap != null; eventMap = eventMap.next, frame++) { EventData eventData = skeletonData.findEvent(eventMap.getString("name")); if (eventData == null) throw new SerializationException("Event not found: " + eventMap.getString("name")); Event event = new Event(eventMap.getFloat("time", 0), eventData); @@ -750,7 +834,7 @@ public class SkeletonJson { event.volume = eventMap.getFloat("volume", eventData.volume); event.balance = eventMap.getFloat("balance", eventData.balance); } - timeline.setFrame(frameIndex, event); + timeline.setFrame(frame, event); } timelines.add(timeline); } @@ -762,62 +846,71 @@ public class SkeletonJson { skeletonData.animations.add(new Animation(name, timelines, duration)); } - private ValueCurveTimeline readTimeline (JsonValue keyMap, ValueCurveTimeline timeline, float defaultValue, float scale) { + private Timeline readTimeline (JsonValue keyMap, CurveTimeline1 timeline, float defaultValue, float scale) { float time = keyMap.getFloat("time", 0), value = keyMap.getFloat("value", defaultValue); - int bezierIndex = 0; - for (int frameIndex = 0;; frameIndex++) { - timeline.setFrame(frameIndex, time, value); + int bezier = 0; + for (int frame = 0;; frame++) { + timeline.setFrame(frame, time, value); JsonValue nextMap = keyMap.next; if (nextMap == null) break; - bezierIndex = readCurve(keyMap, timeline, frameIndex, bezierIndex, // - time, value, time = nextMap.getFloat("time", 0), value = nextMap.getFloat("value", defaultValue)); + float time2 = nextMap.getFloat("time", 0); + float value2 = nextMap.getFloat("value", defaultValue); + JsonValue curve = keyMap.get("curve"); + if (curve != null) bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, value, value2); + time = time2; + value = value2; keyMap = nextMap; } - timeline.shrink(bezierIndex); + timeline.shrink(bezier); return timeline; } - private PercentCurveTimeline2 readTimeline (JsonValue keyMap, PercentCurveTimeline2 timeline, float defaultValue, + private Timeline readTimeline (JsonValue keyMap, CurveTimeline2 timeline, String name1, String name2, float defaultValue, float scale) { float time = keyMap.getFloat("time", 0); - int bezierIndex = 0; - for (int frameIndex = 0;; frameIndex++) { - float x = keyMap.getFloat("x", defaultValue), y = keyMap.getFloat("y", defaultValue); - timeline.setFrame(frameIndex, time, x * scale, y * scale); + float value1 = keyMap.getFloat(name1, defaultValue), value2 = keyMap.getFloat(name2, defaultValue); + int bezier = 0; + for (int frame = 0;; frame++) { + timeline.setFrame(frame, time, value1, value2); JsonValue nextMap = keyMap.next; if (nextMap == null) break; - bezierIndex = readCurve(keyMap, timeline, frameIndex, bezierIndex, time, time = nextMap.getFloat("time", 0)); + float time2 = nextMap.getFloat("time", 0); + float nvalue1 = nextMap.getFloat(name1, defaultValue), nvalue2 = nextMap.getFloat(name2, defaultValue); + JsonValue curve = keyMap.get("curve"); + if (curve != null) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, value1, nvalue1); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, value2, nvalue2); + } + time = time2; + value1 = nvalue1; + value2 = nvalue2; keyMap = nextMap; } - timeline.shrink(bezierIndex); + timeline.shrink(bezier); return timeline; } - int readCurve (JsonValue map, PercentCurveTimeline timeline, int frameIndex, int bezierIndex, float time1, float time2) { - JsonValue curve = map.get("curve"); - if (curve != null) { - if (curve.isString()) - timeline.setStepped(frameIndex); - else { - timeline.setBezier(frameIndex, bezierIndex++, time1, curve.asFloat(), map.getFloat("c2", 0), map.getFloat("c3", 1), - map.getFloat("c4", 1), time2); - } + int readCurve (JsonValue curve, CurveTimeline timeline, int bezier, int frame, int value, float time1, float time2, + float value1, float value2) { + if (curve.isString()) { + if (value != 0) timeline.setStepped(frame); + } else { + curve = curve.get(value * 4); + float cx1 = curve.asFloat(); + curve = curve.next; + float cy1 = curve.asFloat(); + curve = curve.next; + float cx2 = curve.asFloat(); + curve = curve.next; + float cy2 = curve.asFloat(); + setBezier(timeline, frame, value, bezier++, time1, value1, cx1, cy1, cx2, cy2, time2, value2); } - return bezierIndex; + return bezier; } - int readCurve (JsonValue map, ValueCurveTimeline timeline, int frameIndex, int bezierIndex, float time1, float value1, - float time2, float value2) { - JsonValue curve = map.get("curve"); - if (curve != null) { - if (curve.isString()) - timeline.setStepped(frameIndex); - else { - timeline.setBezier(frameIndex, bezierIndex++, time1, value1, curve.asFloat(), map.getFloat("c2", 0), - map.getFloat("c3", 1), map.getFloat("c4", 1), time2, value2); - } - } - return bezierIndex; + void setBezier (CurveTimeline timeline, int frame, int value, int bezier, float time1, float value1, float cx1, float cy1, + float cx2, float cy2, float time2, float value2) { + timeline.setBezier(bezier, frame, value, time1, value1, cx1, cy1, cx2, cy2, time2, value2); } static class LinkedMesh {