diff --git a/spine-csharp/src/Animation.cs b/spine-csharp/src/Animation.cs
index 70c1b33ee..fca4473cb 100644
--- a/spine-csharp/src/Animation.cs
+++ b/spine-csharp/src/Animation.cs
@@ -27,51 +27,91 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
+using NUnit.Framework.Constraints;
using System;
using System.Collections.Generic;
+using System.Linq;
namespace Spine {
///
- /// 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 {
internal String name;
internal ExposedList timelines;
- internal HashSet timelineIds;
+ internal HashSet timelineIds;
internal float duration;
public Animation (string name, ExposedList timelines, float duration) {
if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
- if (timelines == null) throw new ArgumentNullException("timelines", "timelines cannot be null.");
- // Note: avoiding reallocations by adding all hash set entries at
- // once (EnsureCapacity() is only available in newer .Net versions).
- int[] propertyIDs = new int[timelines.Count];
- for (int i = 0; i < timelines.Count; ++i) {
- propertyIDs[i] = timelines.Items[i].PropertyId;
- }
- this.timelineIds = new HashSet(propertyIDs);
+
this.name = name;
- this.timelines = timelines;
+ SetTimelines(timelines);
this.duration = duration;
}
- public ExposedList Timelines { get { return timelines; } set { timelines = value; } }
+ public ExposedList Timelines {
+ get { return timelines; }
+ set { SetTimelines(value); }
+ }
- /// The duration of the animation in seconds, which is the highest time of all keys in the timeline.
+ public void SetTimelines (ExposedList timelines) {
+ if (timelines == null) throw new ArgumentNullException("timelines", "timelines cannot be null.");
+ this.timelines = timelines;
+ // Note: avoiding reallocations by adding all hash set entries at
+ // once (EnsureCapacity() is only available in newer .Net versions).
+ int idCount = 0;
+ int timelinesCount = timelines.Count;
+ var timelinesItems = timelines.Items;
+ for (int t = 0; t < timelinesCount; ++t) {
+ idCount += timelinesItems[t].PropertyIds.Length;
+ }
+ string[] propertyIds = new string[idCount];
+ int currentId = 0;
+ for (int t = 0; t < timelinesCount; ++t) {
+ var ids = timelinesItems[t].PropertyIds;
+ for (int i = 0, idsLength = ids.Length; i < idsLength; ++i) {
+ propertyIds[currentId++] = ids[i];
+ }
+ }
+ this.timelineIds = new HashSet(propertyIds);
+ }
+
+ /// 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 Duration { get { return duration; } set { duration = value; } }
/// The animation's name, which is unique across all animations in the skeleton.
public string Name { get { return name; } }
- /// Whether the timeline with the property id is contained in this animation.
- public bool HasTimeline (int id) {
- return timelineIds.Contains(id);
+ /// Returns true if this animation contains a timeline with any of the specified property IDs.
+ public bool HasTimeline (string[] propertyIds) {
+ foreach (string id in propertyIds)
+ if (timelineIds.Contains(id)) return true;
+ return false;
}
- /// Applies all the animation's timelines to the specified skeleton.
+ /// Applies the animation's timelines to the specified skeleton.
///
- public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events, float alpha, MixBlend blend,
- MixDirection direction) {
+ /// The skeleton the animation is being applied to. This provides access to the bones, slots, and other skeleton
+ /// components the timelines may change.
+ /// 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.
+ /// 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 and loop is
+ /// true then the animation will repeat, else the last frame will be applied.
+ /// If true, the animation repeats after the .
+ /// If any events are fired, they are added to this list. Can be null to ignore fired events or if no timelines
+ /// fire events.
+ /// 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).
+ /// Controls how mixing is applied when alpha < 1.
+ /// Indicates whether the timelines are mixing in or out. Used by timelines which perform instant transitions,
+ /// such as or .
+ public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events, float alpha,
+ MixBlend blend, MixDirection direction) {
if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
if (loop && duration != 0) {
@@ -79,94 +119,51 @@ namespace Spine {
if (lastTime > 0) lastTime %= duration;
}
- ExposedList timelines = this.timelines;
- for (int i = 0, n = timelines.Count; i < n; i++)
- timelines.Items[i].Apply(skeleton, lastTime, time, events, alpha, blend, direction);
+ var timelines = this.timelines.Items;
+ for (int i = 0, n = this.timelines.Count; i < n; i++)
+ timelines[i].Apply(skeleton, lastTime, time, events, alpha, blend, direction);
}
override public string ToString () {
return name;
}
- /// After the first and before the last entry.
- /// Index of first value greater than the target.
- internal static int BinarySearch (float[] values, float target, int step) {
- int low = 0;
- int high = values.Length / step - 2;
- if (high == 0) return step;
- int current = (int)((uint)high >> 1);
- while (true) {
- if (values[(current + 1) * step] <= target)
- low = current + 1;
- else
- high = current;
- if (low == high) return (low + 1) * step;
- current = (int)((uint)(low + high) >> 1);
- }
+ /// Search using a stride of 1.
+ /// Must be >= the first value in frames.
+ /// The index of the first value <= time.
+ internal static int Search (float[] frames, float time) {
+ int n = frames.Length;
+ for (int i = 1; i < n; i++)
+ if (frames[i] > time) return i - 1;
+ return n - 1;
}
- /// After the first and before the last entry.
- internal static int BinarySearch (float[] values, float target) {
- int low = 0;
- int high = values.Length - 2;
- if (high == 0) return 1;
- int current = (int)((uint)high >> 1);
- while (true) {
- if (values[current + 1] <= target)
- low = current + 1;
- else
- high = current;
- if (low == high) return (low + 1);
- current = (int)((uint)(low + high) >> 1);
- }
- }
-
- internal static int LinearSearch (float[] values, float target, int step) {
- for (int i = 0, last = values.Length - step; i <= last; i += step)
- if (values[i] > target) return i;
- return -1;
+ /// Search using the specified stride.
+ /// Must be >= the first value in frames.
+ /// The index of the first value <= time.
+ internal 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;
}
}
///
- /// The interface for all timelines.
- public interface Timeline {
- /// Applies this timeline to the skeleton.
- /// The skeleton the timeline is being applied to. This provides access to the bones, slots, and other
- /// skeleton components the timeline may change.
- /// The time this timeline was last applied. Timelines such as trigger only at specific
- /// times rather than every frame. In that case, the timeline triggers everything between lastTime
- /// (exclusive) and time (inclusive).
- /// The time within the animation. Most timelines find the key before and the key after this time so they can
- /// interpolate between the keys.
- /// If any events are fired, they are added to this list. Can be null to ignore firing events or if the
- /// timeline does not fire events.
- /// 0 applies the current or setup value (depending on blend). 1 applies the timeline value.
- /// Between 0 and 1 applies a value between the current or setup value and the timeline value. 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 (layered).
- /// Controls how mixing is applied when alpha < 1.
- /// Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions,
- /// such as or , and other such as {@link ScaleTimeline}.
- void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, MixDirection direction);
- /// Uniquely encodes both the type of this timeline and the skeleton property that it affects.
- int PropertyId { get; }
- }
-
- ///
- /// Controls how a timeline is mixed with the setup or current pose.
+ /// Controls how timeline values are mixed with setup pose values or current pose values when a timeline is applied with
+ /// alpha < 1.
///
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 or
- /// , use the setup value before the first key.
+ /// , use the setup value before the first frame.
///
/// First is intended for the first animations applied, not for animations layered on top of those.
///
@@ -174,8 +171,8 @@ namespace Spine {
///
///
- /// 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.
///
@@ -183,275 +180,362 @@ namespace Spine {
///
///
- /// 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.
+ /// Add is intended for animations layered on top of others, not for the first animations applied. Properties
+ /// 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.
///
public enum MixDirection {
In,
Out
}
- internal enum TimelineType {
- Rotate = 0, Translate, Scale, Shear, //
- Attachment, Color, Deform, //
+ internal enum Property {
+ Rotate=0, TranslateX, TranslateY, ScaleX, ScaleY, ShearX, ShearY, //
+ RGBA, RGB2, //
+ Attachment, Deform, //
Event, DrawOrder, //
IkConstraint, TransformConstraint, //
- PathConstraintPosition, PathConstraintSpacing, PathConstraintMix, //
- TwoColor
+ PathConstraintPosition, PathConstraintSpacing, PathConstraintMix
+ }
+
+ ///
+ /// The base class for all timelines.
+ public abstract class Timeline {
+ private readonly string[] propertyIds;
+ internal readonly float[] frames;
+
+ /// Unique identifiers for the properties the timeline modifies.
+ public Timeline (int frameCount, params string[] propertyIds) {
+ if (propertyIds == null) throw new System.ArgumentNullException("propertyIds", "propertyIds cannot be null.");
+ this.propertyIds = propertyIds;
+ frames = new float[frameCount * FrameEntries];
+ }
+
+ /// Uniquely encodes both the type of this timeline and the skeleton properties that it affects.
+ public string[] PropertyIds {
+ get { return propertyIds; }
+ }
+
+ /// The time in seconds and any other values for each frame.
+ public float[] Frames {
+ get { return frames; }
+ }
+
+ /// The number of entries stored per frame.
+ public virtual int FrameEntries {
+ get { return 1; }
+ }
+
+ /// The number of frames for this timeline.
+ public int FrameCount {
+ get { return frames.Length / FrameEntries; }
+ }
+
+ public float Duration {
+ get {
+ return frames[frames.Length - FrameEntries];
+ }
+ }
+
+ /// Applies this timeline to the skeleton.
+ /// The skeleton the timeline is being applied to. This provides access to the bones, slots, and other
+ /// skeleton components the timeline may change.
+ /// The time this timeline was last applied. Timelines such as 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.
+ /// 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.
+ /// 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.
+ /// 0 applies the current or setup value (depending on blend). 1 applies the timeline value.
+ /// Between 0 and 1 applies a value between the current or setup value and the timeline value.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).
+ /// Controls how mixing is applied when alpha < 1.
+ /// Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions,
+ /// such as or , and other such as .
+ public abstract void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha,
+ MixBlend blend, MixDirection direction);
}
/// An interface for timelines which change the property of a bone.
public interface IBoneTimeline {
- /// The index of the bone in that will be changed.
+ /// The index of the bone in that will be changed when this timeline is applied.
int BoneIndex { get; }
}
/// An interface for timelines which change the property of a slot.
public interface ISlotTimeline {
- /// The index of the slot in that will be changed.
+ /// The index of the slot in that will be changed when this timeline is applied.
int SlotIndex { get; }
}
- /// The base class for timelines that use interpolation between key frame values.
- abstract public class CurveTimeline : Timeline {
- protected const float LINEAR = 0, STEPPED = 1, BEZIER = 2;
- protected const int BEZIER_SIZE = 10 * 2 - 1;
+ /// The base class for timelines that interpolate between frame values using stepped, linear, or a Bezier curve.
+ public abstract class CurveTimeline : Timeline {
+ public const int LINEAR = 0, STEPPED = 1, BEZIER = 2, BEZIER_SIZE = 18;
- internal float[] curves; // type, x, y, ...
+ internal float[] curves;
/// The number of key frames for this timeline.
- public int FrameCount { get { return curves.Length / BEZIER_SIZE + 1; } }
- public CurveTimeline (int frameCount) {
- if (frameCount <= 0) throw new ArgumentOutOfRangeException("frameCount must be > 0: ");
- curves = new float[(frameCount - 1) * BEZIER_SIZE];
+ /// The maximum number of Bezier curves. See .
+ /// Unique identifiers for the properties the timeline modifies.
+ public CurveTimeline (int frameCount, int bezierCount, params string[] propertyIds)
+ : base(frameCount, propertyIds) {
+ curves = new float[frameCount + bezierCount * BEZIER_SIZE];
+ curves[frameCount - 1] = STEPPED;
}
- abstract public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, MixDirection direction);
-
- abstract public int PropertyId { get; }
-
- /// Sets the specified key frame to linear interpolation.
- public void SetLinear (int frameIndex) {
- curves[frameIndex * BEZIER_SIZE] = LINEAR;
+ /// Sets the specified frame to linear interpolation.
+ /// Between 0 and frameCount - 1, inclusive.
+ public void SetLinear (int frame) {
+ curves[frame] = LINEAR;
}
- /// Sets the specified key frame to stepped interpolation.
- public void SetStepped (int frameIndex) {
- curves[frameIndex * BEZIER_SIZE] = STEPPED;
+ /// Sets the specified frame to stepped interpolation.
+ /// Between 0 and frameCount - 1, inclusive.
+ public void SetStepped (int frame) {
+ curves[frame] = STEPPED;
}
- /// Returns the interpolation type for the specified key frame.
- /// Linear is 0, stepped is 1, Bezier is 2.
- public float GetCurveType (int frameIndex) {
- int index = frameIndex * BEZIER_SIZE;
- if (index == curves.Length) return LINEAR;
- float type = curves[index];
- if (type == LINEAR) return LINEAR;
- if (type == STEPPED) return STEPPED;
- return BEZIER;
+ /// Returns the interpolation type for the specified frame.
+ /// Between 0 and frameCount - 1, inclusive.
+ /// , or + the index of the Bezier segments.
+ public float GetCurveType (int frame) {
+ return (int)curves[frame];
}
- /// Sets the specified key frame to Bezier interpolation. cx1 and cx2 are from 0 to 1,
- /// representing the percent of time between the two key frames. cy1 and cy2 are the percent of the
- /// difference between the key frame's values.
- public void SetCurve (int frameIndex, float cx1, float cy1, float cx2, float cy2) {
- float tmpx = (-cx1 * 2 + cx2) * 0.03f, tmpy = (-cy1 * 2 + cy2) * 0.03f;
- float dddfx = ((cx1 - cx2) * 3 + 1) * 0.006f, dddfy = ((cy1 - cy2) * 3 + 1) * 0.006f;
- float ddfx = tmpx * 2 + dddfx, ddfy = tmpy * 2 + dddfy;
- float dfx = cx1 * 0.3f + tmpx + dddfx * 0.16666667f, dfy = cy1 * 0.3f + tmpy + dddfy * 0.16666667f;
+ /// 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 = FrameCount + bezierCount * BEZIER_SIZE;
+ if (curves.Length > size) {
+ float[] newCurves = new float[size];
+ Array.Copy(curves, 0, newCurves, 0, size);
+ curves = newCurves;
+ }
+ }
+
+
+ ///
+ /// Stores the segments for the specified Bezier curve. For timelines that modify multiple values, there may be more than
+ /// one curve per frame.
+ /// The ordinal of this Bezier curve for this timeline, between 0 and bezierCount - 1 (specified
+ /// in the constructor), inclusive.
+ /// Between 0 and frameCount - 1, inclusive.
+ /// The index of the value for this frame that this curve is used for.
+ /// The time for the first key.
+ /// The value for the first key.
+ /// The time for the first Bezier handle.
+ /// The value for the first Bezier handle.
+ /// The time of the second Bezier handle.
+ /// The value for the second Bezier handle.
+ /// The time for the second key.
+ /// 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 = frameIndex * BEZIER_SIZE;
float[] curves = this.curves;
- curves[i++] = BEZIER;
-
- float x = dfx, y = dfy;
- for (int n = i + BEZIER_SIZE - 1; i < n; i += 2) {
+ int i = FrameCount + 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;
+ float dx = (cx1 - time1) * 0.3f + tmpx + dddx * 0.16666667f, dy = (cy1 - value1) * 0.3f + tmpy + dddy * 0.16666667f;
+ float x = time1 + dx, y = value1 + dy;
+ for (int n = i + BEZIER_SIZE; i < n; i += 2) {
curves[i] = x;
curves[i + 1] = y;
- dfx += ddfx;
- dfy += ddfy;
- ddfx += dddfx;
- ddfy += dddfy;
- x += dfx;
- y += dfy;
+ dx += ddx;
+ dy += ddy;
+ ddx += dddx;
+ ddy += dddy;
+ x += dx;
+ y += dy;
}
}
- /// Returns the interpolated percentage for the specified key frame and linear percentage.
- public float GetCurvePercent (int frameIndex, float percent) {
- percent = MathUtils.Clamp (percent, 0, 1);
+ ///
+ /// Returns the Bezier interpolated value for the specified time.
+ /// The index into for the values of the frame before time.
+ /// The offset from frameIndex to the value this curve is used for.
+ /// The index of the Bezier segments. See .
+ public float GetBezierValue (float time, int frameIndex, int valueOffset, int i) {
float[] curves = this.curves;
- int i = frameIndex * BEZIER_SIZE;
- float type = curves[i];
- if (type == LINEAR) return percent;
- if (type == STEPPED) return 0;
- i++;
- float x = 0;
- for (int start = i, n = i + BEZIER_SIZE - 1; i < n; i += 2) {
- x = curves[i];
- if (x >= percent) {
- if (i == start) return curves[i + 1] * percent / x; // First point is 0,0.
- float prevX = curves[i - 2], prevY = curves[i - 1];
- return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX);
+ if (curves[i] > time) {
+ 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 + (time - x) / (curves[i] - x) * (curves[i + 1] - y);
}
}
- float y = curves[i - 1];
- return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1.
+ frameIndex += FrameEntries;
+ { // scope added to prevent compile error "float x and y declared in enclosing scope"
+ float x = curves[n - 2], y = curves[n - 1];
+ return y + (time - x) / (frames[frameIndex] - x) * (frames[frameIndex + valueOffset] - y);
+ }
+ }
+ }
+
+ /// The base class for a that sets one property.
+ public abstract class CurveTimeline1 : CurveTimeline {
+ public const int ENTRIES = 2;
+ internal const int VALUE = 1;
+
+ /// The maximum number of Bezier curves. See .
+ /// Unique identifiers for the properties the timeline modifies.
+ public CurveTimeline1 (int frameCount, int bezierCount, params string[] propertyIds)
+ : base(frameCount, bezierCount, propertyIds) {
+ }
+
+ public override int FrameEntries {
+ get { return ENTRIES; }
+ }
+
+ /// Sets the time and value for the specified frame.
+ /// Between 0 and frameCount, inclusive.
+ /// 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 which sets two properties.
+ public abstract class CurveTimeline2 : CurveTimeline {
+ public const int ENTRIES = 3;
+ internal const int VALUE1 = 1, VALUE2 = 2;
+
+ /// The maximum number of Bezier curves. See .
+ /// Unique identifiers for the properties the timeline modifies.
+ public CurveTimeline2 (int frameCount, int bezierCount, params string[] propertyIds)
+ :base (frameCount, bezierCount, propertyIds) {
+ }
+
+ public override int FrameEntries {
+ get { return ENTRIES; }
+ }
+
+ /// Sets the time and values for the specified frame.
+ /// Between 0 and frameCount, inclusive.
+ /// 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 .
- public class RotateTimeline : CurveTimeline, IBoneTimeline {
- public const int ENTRIES = 2;
- internal const int PREV_TIME = -2, PREV_ROTATION = -1;
- internal const int ROTATION = 1;
+ public class RotateTimeline : CurveTimeline1, IBoneTimeline {
+ readonly int boneIndex;
- internal int boneIndex;
- internal float[] frames; // time, degrees, ...
-
- public RotateTimeline (int frameCount)
- : base(frameCount) {
- frames = new float[frameCount << 1];
+ public RotateTimeline (int frameCount, int bezierCount, int boneIndex)
+ : base(frameCount, bezierCount, (int)Property.Rotate + "|" + boneIndex) {
+ this.boneIndex = boneIndex;
}
- override public int PropertyId {
- get { return ((int)TimelineType.Rotate << 24) + boneIndex; }
- }
- /// The index of the bone in that will be changed.
public int BoneIndex {
- set {
- if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0.");
- this.boneIndex = value;
- }
get {
return boneIndex;
}
}
- /// The time in seconds and rotation in degrees for each key frame.
- public float[] Frames { get { return frames; } set { frames = value; } }
-
- /// Sets the time in seconds and the rotation in degrees for the specified key frame.
- public void SetFrame (int frameIndex, float time, float degrees) {
- frameIndex <<= 1;
- frames[frameIndex] = time;
- frames[frameIndex + ROTATION] = degrees;
- }
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend,
MixDirection direction) {
Bone bone = skeleton.bones.Items[boneIndex];
if (!bone.active) return;
- float[] frames = this.frames;
+
if (time < frames[0]) { // Time is before first frame.
switch (blend) {
case MixBlend.Setup:
bone.rotation = bone.data.rotation;
return;
case MixBlend.First:
- float r = bone.data.rotation - bone.rotation;
- bone.rotation += (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * alpha;
+ bone.rotation += (bone.data.rotation - bone.rotation) * alpha;
return;
}
return;
}
- if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
- float r = frames[frames.Length + PREV_ROTATION];
- switch (blend) {
- case MixBlend.Setup:
- bone.rotation = bone.data.rotation + r * alpha;
- break;
- case MixBlend.First:
- case MixBlend.Replace:
- r += bone.data.rotation - bone.rotation;
- r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360;
- goto case MixBlend.Add; // Fall through.
-
- case MixBlend.Add:
- bone.rotation += r * alpha;
- break;
- }
- return;
- }
-
- // Interpolate between the previous frame and the current frame.
- int frame = Animation.BinarySearch(frames, time, ENTRIES);
- float prevRotation = frames[frame + PREV_ROTATION];
- float frameTime = frames[frame];
- float percent = GetCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
- // scope for 'r' to prevent compile error.
- {
- float r = frames[frame + ROTATION] - prevRotation;
- r = prevRotation + (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * percent;
- switch (blend) {
- case MixBlend.Setup:
- bone.rotation = bone.data.rotation + (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * alpha;
- break;
- case MixBlend.First:
- case MixBlend.Replace:
- r += bone.data.rotation - bone.rotation;
- goto case MixBlend.Add; // Fall through.
- case MixBlend.Add:
- bone.rotation += (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * alpha;
- break;
- }
+ float r = GetCurveValue(time);
+ switch (blend) {
+ case MixBlend.Setup:
+ bone.rotation = bone.data.rotation + r * alpha;
+ break;
+ case MixBlend.First:
+ case MixBlend.Replace:
+ r += bone.data.rotation - bone.rotation;
+ goto case MixBlend.Add; // Fall through.
+ case MixBlend.Add:
+ bone.rotation += r * alpha;
+ break;
}
}
}
/// Changes a bone's local and .
- public class TranslateTimeline : CurveTimeline, IBoneTimeline {
- public const int ENTRIES = 3;
- protected const int PREV_TIME = -3, PREV_X = -2, PREV_Y = -1;
- protected const int X = 1, Y = 2;
+ public class TranslateTimeline : CurveTimeline2, IBoneTimeline {
+ readonly int boneIndex;
- internal int boneIndex;
- internal float[] frames; // time, x, y, ...
-
- public TranslateTimeline (int frameCount)
- : base(frameCount) {
- frames = new float[frameCount * ENTRIES];
+ public TranslateTimeline (int frameCount, int bezierCount, int boneIndex)
+ : base(frameCount, bezierCount, //
+ (int)Property.TranslateX + "|" + boneIndex, //
+ (int) Property.TranslateY + "|" + boneIndex) {
+ this.boneIndex = boneIndex;
}
- override public int PropertyId {
- get { return ((int)TimelineType.Translate << 24) + boneIndex; }
- }
-
- /// The index of the bone in that will be changed.
public int BoneIndex {
- set {
- if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0.");
- this.boneIndex = value;
- }
get {
return boneIndex;
}
}
- /// The time in seconds, x, and y values for each key frame.
- public float[] Frames { get { return frames; } set { frames = value; } }
-
-
- /// Sets the time in seconds, x, and y values for the specified key frame.
- public void SetFrame (int frameIndex, float time, float x, float y) {
- frameIndex *= ENTRIES;
- frames[frameIndex] = time;
- frames[frameIndex + X] = x;
- frames[frameIndex + Y] = y;
- }
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend,
MixDirection direction) {
Bone bone = skeleton.bones.Items[boneIndex];
if (!bone.active) return;
+
float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame.
switch (blend) {
@@ -468,21 +552,26 @@ namespace Spine {
}
float x, y;
- if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
- x = frames[frames.Length + PREV_X];
- y = frames[frames.Length + PREV_Y];
- } else {
- // Interpolate between the previous frame and the current frame.
- int frame = Animation.BinarySearch(frames, time, ENTRIES);
- x = frames[frame + PREV_X];
- y = frames[frame + PREV_Y];
- float frameTime = frames[frame];
- float percent = GetCurvePercent(frame / ENTRIES - 1,
- 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
-
- x += (frames[frame + X] - x) * percent;
- y += (frames[frame + Y] - y) * percent;
+ int i = Animation.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);
+ break;
}
+
switch (blend) {
case MixBlend.Setup:
bone.x = bone.data.x + x * alpha;
@@ -502,19 +591,27 @@ namespace Spine {
}
/// Changes a bone's local and .
- public class ScaleTimeline : TranslateTimeline, IBoneTimeline {
- public ScaleTimeline (int frameCount)
- : base(frameCount) {
+ public class ScaleTimeline : CurveTimeline2, IBoneTimeline {
+ readonly int boneIndex;
+
+ public ScaleTimeline (int frameCount, int bezierCount, int boneIndex)
+ : base(frameCount, bezierCount, //
+ (int)Property.ScaleX + "|" + boneIndex, //
+ (int)Property.ScaleY + "|" + boneIndex) {
+ this.boneIndex = boneIndex;
}
- override public int PropertyId {
- get { return ((int)TimelineType.Scale << 24) + boneIndex; }
+ public int BoneIndex {
+ get {
+ return boneIndex;
+ }
}
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend,
MixDirection direction) {
Bone bone = skeleton.bones.Items[boneIndex];
if (!bone.active) return;
+
float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame.
switch (blend) {
@@ -531,21 +628,28 @@ namespace Spine {
}
float x, y;
- if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
- x = frames[frames.Length + PREV_X] * bone.data.scaleX;
- y = frames[frames.Length + PREV_Y] * bone.data.scaleY;
- } else {
- // Interpolate between the previous frame and the current frame.
- int frame = Animation.BinarySearch(frames, time, ENTRIES);
- x = frames[frame + PREV_X];
- y = frames[frame + PREV_Y];
- float frameTime = frames[frame];
- float percent = GetCurvePercent(frame / ENTRIES - 1,
- 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
-
- x = (x + (frames[frame + X] - x) * percent) * bone.data.scaleX;
- y = (y + (frames[frame + Y] - y) * percent) * bone.data.scaleY;
+ int i = Animation.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);
+ break;
}
+ x *= bone.data.scaleX;
+ y *= bone.data.scaleY;
+
if (alpha == 1) {
if (blend == MixBlend.Add) {
bone.scaleX += x - bone.data.scaleX;
@@ -607,19 +711,27 @@ namespace Spine {
}
/// Changes a bone's local and .
- public class ShearTimeline : TranslateTimeline, IBoneTimeline {
- public ShearTimeline (int frameCount)
- : base(frameCount) {
+ public class ShearTimeline : CurveTimeline2, IBoneTimeline {
+ readonly int boneIndex;
+
+ public ShearTimeline (int frameCount, int bezierCount, int boneIndex)
+ : base(frameCount, bezierCount, //
+ (int)Property.ShearX + "|" + boneIndex, //
+ (int)Property.ShearY + "|" + boneIndex) {
+ this.boneIndex = boneIndex;
}
- override public int PropertyId {
- get { return ((int)TimelineType.Shear << 24) + boneIndex; }
+ public int BoneIndex {
+ get {
+ return boneIndex;
+ }
}
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend,
MixDirection direction) {
Bone bone = skeleton.bones.Items[boneIndex];
if (!bone.active) return;
+
float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame.
switch (blend) {
@@ -636,21 +748,27 @@ namespace Spine {
}
float x, y;
- if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
- x = frames[frames.Length + PREV_X];
- y = frames[frames.Length + PREV_Y];
- } else {
- // Interpolate between the previous frame and the current frame.
- int frame = Animation.BinarySearch(frames, time, ENTRIES);
- x = frames[frame + PREV_X];
- y = frames[frame + PREV_Y];
- float frameTime = frames[frame];
- float percent = GetCurvePercent(frame / ENTRIES - 1,
- 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
-
- x = x + (frames[frame + X] - x) * percent;
- y = y + (frames[frame + Y] - y) * percent;
+ int i = Animation.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);
+ break;
}
+
+
switch (blend) {
case MixBlend.Setup:
bone.shearX = bone.data.shearX + x * alpha;
@@ -672,48 +790,42 @@ namespace Spine {
/// Changes a slot's .
public class ColorTimeline : CurveTimeline, ISlotTimeline {
public const int ENTRIES = 5;
- protected const int PREV_TIME = -5, PREV_R = -4, PREV_G = -3, PREV_B = -2, PREV_A = -1;
protected const int R = 1, G = 2, B = 3, A = 4;
- internal int slotIndex;
- internal float[] frames; // time, r, g, b, a, ...
+ readonly int slotIndex;
- public ColorTimeline (int frameCount)
- : base(frameCount) {
- frames = new float[frameCount * ENTRIES];
+ public ColorTimeline (int frameCount, int bezierCount, int slotIndex)
+ : base(frameCount, bezierCount, //
+ (int)Property.RGBA + "|" + slotIndex) {
+ this.slotIndex = slotIndex;
+ }
+ public override int FrameEntries {
+ get { return ENTRIES; }
}
- override public int PropertyId {
- get { return ((int)TimelineType.Color << 24) + slotIndex; }
- }
-
- /// The index of the slot in that will be changed.
public int SlotIndex {
- set {
- if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0.");
- this.slotIndex = value;
- }
get {
return slotIndex;
}
}
- /// The time in seconds, red, green, blue, and alpha values for each key frame.
- public float[] Frames { get { return frames; } set { frames = value; } }
- /// 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.
+ /// Between 0 and frameCount, inclusive.
+ /// 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;
}
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend,
MixDirection direction) {
Slot slot = skeleton.slots.Items[slotIndex];
if (!slot.bone.active) return;
+
float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame.
var slotData = slot.data;
@@ -736,28 +848,34 @@ namespace Spine {
}
float r, g, b, a;
- if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
- int i = frames.Length;
- r = frames[i + PREV_R];
- g = frames[i + PREV_G];
- b = frames[i + PREV_B];
- a = frames[i + PREV_A];
- } else {
- // Interpolate between the previous frame and the current frame.
- int frame = Animation.BinarySearch(frames, time, ENTRIES);
- r = frames[frame + PREV_R];
- g = frames[frame + PREV_G];
- b = frames[frame + PREV_B];
- a = frames[frame + PREV_A];
- float frameTime = frames[frame];
- float percent = GetCurvePercent(frame / ENTRIES - 1,
- 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
-
- r += (frames[frame + R] - r) * percent;
- g += (frames[frame + G] - g) * percent;
- b += (frames[frame + B] - b) * percent;
- a += (frames[frame + A] - a) * percent;
+ int i = Animation.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];
+ 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);
+ break;
}
+
if (alpha == 1) {
slot.r = r;
slot.g = g;
@@ -789,52 +907,52 @@ namespace Spine {
/// Changes a slot's and for two color tinting.
public class TwoColorTimeline : CurveTimeline, ISlotTimeline {
public const int ENTRIES = 8;
- protected const int PREV_TIME = -8, PREV_R = -7, PREV_G = -6, PREV_B = -5, PREV_A = -4;
- protected const int PREV_R2 = -3, PREV_G2 = -2, PREV_B2 = -1;
protected const int R = 1, G = 2, B = 3, A = 4, R2 = 5, G2 = 6, B2 = 7;
- internal int slotIndex;
- internal float[] frames; // time, r, g, b, a, r2, g2, b2, ...
+ readonly int slotIndex;
- public TwoColorTimeline (int frameCount) :
- base(frameCount) {
- frames = new float[frameCount * ENTRIES];
+ public TwoColorTimeline (int frameCount, int bezierCount, int slotIndex)
+ : base(frameCount, bezierCount, //
+ (int)Property.RGBA + "|" + slotIndex, //
+ (int) Property.RGB2 + "|" + slotIndex) {
+ this.slotIndex = slotIndex;
}
- override public int PropertyId {
- get { return ((int)TimelineType.TwoColor << 24) + slotIndex; }
- }
-
- /// The index of the slot in that will be changed.
- public int SlotIndex {
- set {
- if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0.");
- this.slotIndex = value;
+ public override int FrameEntries {
+ get {
+ return ENTRIES;
}
+ }
+
+ ///
+ /// The index of the slot in that will be changed when this timeline is applied. The
+ /// must have a dark color available.
+ public int SlotIndex {
get {
return slotIndex;
}
}
- /// The time in seconds, red, green, blue, and alpha values for each key frame.
- public float[] Frames { get { return frames; } }
- /// 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 *= ENTRIES;
- 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.
+ /// Between 0 and frameCount, inclusive.
+ /// 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;
}
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend,
MixDirection direction) {
Slot slot = skeleton.slots.Items[slotIndex];
if (!slot.bone.active) return;
+
float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame.
var slotData = slot.data;
@@ -868,37 +986,46 @@ namespace Spine {
}
float r, g, b, a, r2, g2, b2;
- if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
- int i = frames.Length;
- r = frames[i + PREV_R];
- g = frames[i + PREV_G];
- b = frames[i + PREV_B];
- a = frames[i + PREV_A];
- r2 = frames[i + PREV_R2];
- g2 = frames[i + PREV_G2];
- b2 = frames[i + PREV_B2];
- } else {
- // Interpolate between the previous frame and the current frame.
- int frame = Animation.BinarySearch(frames, time, ENTRIES);
- r = frames[frame + PREV_R];
- g = frames[frame + PREV_G];
- b = frames[frame + PREV_B];
- a = frames[frame + PREV_A];
- r2 = frames[frame + PREV_R2];
- g2 = frames[frame + PREV_G2];
- b2 = frames[frame + PREV_B2];
- float frameTime = frames[frame];
- float percent = GetCurvePercent(frame / ENTRIES - 1,
- 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
-
- r += (frames[frame + R] - r) * percent;
- g += (frames[frame + G] - g) * percent;
- b += (frames[frame + B] - b) * percent;
- a += (frames[frame + A] - a) * percent;
- r2 += (frames[frame + R2] - r2) * percent;
- g2 += (frames[frame + G2] - g2) * percent;
- b2 += (frames[frame + B2] - b2) * percent;
+ int i = Animation.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];
+ a = frames[i + A];
+ r2 = frames[i + R2];
+ g2 = frames[i + G2];
+ b2 = frames[i + B2];
+ 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);
+ break;
}
+
if (alpha == 1) {
slot.r = r;
slot.g = g;
@@ -944,49 +1071,41 @@ namespace Spine {
/// Changes a slot's .
public class AttachmentTimeline : Timeline, ISlotTimeline {
- internal int slotIndex;
- internal float[] frames; // time, ...
- internal string[] attachmentNames;
+ readonly int slotIndex;
+ readonly string[] attachmentNames;
- public AttachmentTimeline (int frameCount) {
- frames = new float[frameCount];
+ public AttachmentTimeline (int frameCount, int slotIndex)
+ : base(frameCount, (int)Property.Attachment + "|" + slotIndex) {
+ this.slotIndex = slotIndex;
attachmentNames = new String[frameCount];
}
- public int PropertyId {
- get { return ((int)TimelineType.Attachment << 24) + slotIndex; }
- }
-
- /// The number of key frames for this timeline.
- public int FrameCount { get { return frames.Length; } }
-
- /// The index of the slot in that will be changed.
public int SlotIndex {
- set {
- if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0.");
- this.slotIndex = value;
- }
get {
return slotIndex;
}
}
- /// The time in seconds for each key frame.
- public float[] Frames { get { return frames; } set { frames = value; } }
-
- /// The attachment name for each key frame. May contain null values to clear the attachment.
- public string[] AttachmentNames { get { return attachmentNames; } set { attachmentNames = value; } }
-
- /// 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;
+ /// The attachment name for each frame. May contain null values to clear the attachment.
+ public string[] AttachmentNames {
+ get {
+ return attachmentNames;
+ }
}
- public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend,
+ /// Sets the time and attachment name for the specified frame.
+ /// Between 0 and frameCount, inclusive.
+ /// The frame time in seconds.
+ public void SetFrame (int frame, float time, String attachmentName) {
+ frames[frame] = time;
+ attachmentNames[frame] = attachmentName;
+ }
+
+ public override void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend,
MixDirection direction) {
Slot slot = skeleton.slots.Items[slotIndex];
if (!slot.bone.active) return;
+
if (direction == MixDirection.Out) {
if (blend == MixBlend.Setup) SetAttachment(skeleton, slot, slot.data.attachmentName);
return;
@@ -998,13 +1117,7 @@ namespace Spine {
return;
}
- int frameIndex;
- if (time >= frames[frames.Length - 1]) // Time is after last frame.
- frameIndex = frames.Length - 1;
- else
- frameIndex = Animation.BinarySearch(frames, time) - 1;
-
- SetAttachment(skeleton, slot, attachmentNames[frameIndex]);
+ SetAttachment(skeleton, slot, attachmentNames[Animation.Search(frames, time)]);
}
private void SetAttachment (Skeleton skeleton, Slot slot, string attachmentName) {
@@ -1014,46 +1127,98 @@ namespace Spine {
/// Changes a slot's to deform a .
public class DeformTimeline : CurveTimeline, ISlotTimeline {
- internal int slotIndex;
- internal VertexAttachment attachment;
- internal float[] frames; // time, ...
- internal float[][] frameVertices;
+ readonly int slotIndex;
+ readonly VertexAttachment attachment;
+ internal float[][] vertices;
- public DeformTimeline (int frameCount)
- : base(frameCount) {
- frames = new float[frameCount];
- frameVertices = new float[frameCount][];
+ public DeformTimeline (int frameCount, int bezierCount, int slotIndex, VertexAttachment attachment)
+ : base(frameCount, bezierCount, (int)Property.Deform + "|" + slotIndex + "|" + attachment.Id) {
+ this.slotIndex = slotIndex;
+ this.attachment = attachment;
+ vertices = new float[frameCount][];
}
- override public int PropertyId {
- get { return ((int)TimelineType.Deform << 27) + attachment.id + slotIndex; }
- }
-
- /// The index of the slot in that will be changed.
public int SlotIndex {
- set {
- if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0.");
- this.slotIndex = value;
- }
get {
return slotIndex;
}
}
/// The attachment that will be deformed.
- public VertexAttachment Attachment { get { return attachment; } set { attachment = value; } }
+ ///
+ public VertexAttachment Attachment {
+ get {
+ return attachment;
+ }
+ }
- /// The time in seconds for each key frame.
- public float[] Frames { get { return frames; } set { frames = value; } }
+ /// The vertices for each frame.
+ public float[][] Vertices {
+ get {
+ return vertices;
+ }
+ }
- /// The vertices for each key frame.
- public float[][] Vertices { get { return frameVertices; } set { frameVertices = value; } }
-
-
- /// Sets the time in seconds and the vertices for the specified key frame.
+ /// Sets the time and vertices for the specified frame.
+ /// Between 0 and frameCount, inclusive.
+ /// The frame time in seconds.
/// 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;
+ }
+
+ /// Ignored (0 is used for a deform timeline).
+ /// Ignored (1 is used for a deform timeline).
+ 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 = FrameCount + 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.
+ /// 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 + FrameEntries] - 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);
+ }
+ }
+ { // scope added to prevent compile error "float x and y declared in enclosing scope"
+ float x = curves[n - 2], y = curves[n - 1];
+ return y + (1 - y) * (time - x) / (frames[frame + FrameEntries] - x);
+ }
}
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend,
@@ -1066,8 +1231,8 @@ namespace Spine {
var deformArray = slot.Deform;
if (deformArray.Count == 0) blend = MixBlend.Setup;
- float[][] frameVertices = this.frameVertices;
- int vertexCount = frameVertices[0].Length;
+ float[][] vertices = this.vertices;
+ int vertexCount = vertices[0].Length;
float[] frames = this.frames;
float[] deform;
@@ -1113,7 +1278,7 @@ namespace Spine {
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 == MixBlend.Add) {
if (vertexAttachment.bones == null) {
@@ -1170,12 +1335,10 @@ namespace Spine {
return;
}
- // Interpolate between the previous frame and the current frame.
- int frame = Animation.BinarySearch(frames, time);
- float[] prevVertices = frameVertices[frame - 1];
- float[] nextVertices = frameVertices[frame];
- float frameTime = frames[frame];
- float percent = GetCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime));
+ int frame = Animation.Search(frames, time);
+ float percent = GetCurvePercent(time, frame);
+ float[] prevVertices = vertices[frame];
+ float[] nextVertices = vertices[frame + 1];
if (alpha == 1) {
if (blend == MixBlend.Add) {
@@ -1252,37 +1415,34 @@ namespace Spine {
/// Fires an when specific animation times are reached.
public class EventTimeline : Timeline {
- internal float[] frames; // time, ...
- private Event[] events;
+ readonly static string[] propertyIds = { ((int)Property.Event).ToString() };
+ readonly Event[] events;
- public EventTimeline (int frameCount) {
- frames = new float[frameCount];
+ public EventTimeline (int frameCount)
+ : base(frameCount, propertyIds) {
events = new Event[frameCount];
}
- public int PropertyId {
- get { return ((int)TimelineType.Event << 24); }
+ /// The event for each frame.
+ public Event[] Events {
+ get {
+ return events;
+ }
}
- /// The number of key frames for this timeline.
- public int FrameCount { get { return frames.Length; } }
-
- /// The time in seconds for each key frame.
- public float[] Frames { get { return frames; } set { frames = value; } }
-
- /// The event for each key frame.
- public Event[] Events { get { return events; } set { events = value; } }
-
- /// Sets the time in seconds and the event for the specified key frame.
- public void SetFrame (int frameIndex, Event e) {
- frames[frameIndex] = e.Time;
- events[frameIndex] = e;
+ /// Sets the time and event for the specified frame.
+ /// Between 0 and frameCount, inclusive.
+ public void SetFrame (int frame, Event e) {
+ frames[frame] = e.time;
+ events [frame] = e;
}
/// Fires events for frames > lastTime and <= time.
- public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend,
- MixDirection direction) {
+ public override void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha,
+ MixBlend blend, MixDirection direction) {
+
if (firedEvents == null) return;
+
float[] frames = this.frames;
int frameCount = frames.Length;
@@ -1293,83 +1453,73 @@ namespace Spine {
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 = Animation.BinarySearch(frames, lastTime);
- float frameTime = frames[frame];
- while (frame > 0) { // Fire multiple events with the same frame.
- if (frames[frame - 1] != frameTime) break;
- frame--;
+ i = Animation.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 .
public class DrawOrderTimeline : Timeline {
- internal float[] frames; // time, ...
- private int[][] drawOrders;
+ static readonly string[] propertyIds = { ((int)Property.DrawOrder).ToString() };
- public DrawOrderTimeline (int frameCount) {
- frames = new float[frameCount];
+ readonly int[][] drawOrders;
+
+ public DrawOrderTimeline (int frameCount)
+ : base(frameCount, propertyIds) {
drawOrders = new int[frameCount][];
}
- public int PropertyId {
- get { return ((int)TimelineType.DrawOrder << 24); }
+ /// The draw order for each frame.
+ /// .
+ public int[][] DrawOrders {
+ get {
+ return drawOrders;
+ }
}
- /// The number of key frames for this timeline.
- public int FrameCount { get { return frames.Length; } }
-
- /// The time in seconds for each key frame.
- public float[] Frames { get { return frames; } set { frames = value; } } // time, ...
-
- /// The draw order for each key frame.
- /// .
- public int[][] DrawOrders { get { return drawOrders; } set { drawOrders = value; } }
-
- /// Sets the time in seconds and the draw order for the specified key frame.
- /// For each slot in 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.
+ /// Between 0 and frameCount, inclusive.
+ /// The frame time in seconds.
+ /// For each slot in , 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, ExposedList firedEvents, float alpha, MixBlend blend,
+ public override void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend,
MixDirection direction) {
- ExposedList drawOrder = skeleton.drawOrder;
- ExposedList slots = skeleton.slots;
+
+ var drawOrder = skeleton.drawOrder.Items;
if (direction == MixDirection.Out) {
- if (blend == MixBlend.Setup) Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count);
+ if (blend == MixBlend.Setup) Array.Copy(skeleton.slots.Items, 0, drawOrder, 0, skeleton.slots.Count);
return;
}
float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame.
- if (blend == MixBlend.Setup || blend == MixBlend.First) Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count);
+ if (blend == MixBlend.Setup || blend == MixBlend.First) Array.Copy(skeleton.slots.Items, 0, drawOrder, 0, skeleton.slots.Count);
return;
}
- int frame;
- if (time >= frames[frames.Length - 1]) // Time is after last frame.
- frame = frames.Length - 1;
- else
- frame = Animation.BinarySearch(frames, time) - 1;
-
- int[] drawOrderToSetupIndex = drawOrders[frame];
- if (drawOrderToSetupIndex == null) {
- Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count);
- } else {
- var drawOrderItems = drawOrder.Items;
- var slotsItems = slots.Items;
+ int[] drawOrderToSetupIndex = drawOrders[Animation.Search(frames, time)];
+ if (drawOrderToSetupIndex == null)
+ Array.Copy(skeleton.slots.Items, 0, drawOrder, 0, skeleton.slots.Count);
+ else {
+ var slots = skeleton.slots.Items;
for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++)
- drawOrderItems[i] = slotsItems[drawOrderToSetupIndex[i]];
+ drawOrder[i] = slots[drawOrderToSetupIndex[i]];
}
}
}
@@ -1378,52 +1528,49 @@ namespace Spine {
/// , , and .
public class IkConstraintTimeline : CurveTimeline {
public const int ENTRIES = 6;
- private const int PREV_TIME = -6, PREV_MIX = -5, PREV_SOFTNESS = -4, PREV_BEND_DIRECTION = -3, PREV_COMPRESS = -2,
- PREV_STRETCH = -1;
private const int MIX = 1, SOFTNESS = 2, BEND_DIRECTION = 3, COMPRESS = 4, STRETCH = 5;
- internal int ikConstraintIndex;
- internal float[] frames; // time, mix, softness, bendDirection, compress, stretch, ...
+ readonly int ikConstraintIndex;
- public IkConstraintTimeline (int frameCount)
- : base(frameCount) {
- frames = new float[frameCount * ENTRIES];
+ public IkConstraintTimeline (int frameCount, int bezierCount, int ikConstraintIndex)
+ : base(frameCount, bezierCount, (int)Property.IkConstraint + "|" + ikConstraintIndex) {
+ this.ikConstraintIndex = ikConstraintIndex;
}
- override public int PropertyId {
- get { return ((int)TimelineType.IkConstraint << 24) + ikConstraintIndex; }
- }
-
- /// The index of the IK constraint slot in that will be changed.
- public int IkConstraintIndex {
- set {
- if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0.");
- this.ikConstraintIndex = value;
+ public override int FrameEntries {
+ get {
+ return ENTRIES;
}
+ }
+
+ /// The index of the IK constraint slot in that will be changed when this timeline is
+ /// applied.
+ public int IkConstraintIndex {
get {
return ikConstraintIndex;
}
}
- /// The time in seconds, mix, softness, bend direction, compress, and stretch for each key frame.
- public float[] Frames { get { return frames; } set { frames = value; } }
-
- /// 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, bool compress,
+ /// Sets the time, mix, softness, bend direction, compress, and stretch for the specified frame.
+ /// Between 0 and frameCount, inclusive.
+ /// The frame time in seconds.
+ /// 1 or -1.
+ public void SetFrame (int frame, float time, float mix, float softness, int bendDirection, bool compress,
bool 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;
}
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend,
MixDirection direction) {
IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex];
if (!constraint.active) return;
+
float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame.
switch (blend) {
@@ -1445,59 +1592,48 @@ namespace Spine {
return;
}
- if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
- if (blend == MixBlend.Setup) {
- constraint.mix = constraint.data.mix + (frames[frames.Length + PREV_MIX] - constraint.data.mix) * alpha;
- constraint.softness = constraint.data.softness
- + (frames[frames.Length + PREV_SOFTNESS] - constraint.data.softness) * alpha;
- if (direction == MixDirection.Out) {
- constraint.bendDirection = constraint.data.bendDirection;
- constraint.compress = constraint.data.compress;
- constraint.stretch = constraint.data.stretch;
- } else {
- constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION];
- constraint.compress = frames[frames.Length + PREV_COMPRESS] != 0;
- constraint.stretch = frames[frames.Length + PREV_STRETCH] != 0;
- }
- } else {
- constraint.mix += (frames[frames.Length + PREV_MIX] - constraint.mix) * alpha;
- constraint.softness += (frames[frames.Length + PREV_SOFTNESS] - constraint.softness) * alpha;
- if (direction == MixDirection.In) {
- constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION];
- constraint.compress = frames[frames.Length + PREV_COMPRESS] != 0;
- constraint.stretch = frames[frames.Length + PREV_STRETCH] != 0;
- }
- }
- return;
+ float mix, softness;
+ int i = Animation.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);
+ break;
}
- // Interpolate between the previous frame and the current frame.
- int frame = Animation.BinarySearch(frames, time, ENTRIES);
- float mix = frames[frame + PREV_MIX];
- float softness = frames[frame + PREV_SOFTNESS];
- float frameTime = frames[frame];
- float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
-
if (blend == MixBlend.Setup) {
- constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha;
- constraint.softness = constraint.data.softness
- + (softness + (frames[frame + 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 == MixDirection.Out) {
constraint.bendDirection = constraint.data.bendDirection;
constraint.compress = constraint.data.compress;
constraint.stretch = constraint.data.stretch;
- } else {
- constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION];
- constraint.compress = frames[frame + PREV_COMPRESS] != 0;
- constraint.stretch = frames[frame + PREV_STRETCH] != 0;
}
- } else {
- constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha;
- constraint.softness += (softness + (frames[frame + SOFTNESS] - softness) * percent - constraint.softness) * alpha;
+ else {
+ constraint.bendDirection = (int)frames[i + BEND_DIRECTION];
+ constraint.compress = frames[i + COMPRESS] != 0;
+ constraint.stretch = frames[i + STRETCH] != 0;
+ }
+ }
+ else {
+ constraint.mix += (mix - constraint.mix) * alpha;
+ constraint.softness += (softness - constraint.softness) * alpha;
if (direction == MixDirection.In) {
- constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION];
- constraint.compress = frames[frame + PREV_COMPRESS] != 0;
- constraint.stretch = frames[frame + PREV_STRETCH] != 0;
+ constraint.bendDirection = (int)frames[i + BEND_DIRECTION];
+ constraint.compress = frames[i + COMPRESS] != 0;
+ constraint.stretch = frames[i + STRETCH] != 0;
}
}
}
@@ -1506,49 +1642,46 @@ namespace Spine {
/// Changes a transform constraint's mixes.
public class TransformConstraintTimeline : CurveTimeline {
public const int ENTRIES = 5;
- private const int PREV_TIME = -5, PREV_ROTATE = -4, PREV_TRANSLATE = -3, PREV_SCALE = -2, PREV_SHEAR = -1;
private const int ROTATE = 1, TRANSLATE = 2, SCALE = 3, SHEAR = 4;
- internal int transformConstraintIndex;
- internal float[] frames; // time, rotate mix, translate mix, scale mix, shear mix, ...
+ readonly int transformConstraintIndex;
- public TransformConstraintTimeline (int frameCount)
- : base(frameCount) {
- frames = new float[frameCount * ENTRIES];
+ public TransformConstraintTimeline (int frameCount, int bezierCount, int transformConstraintIndex)
+ : base(frameCount, bezierCount, (int)Property.TransformConstraint + "|" + transformConstraintIndex) {
+ this.transformConstraintIndex = transformConstraintIndex;
}
- override public int PropertyId {
- get { return ((int)TimelineType.TransformConstraint << 24) + transformConstraintIndex; }
- }
-
- /// The index of the transform constraint slot in that will be changed.
- public int TransformConstraintIndex {
- set {
- if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0.");
- this.transformConstraintIndex = value;
+ public override int FrameEntries {
+ get {
+ return ENTRIES;
}
+ }
+
+ /// The index of the transform constraint slot in that will be changed when this
+ /// timeline is applied.
+ public int TransformConstraintIndex {
get {
return transformConstraintIndex;
}
}
- /// The time in seconds, rotate mix, translate mix, scale mix, and shear mix for each key frame.
- public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, scale mix, shear mix, ...
-
- /// 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.
+ /// Between 0 and frameCount, inclusive.
+ /// 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;
}
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend,
MixDirection direction) {
TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex];
if (!constraint.active) return;
+
float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame.
TransformConstraintData data = constraint.data;
@@ -1570,28 +1703,34 @@ namespace Spine {
}
float rotate, translate, scale, shear;
- if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
- int i = frames.Length;
- rotate = frames[i + PREV_ROTATE];
- translate = frames[i + PREV_TRANSLATE];
- scale = frames[i + PREV_SCALE];
- shear = frames[i + PREV_SHEAR];
- } else {
- // Interpolate between the previous frame and the current frame.
- int frame = Animation.BinarySearch(frames, time, ENTRIES);
- rotate = frames[frame + PREV_ROTATE];
- translate = frames[frame + PREV_TRANSLATE];
- scale = frames[frame + PREV_SCALE];
- shear = frames[frame + PREV_SHEAR];
- float frameTime = frames[frame];
- float percent = GetCurvePercent(frame / ENTRIES - 1,
- 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
-
- rotate += (frames[frame + ROTATE] - rotate) * percent;
- translate += (frames[frame + TRANSLATE] - translate) * percent;
- scale += (frames[frame + SCALE] - scale) * percent;
- shear += (frames[frame + SHEAR] - shear) * percent;
+ int i = Animation.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];
+ 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);
+ break;
}
+
if (blend == MixBlend.Setup) {
TransformConstraintData data = constraint.data;
constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha;
@@ -1608,49 +1747,27 @@ namespace Spine {
}
/// Changes a path constraint's .
- public class PathConstraintPositionTimeline : CurveTimeline {
- public const int ENTRIES = 2;
- protected const int PREV_TIME = -2, PREV_VALUE = -1;
- protected const int VALUE = 1;
+ public class PathConstraintPositionTimeline : CurveTimeline1 {
+ readonly int pathConstraintIndex;
- internal int pathConstraintIndex;
- internal float[] frames; // time, position, ...
-
- public PathConstraintPositionTimeline (int frameCount)
- : base(frameCount) {
- frames = new float[frameCount * ENTRIES];
+ public PathConstraintPositionTimeline (int frameCount, int bezierCount, int pathConstraintIndex)
+ :base(frameCount, bezierCount, (int)Property.PathConstraintPosition + "|" + pathConstraintIndex) {
+ this.pathConstraintIndex = pathConstraintIndex;
}
- override public int PropertyId {
- get { return ((int)TimelineType.PathConstraintPosition << 24) + pathConstraintIndex; }
- }
-
- /// The index of the path constraint slot in that will be changed.
+ /// The index of the path constraint slot in that will be changed when this timeline
+ /// is applied.
public int PathConstraintIndex {
- set {
- if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0.");
- this.pathConstraintIndex = value;
- }
get {
return pathConstraintIndex;
}
}
- /// The time in seconds and path constraint position for each key frame.
- public float[] Frames { get { return frames; } set { frames = value; } } // time, position, ...
-
- /// Sets the time in seconds and path constraint position for the specified key frame.
- public void SetFrame (int frameIndex, float time, float position) {
- frameIndex *= ENTRIES;
- frames[frameIndex] = time;
- frames[frameIndex + VALUE] = position;
- }
-
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend,
MixDirection direction) {
PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex];
if (!constraint.active) return;
- float[] frames = this.frames;
+
if (time < frames[0]) { // Time is before first frame.
switch (blend) {
case MixBlend.Setup:
@@ -1663,19 +1780,7 @@ namespace Spine {
return;
}
- float position;
- if (time >= frames[frames.Length - ENTRIES]) // Time is after last frame.
- position = frames[frames.Length + PREV_VALUE];
- else {
- // Interpolate between the previous frame and the current frame.
- int frame = Animation.BinarySearch(frames, time, ENTRIES);
- position = frames[frame + PREV_VALUE];
- float frameTime = frames[frame];
- float percent = GetCurvePercent(frame / ENTRIES - 1,
- 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
-
- position += (frames[frame + VALUE] - position) * percent;
- }
+ float position = GetCurveValue(time);
if (blend == MixBlend.Setup)
constraint.position = constraint.data.position + (position - constraint.data.position) * alpha;
else
@@ -1684,19 +1789,28 @@ namespace Spine {
}
/// Changes a path constraint's .
- public class PathConstraintSpacingTimeline : PathConstraintPositionTimeline {
- public PathConstraintSpacingTimeline (int frameCount)
- : base(frameCount) {
+ public class PathConstraintSpacingTimeline : CurveTimeline1 {
+ readonly int pathConstraintIndex;
+
+ public PathConstraintSpacingTimeline (int frameCount, int bezierCount, int pathConstraintIndex)
+ : base(frameCount, bezierCount, (int)Property.PathConstraintSpacing + "|" + pathConstraintIndex) {
+ this.pathConstraintIndex = pathConstraintIndex;
}
- override public int PropertyId {
- get { return ((int)TimelineType.PathConstraintSpacing << 24) + pathConstraintIndex; }
+ /// The index of the path constraint slot in that will be changed when this timeline
+ /// is applied.
+ public int PathConstraintIndex {
+ get {
+ return pathConstraintIndex;
+ }
}
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend,
MixDirection direction) {
+
PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex];
if (!constraint.active) return;
+
float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame.
switch (blend) {
@@ -1710,20 +1824,7 @@ namespace Spine {
return;
}
- float spacing;
- if (time >= frames[frames.Length - ENTRIES]) // Time is after last frame.
- spacing = frames[frames.Length + PREV_VALUE];
- else {
- // Interpolate between the previous frame and the current frame.
- int frame = Animation.BinarySearch(frames, time, ENTRIES);
- spacing = frames[frame + PREV_VALUE];
- float frameTime = frames[frame];
- float percent = GetCurvePercent(frame / ENTRIES - 1,
- 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
-
- spacing += (frames[frame + VALUE] - spacing) * percent;
- }
-
+ float spacing = GetCurveValue(time);
if (blend == MixBlend.Setup)
constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha;
else
@@ -1732,49 +1833,27 @@ namespace Spine {
}
/// Changes a path constraint's mixes.
- public class PathConstraintMixTimeline : CurveTimeline {
- public const int ENTRIES = 3;
- private const int PREV_TIME = -3, PREV_ROTATE = -2, PREV_TRANSLATE = -1;
- private const int ROTATE = 1, TRANSLATE = 2;
+ public class PathConstraintMixTimeline : CurveTimeline2 {
+ readonly int pathConstraintIndex;
- internal int pathConstraintIndex;
- internal float[] frames; // time, rotate mix, translate mix, ...
-
- public PathConstraintMixTimeline (int frameCount)
- : base(frameCount) {
- frames = new float[frameCount * ENTRIES];
+ public PathConstraintMixTimeline (int frameCount, int bezierCount, int pathConstraintIndex)
+ : base(frameCount, bezierCount, (int)Property.PathConstraintMix + "|" + pathConstraintIndex) {
+ this.pathConstraintIndex = pathConstraintIndex;
}
- override public int PropertyId {
- get { return ((int)TimelineType.PathConstraintMix << 24) + pathConstraintIndex; }
- }
-
- /// The index of the path constraint slot in that will be changed.
+ /// The index of the path constraint slot in that will be changed when this timeline
+ /// is applied.
public int PathConstraintIndex {
- set {
- if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0.");
- this.pathConstraintIndex = value;
- }
get {
return pathConstraintIndex;
}
}
- /// The time in seconds, rotate mix, and translate mix for each key frame.
- public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, ...
-
- /// The time in seconds, rotate mix, and translate mix for the specified key frame.
- public void SetFrame (int frameIndex, float time, float rotateMix, float translateMix) {
- frameIndex *= ENTRIES;
- frames[frameIndex] = time;
- frames[frameIndex + ROTATE] = rotateMix;
- frames[frameIndex + TRANSLATE] = translateMix;
- }
-
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend,
MixDirection direction) {
PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex];
if (!constraint.active) return;
+
float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame.
switch (blend) {
@@ -1791,20 +1870,24 @@ namespace Spine {
}
float rotate, translate;
- if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
- rotate = frames[frames.Length + PREV_ROTATE];
- translate = frames[frames.Length + PREV_TRANSLATE];
- } else {
- // Interpolate between the previous frame and the current frame.
- int frame = Animation.BinarySearch(frames, time, ENTRIES);
- rotate = frames[frame + PREV_ROTATE];
- translate = frames[frame + PREV_TRANSLATE];
- float frameTime = frames[frame];
- float percent = GetCurvePercent(frame / ENTRIES - 1,
- 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
-
- rotate += (frames[frame + ROTATE] - rotate) * percent;
- translate += (frames[frame + TRANSLATE] - translate) * percent;
+ int i = Animation.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);
+ break;
}
if (blend == MixBlend.Setup) {
diff --git a/spine-csharp/src/AnimationState.cs b/spine-csharp/src/AnimationState.cs
index e1c132a11..b093169f3 100644
--- a/spine-csharp/src/AnimationState.cs
+++ b/spine-csharp/src/AnimationState.cs
@@ -112,7 +112,7 @@ namespace Spine {
// end of difference
private readonly EventQueue queue; // Initialized by constructor.
- private readonly HashSet propertyIDs = new HashSet();
+ private readonly HashSet propertyIds = new HashSet();
private bool animationsChanged;
private float timeScale = 1;
private int unkeyedState;
@@ -244,7 +244,13 @@ namespace Spine {
mix = 0; // Set to setup pose the last time the entry will be applied.
// Apply current entry.
- float animationLast = current.animationLast, animationTime = current.AnimationTime;
+ float animationLast = current.animationLast, animationTime = current.AnimationTime, applyTime = animationTime;
+ ExposedList applyEvents = events;
+ if (current.reverse) {
+ applyTime = current.animation.duration - applyTime;
+ applyEvents = null;
+ }
+
int timelineCount = current.animation.timelines.Count;
var timelines = current.animation.timelines;
var timelinesItems = timelines.Items;
@@ -252,9 +258,9 @@ namespace Spine {
for (int ii = 0; ii < timelineCount; ii++) {
var timeline = timelinesItems[ii];
if (timeline is AttachmentTimeline)
- ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, animationTime, blend, true);
+ ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, blend, true);
else
- timeline.Apply(skeleton, animationLast, animationTime, events, mix, blend, MixDirection.In);
+ timeline.Apply(skeleton, animationLast, applyTime, applyEvents, mix, blend, MixDirection.In);
}
} else {
var timelineMode = current.timelineMode.Items;
@@ -268,12 +274,12 @@ namespace Spine {
MixBlend timelineBlend = timelineMode[ii] == AnimationState.Subsequent ? blend : MixBlend.Setup;
var rotateTimeline = timeline as RotateTimeline;
if (rotateTimeline != null)
- ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation,
+ ApplyRotateTimeline(rotateTimeline, skeleton, applyTime, mix, timelineBlend, timelinesRotation,
ii << 1, firstFrame);
else if (timeline is AttachmentTimeline)
- ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, animationTime, blend, true);
+ ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, blend, true);
else
- timeline.Apply(skeleton, animationLast, animationTime, events, mix, timelineBlend, MixDirection.In);
+ timeline.Apply(skeleton, animationLast, applyTime, applyEvents, mix, timelineBlend, MixDirection.In);
}
}
QueueEvents(current, animationTime);
@@ -314,17 +320,23 @@ namespace Spine {
if (blend != MixBlend.First) blend = from.mixBlend; // Track 0 ignores track mix blend.
}
- var eventBuffer = mix < from.eventThreshold ? this.events : null;
bool attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold;
- float animationLast = from.animationLast, animationTime = from.AnimationTime;
var timelines = from.animation.timelines;
int timelineCount = timelines.Count;
var timelinesItems = timelines.Items;
float alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix);
+ float animationLast = from.animationLast, animationTime = from.AnimationTime, applyTime = animationTime;
+ ExposedList events = null;
+ if (from.reverse)
+ applyTime = from.animation.duration - applyTime;
+ else {
+ if (mix < from.eventThreshold) events = this.events;
+ }
+
if (blend == MixBlend.Add) {
for (int i = 0; i < timelineCount; i++)
- timelinesItems[i].Apply(skeleton, animationLast, animationTime, eventBuffer, alphaMix, blend, MixDirection.Out);
+ timelinesItems[i].Apply(skeleton, animationLast, applyTime, events, alphaMix, blend, MixDirection.Out);
} else {
var timelineMode = from.timelineMode.Items;
var timelineHoldMix = from.timelineHoldMix.Items;
@@ -366,14 +378,14 @@ namespace Spine {
from.totalAlpha += alpha;
var rotateTimeline = timeline as RotateTimeline;
if (rotateTimeline != null) {
- ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, alpha, timelineBlend, timelinesRotation,
- i << 1, firstFrame);
+ ApplyRotateTimeline(rotateTimeline, skeleton, applyTime, alpha, timelineBlend, timelinesRotation, i << 1,
+ firstFrame);
} else if (timeline is AttachmentTimeline) {
- ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, animationTime, timelineBlend, attachments);
+ ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, timelineBlend, attachments);
} else {
if (drawOrder && timeline is DrawOrderTimeline && timelineBlend == MixBlend.Setup)
direction = MixDirection.In;
- timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, alpha, timelineBlend, direction);
+ timeline.Apply(skeleton, animationLast, applyTime, events, alpha, timelineBlend, direction);
}
}
}
@@ -393,7 +405,7 @@ namespace Spine {
private void ApplyAttachmentTimeline (AttachmentTimeline timeline, Skeleton skeleton, float time, MixBlend blend,
bool attachments) {
- Slot slot = skeleton.slots.Items[timeline.slotIndex];
+ Slot slot = skeleton.slots.Items[timeline.SlotIndex];
if (!slot.bone.active) return;
float[] frames = timeline.frames;
@@ -402,12 +414,7 @@ namespace Spine {
SetAttachment(skeleton, slot, slot.data.attachmentName, attachments);
}
else {
- int frameIndex;
- if (time >= frames[frames.Length - 1]) // Time is after last frame.
- frameIndex = frames.Length - 1;
- else
- frameIndex = Animation.BinarySearch(frames, time) - 1;
- SetAttachment(skeleton, slot, timeline.attachmentNames[frameIndex], attachments);
+ SetAttachment(skeleton, slot, timeline.AttachmentNames[Animation.Search(frames, time)], attachments);
}
// If an attachment wasn't set (ie before the first frame or attachments is false), set the setup attachment later.
@@ -432,7 +439,7 @@ namespace Spine {
return;
}
- Bone bone = skeleton.bones.Items[timeline.boneIndex];
+ Bone bone = skeleton.bones.Items[timeline.BoneIndex];
if (!bone.active) return;
float[] frames = timeline.frames;
@@ -441,7 +448,7 @@ namespace Spine {
switch (blend) {
case MixBlend.Setup:
bone.rotation = bone.data.rotation;
- return;
+ goto default; // Fall through.
default:
return;
case MixBlend.First:
@@ -451,21 +458,7 @@ namespace Spine {
}
} else {
r1 = blend == MixBlend.Setup ? bone.data.rotation : bone.rotation;
- if (time >= frames[frames.Length - RotateTimeline.ENTRIES]) // Time is after last frame.
- r2 = bone.data.rotation + frames[frames.Length + RotateTimeline.PREV_ROTATION];
- else {
- // Interpolate between the previous frame and the current frame.
- int frame = Animation.BinarySearch(frames, time, RotateTimeline.ENTRIES);
- float prevRotation = frames[frame + RotateTimeline.PREV_ROTATION];
- float frameTime = frames[frame];
- float percent = timeline.GetCurvePercent((frame >> 1) - 1,
- 1 - (time - frameTime) / (frames[frame + RotateTimeline.PREV_TIME] - frameTime));
-
- r2 = frames[frame + RotateTimeline.ROTATION] - prevRotation;
- r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360;
- r2 = prevRotation + r2 * percent + bone.data.rotation;
- r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360;
- }
+ r2 = bone.data.rotation + timeline.GetCurveValue(time);
}
// Mix between rotations using the direction of the shortest route on the first frame.
@@ -494,8 +487,7 @@ namespace Spine {
timelinesRotation[i] = total;
}
timelinesRotation[i + 1] = diff;
- r1 += total * alpha;
- bone.rotation = r1 - (16384 - (int)(16384.499999999996 - r1 / 360)) * 360;
+ bone.rotation = r1 + total * alpha;
}
private void QueueEvents (TrackEntry entry, float animationTime) {
@@ -577,10 +569,17 @@ namespace Spine {
queue.Drain();
}
+ ///
+ /// Removes the next entry and all entries after it for the specified entry.
+ public void ClearNext (TrackEntry entry) {
+ DisposeNext(entry.next);
+ }
+
/// Sets the active TrackEntry for a given track number.
private void SetCurrent (int index, TrackEntry current, bool interrupt) {
TrackEntry from = ExpandToIndex(index);
tracks.Items[index] = current;
+ current.previous = null;
if (from != null) {
if (interrupt) queue.Interrupt(from);
@@ -647,7 +646,7 @@ namespace Spine {
/// equivalent to calling .
///
/// If > 0, sets . If <= 0, the delay set is the duration of the previous track entry
- /// minus any mix duration (from the {@link AnimationStateData}) plus the specified Delay (ie the mix
+ /// minus any mix duration (from the plus the specified Delay (ie the mix
/// ends at (Delay = 0) or before (Delay < 0) the previous track entry duration). If the
/// previous entry is looping, its next loop completion is used instead of its duration.
///
@@ -669,18 +668,8 @@ namespace Spine {
queue.Drain();
} else {
last.next = entry;
- if (delay <= 0) {
- float duration = last.animationEnd - last.animationStart;
- if (duration != 0) {
- if (last.loop) {
- delay += duration * (1 + (int)(last.trackTime / duration)); // Completion of next loop.
- } else {
- delay += Math.Max(duration, last.trackTime); // After duration, else next update.
- }
- delay -= data.GetMix(last.animation, animation);
- } else
- delay = last.trackTime; // Next update.
- }
+ entry.previous = last;
+ if (delay <= 0) delay += last.TrackComplete - entry.mixDuration;
}
entry.delay = delay;
@@ -698,11 +687,11 @@ namespace Spine {
/// 0 still mixes out over one frame.
///
/// Mixing in is done by first setting an empty animation, then adding an animation using
- /// and on the returned track entry, set the
- /// . Mixing from an empty animation causes the new animation to be applied more and
- /// more over the mix duration. Properties keyed in the new animation transition from the value from lower tracks or from the
- /// setup pose value if no lower tracks key the property to the value keyed in the new animation.
- ///
+ /// with the desired delay (an empty animation has a duration of 0) and on
+ /// the returned track entry, set the . Mixing from an empty animation causes the new
+ /// animation to be applied more and more over the mix duration. Properties keyed in the new animation transition from the value
+ /// from lower tracks or from the setup pose value if no lower tracks key the property to the value keyed in the new
+ /// animation.
public TrackEntry SetEmptyAnimation (int trackIndex, float mixDuration) {
TrackEntry entry = SetAnimation(trackIndex, AnimationState.EmptyAnimation, false);
entry.mixDuration = mixDuration;
@@ -725,10 +714,10 @@ namespace Spine {
/// after the event occurs.
///
public TrackEntry AddEmptyAnimation (int trackIndex, float mixDuration, float delay) {
- if (delay <= 0) delay -= mixDuration;
- TrackEntry entry = AddAnimation(trackIndex, AnimationState.EmptyAnimation, false, delay);
+ TrackEntry entry = AddAnimation(trackIndex, AnimationState.EmptyAnimation, false, delay <= 0 ? 1 : delay);
entry.mixDuration = mixDuration;
entry.trackEnd = mixDuration;
+ if (delay <= 0 && entry.previous != null) entry.delay = entry.previous.TrackComplete - entry.mixDuration;
return entry;
}
@@ -738,8 +727,9 @@ namespace Spine {
public void SetEmptyAnimations (float mixDuration) {
bool oldDrainDisabled = queue.drainDisabled;
queue.drainDisabled = true;
+ var tracksItems = tracks.Items;
for (int i = 0, n = tracks.Count; i < n; i++) {
- TrackEntry current = tracks.Items[i];
+ TrackEntry current = tracksItems[i];
if (current != null) SetEmptyAnimation(current.trackIndex, mixDuration);
}
queue.drainDisabled = oldDrainDisabled;
@@ -798,10 +788,10 @@ namespace Spine {
animationsChanged = false;
// Process in the order that animations are applied.
- propertyIDs.Clear();
-
+ propertyIds.Clear();
+ int n = tracks.Count;
var tracksItems = tracks.Items;
- for (int i = 0, n = tracks.Count; i < n; i++) {
+ for (int i = 0; i < n; i++) {
TrackEntry entry = tracksItems[i];
if (entry == null) continue;
while (entry.mixingFrom != null) // Move to last entry, then iterate in reverse.
@@ -814,6 +804,8 @@ namespace Spine {
}
}
+
+
private void ComputeHold (TrackEntry entry) {
TrackEntry to = entry.mixingTo;
var timelines = entry.animation.timelines.Items;
@@ -821,11 +813,11 @@ namespace Spine {
var timelineMode = entry.timelineMode.Resize(timelinesCount).Items; //timelineMode.setSize(timelinesCount);
entry.timelineHoldMix.Clear();
var timelineHoldMix = entry.timelineHoldMix.Resize(timelinesCount).Items; //timelineHoldMix.setSize(timelinesCount);
- var propertyIDs = this.propertyIDs;
+ var propertyIds = this.propertyIds;
if (to != null && to.holdPrevious) {
for (int i = 0; i < timelinesCount; i++)
- timelineMode[i] = propertyIDs.Add(timelines[i].PropertyId) ? AnimationState.HoldFirst : AnimationState.HoldSubsequent;
+ timelineMode[i] = propertyIds.AddAll(timelines[i].PropertyIds) ? AnimationState.HoldFirst : AnimationState.HoldSubsequent;
return;
}
@@ -833,15 +825,15 @@ namespace Spine {
// outer:
for (int i = 0; i < timelinesCount; i++) {
Timeline timeline = timelines[i];
- int id = timeline.PropertyId;
- if (!propertyIDs.Add(id))
+ String[] ids = timeline.PropertyIds;
+ if (!propertyIds.AddAll(ids))
timelineMode[i] = AnimationState.Subsequent;
else if (to == null || timeline is AttachmentTimeline || timeline is DrawOrderTimeline
- || timeline is EventTimeline || !to.animation.HasTimeline(id)) {
+ || timeline is EventTimeline || !to.animation.HasTimeline(ids)) {
timelineMode[i] = AnimationState.First;
} else {
for (TrackEntry next = to.mixingTo; next != null; next = next.mixingTo) {
- if (next.animation.HasTimeline(id)) continue;
+ if (next.animation.HasTimeline(ids)) continue;
if (next.mixDuration > 0) {
timelineMode[i] = AnimationState.HoldMix;
timelineHoldMix[i] = next;
@@ -892,8 +884,9 @@ namespace Spine {
override public string ToString () {
var buffer = new System.Text.StringBuilder();
+ var tracksItems = tracks.Items;
for (int i = 0, n = tracks.Count; i < n; i++) {
- TrackEntry entry = tracks.Items[i];
+ TrackEntry entry = tracksItems[i];
if (entry == null) continue;
if (buffer.Length > 0) buffer.Append(", ");
buffer.Append(entry.ToString());
@@ -912,7 +905,7 @@ namespace Spine {
public class TrackEntry : Pool.IPoolable {
internal Animation animation;
- internal TrackEntry next, mixingFrom, mixingTo;
+ internal TrackEntry previous, next, mixingFrom, mixingTo;
// difference to libgdx reference: delegates are used for event callbacks instead of 'AnimationStateListener listener'.
public event AnimationState.TrackEntryDelegate Start, Interrupt, End, Dispose, Complete;
public event AnimationState.TrackEntryEventDelegate Event;
@@ -925,7 +918,7 @@ namespace Spine {
internal int trackIndex;
- internal bool loop, holdPrevious;
+ internal bool loop, holdPrevious, reverse;
internal float eventThreshold, attachmentThreshold, drawOrderThreshold;
internal float animationStart, animationEnd, animationLast, nextAnimationLast;
internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f;
@@ -937,6 +930,7 @@ namespace Spine {
// IPoolable.Reset()
public void Reset () {
+ previous = null;
next = null;
mixingFrom = null;
mixingTo = null;
@@ -973,7 +967,10 @@ namespace Spine {
/// track entry >= this track entry's Delay).
///
/// affects the delay.
- ///
+ ///
+ /// When using with a delay <= 0, the delay
+ /// is set using the mix duration from the . If is set afterward, the delay
+ /// may need to be adjusted.
public float Delay { get { return delay; } set { delay = value; } }
///
@@ -994,6 +991,21 @@ namespace Spine {
///
public float TrackEnd { get { return trackEnd; } set { trackEnd = value; } }
+ ///
+ /// If this track entry is non-looping, the track time in seconds when is reached, or the current
+ /// if it has already been reached. If this track entry is looping, the track time when this
+ /// animation will reach its next (the next loop completion).
+ public float TrackComplete {
+ get {
+ float duration = animationEnd - animationStart;
+ if (duration != 0) {
+ if (loop) return duration * (1 + (int)(trackTime / duration)); // Completion of next loop.
+ if (trackTime < duration) return duration; // Before duration.
+ }
+ return trackTime; // Next update.
+ }
+ }
+
///
///
/// Seconds when this animation starts, both initially and after looping. Defaults to 0.
@@ -1043,11 +1055,13 @@ namespace Spine {
/// Multiplier for the delta time when this track entry is updated, causing time for this animation to pass slower or
/// faster. Defaults to 1.
///
+ /// Values < 0 are not supported. To play an animation in reverse, use .
+ ///
/// is not affected by track entry time scale, so may need to be adjusted to
/// match the animation speed.
///
- /// When using with a Delay <= 0, note the
- /// { is set using the mix duration from the , assuming time scale to be 1. If
+ /// When using with a Delay <= 0, the
+ /// is set using the mix duration from the , assuming time scale to be 1. If
/// the time scale is not 1, the delay may need to be adjusted.
///
/// See AnimationState for affecting all animations.
@@ -1086,9 +1100,16 @@ namespace Spine {
public float DrawOrderThreshold { get { return drawOrderThreshold; } set { drawOrderThreshold = value; } }
///
- /// The animation queued to start after this animation, or null. Next makes up a linked list.
+ /// The animation queued to start after this animation, or null if there is none. next makes up a doubly linked
+ /// list.
+ ///
+ /// See to truncate the list.
public TrackEntry Next { get { return next; } }
+ ///
+ /// The animation queued to play before this animation, or null. previous makes up a doubly linked list.
+ public TrackEntry Previous { get { return previous; } }
+
///
/// Returns true if at least one loop has been completed.
///
@@ -1108,20 +1129,21 @@ namespace Spine {
///
/// The MixDuration can be set manually rather than use the value from
/// . In that case, the MixDuration can be set for a new
- /// track entry only before is first called.
- ///
- /// When using with a Delay <= 0, note the
- /// is set using the mix duration from the , not a mix duration set
- /// afterward.
- ///
+ /// track entry only before is first called.
+ ///
+ /// When using with a Delay <= 0, the
+ /// is set using the mix duration from the . If mixDuration is set
+ /// afterward, the delay may need to be adjusted. For example:
+ /// entry.Delay = entry.previous.TrackComplete - entry.MixDuration;
+ ///
public float MixDuration { get { return mixDuration; } set { mixDuration = value; } }
///
///
- /// Controls how properties keyed in the animation are mixed with lower tracks. Defaults to , which
- /// replaces the values from the lower tracks with the animation values. adds the animation values to
- /// the values from the lower tracks.
- ///
+ /// Controls how properties keyed in the animation are mixed with lower tracks. Defaults to .
+ ///
+ /// Track entries on track 0 ignore this setting and always use .
+ ///
/// The MixBlend can be set for a new track entry only before is first
/// called.
///
@@ -1153,6 +1175,10 @@ namespace Spine {
///
public bool HoldPrevious { get { return holdPrevious; } set { holdPrevious = value; } }
+ ///
+ /// If true, the animation will be applied in reverse. Events are not fired when an animation is applied in reverse.
+ public bool Reverse { get { return reverse; } set { reverse = value; } }
+
///
///
/// Resets the rotation directions for mixing this entry's rotate timelines. This can be useful to avoid bones rotating the
@@ -1331,4 +1357,14 @@ namespace Spine {
}
}
+ public static class HashSetExtensions {
+ public static bool AddAll (this HashSet set, T[] addSet) {
+ bool anyItemAdded = false;
+ for (int i = 0, n = addSet.Length; i < n; ++i) {
+ var item = addSet[i];
+ anyItemAdded |= set.Add(item);
+ }
+ return anyItemAdded;
+ }
+ }
}
diff --git a/spine-csharp/src/Attachments/AtlasAttachmentLoader.cs b/spine-csharp/src/Attachments/AtlasAttachmentLoader.cs
index 52b405988..3adebb9fd 100644
--- a/spine-csharp/src/Attachments/AtlasAttachmentLoader.cs
+++ b/spine-csharp/src/Attachments/AtlasAttachmentLoader.cs
@@ -39,7 +39,7 @@ namespace Spine {
private Atlas[] atlasArray;
public AtlasAttachmentLoader (params Atlas[] atlasArray) {
- if (atlasArray == null) throw new ArgumentNullException("atlas array cannot be null.");
+ if (atlasArray == null) throw new ArgumentNullException("atlas", "atlas array cannot be null.");
this.atlasArray = atlasArray;
}
diff --git a/spine-csharp/src/Attachments/VertexAttachment.cs b/spine-csharp/src/Attachments/VertexAttachment.cs
index 2c534def1..5466c7514 100644
--- a/spine-csharp/src/Attachments/VertexAttachment.cs
+++ b/spine-csharp/src/Attachments/VertexAttachment.cs
@@ -56,7 +56,7 @@ namespace Spine {
deformAttachment = this;
lock (VertexAttachment.nextIdLock) {
- id = (VertexAttachment.nextID++ & 65535) << 11;
+ id = VertexAttachment.nextID++;
}
}
diff --git a/spine-csharp/src/Bone.cs b/spine-csharp/src/Bone.cs
index 5031e24c3..b0065d0a8 100644
--- a/spine-csharp/src/Bone.cs
+++ b/spine-csharp/src/Bone.cs
@@ -116,6 +116,7 @@ namespace Spine {
/// Returns the magnitide (always positive) of the world scale Y.
public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d); } }
+ /// Copy constructor. Does not copy the bones.
/// May be null.
public Bone (BoneData data, Skeleton skeleton, Bone parent) {
if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
@@ -305,10 +306,10 @@ namespace Spine {
public void WorldToLocal (float worldX, float worldY, out float localX, out float localY) {
float a = this.a, b = this.b, c = this.c, d = this.d;
- float invDet = 1 / (a * d - b * c);
+ float det = a * d - b * c;
float x = worldX - this.worldX, y = worldY - this.worldY;
- localX = (x * d * invDet - y * b * invDet);
- localY = (y * a * invDet - x * c * invDet);
+ localX = (x * d - y * b) / det;
+ localY = (y * a - x * c) / det;
}
public void LocalToWorld (float localX, float localY, out float worldX, out float worldY) {
diff --git a/spine-csharp/src/IkConstraint.cs b/spine-csharp/src/IkConstraint.cs
index 1d88c4a63..a8e91d901 100644
--- a/spine-csharp/src/IkConstraint.cs
+++ b/spine-csharp/src/IkConstraint.cs
@@ -79,20 +79,16 @@ namespace Spine {
stretch = constraint.stretch;
}
- /// Applies the constraint to the constrained bones.
- public void Apply () {
- Update();
- }
-
public void Update () {
+ if (mix == 0) return;
Bone target = this.target;
- ExposedList bones = this.bones;
- switch (bones.Count) {
+ var bones = this.bones.Items;
+ switch (this.bones.Count) {
case 1:
- Apply(bones.Items[0], target.worldX, target.worldY, compress, stretch, data.uniform, mix);
+ Apply(bones[0], target.worldX, target.worldY, compress, stretch, data.uniform, mix);
break;
case 2:
- Apply(bones.Items[0], bones.Items[1], target.worldX, target.worldY, bendDirection, stretch, softness, mix);
+ Apply(bones[0], bones[1], target.worldX, target.worldY, bendDirection, stretch, softness, mix);
break;
}
}
@@ -157,6 +153,7 @@ namespace Spine {
/// Applies 1 bone IK. The target is specified in the world coordinate system.
static public void Apply (Bone bone, float targetX, float targetY, bool compress, bool stretch, bool uniform,
float alpha) {
+ if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null.");
if (!bone.appliedValid) bone.UpdateAppliedTransform();
Bone p = bone.parent;
@@ -175,16 +172,16 @@ namespace Spine {
float sc = pc / bone.skeleton.ScaleY;
pb = -sc * s * bone.skeleton.ScaleX;
pd = sa * s * bone.skeleton.ScaleY;
- rotationIK += (float)Math.Atan2(pc, pa) * MathUtils.RadDeg;
+ rotationIK += (float)Math.Atan2(sc, sa) * MathUtils.RadDeg;
goto default; // Fall through.
- }
- default: {
+ }
+ default: {
float x = targetX - p.worldX, y = targetY - p.worldY;
- float d = pa * pd - pb * pc;
- tx = (x * pd - y * pb) / d - bone.ax;
- ty = (y * pa - x * pc) / d - bone.ay;
- break;
- }
+ float d = pa * pd - pb * pc;
+ tx = (x * pd - y * pb) / d - bone.ax;
+ ty = (y * pa - x * pc) / d - bone.ay;
+ break;
+ }
}
rotationIK += (float)Math.Atan2(ty, tx) * MathUtils.RadDeg;
@@ -198,13 +195,10 @@ namespace Spine {
if (compress || stretch) {
switch (bone.data.transformMode) {
case TransformMode.NoScale:
- tx = targetX - bone.worldX;
- ty = targetY - bone.worldY;
- break;
- case TransformMode.NoScaleOrReflection:
+ case TransformMode.NoScaleOrReflection:
tx = targetX - bone.worldX;
ty = targetY - bone.worldY;
- break;
+ break;
}
float b = bone.data.length * sx, dd = (float)Math.Sqrt(tx * tx + ty * ty);
if ((compress && dd < b) || (stretch && dd > b) && b > 0.0001f) {
@@ -220,10 +214,8 @@ namespace Spine {
/// A direct descendant of the parent bone.
static public void Apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, bool stretch, float softness,
float alpha) {
- if (alpha == 0) {
- child.UpdateWorldTransform();
- return;
- }
+ if (parent == null) throw new ArgumentNullException("parent", "parent cannot be null.");
+ if (child == null) throw new ArgumentNullException("child", "child cannot be null.");
if (!parent.appliedValid) parent.UpdateAppliedTransform();
if (!child.appliedValid) child.UpdateAppliedTransform();
float px = parent.ax, py = parent.ay, psx = parent.ascaleX, sx = psx, psy = parent.ascaleY, csx = child.ascaleX;
diff --git a/spine-csharp/src/PathConstraint.cs b/spine-csharp/src/PathConstraint.cs
index c8fef419e..a11ce2ddd 100644
--- a/spine-csharp/src/PathConstraint.cs
+++ b/spine-csharp/src/PathConstraint.cs
@@ -34,7 +34,7 @@ namespace Spine {
///
///
/// Stores the current pose for a path constraint. A path constraint adjusts the rotation, translation, and scale of the
- /// constrained bones so they follow a {@link PathAttachment}.
+ /// constrained bones so they follow a .
///
/// See Path constraints in the Spine User Guide.
///
@@ -82,11 +82,6 @@ namespace Spine {
translateMix = constraint.translateMix;
}
- /// Applies the constraint to the constrained bones.
- public void Apply () {
- Update();
- }
-
public void Update () {
PathAttachment attachment = target.Attachment as PathAttachment;
if (attachment == null) return;
diff --git a/spine-csharp/src/Skeleton.cs b/spine-csharp/src/Skeleton.cs
index 3484ac424..6745c6673 100644
--- a/spine-csharp/src/Skeleton.cs
+++ b/spine-csharp/src/Skeleton.cs
@@ -40,7 +40,6 @@ namespace Spine {
internal ExposedList transformConstraints;
internal ExposedList pathConstraints;
internal ExposedList updateCache = new ExposedList();
- internal ExposedList updateCacheReset = new ExposedList();
internal Skin skin;
internal float r = 1, g = 1, b = 1, a = 1;
internal float time;
@@ -55,7 +54,13 @@ namespace Spine {
public ExposedList IkConstraints { get { return ikConstraints; } }
public ExposedList PathConstraints { get { return pathConstraints; } }
public ExposedList TransformConstraints { get { return transformConstraints; } }
- public Skin Skin { get { return skin; } set { SetSkin(value); } }
+
+ public Skin Skin {
+ /// The skeleton's current skin. May be null.
+ get { return skin; }
+ /// Sets a skin, .
+ set { SetSkin(value); }
+ }
public float R { get { return r; } set { r = value; } }
public float G { get { return g; } set { g = value; } }
public float B { get { return b; } set { b = value; } }
@@ -72,6 +77,7 @@ namespace Spine {
[Obsolete("Use ScaleY instead. FlipY is when ScaleY is negative.")]
public bool FlipY { get { return scaleY < 0; } set { scaleY = value ? -1f : 1f; } }
+ /// Returns the root bone, or null if the skeleton has no bones.
public Bone RootBone {
get { return bones.Count == 0 ? null : bones.Items[0]; }
}
@@ -81,22 +87,23 @@ namespace Spine {
this.data = data;
bones = new ExposedList(data.bones.Count);
+ var bonesItems = this.bones.Items;
foreach (BoneData boneData in data.bones) {
Bone bone;
if (boneData.parent == null) {
bone = new Bone(boneData, this, null);
} else {
- Bone parent = bones.Items[boneData.parent.index];
+ Bone parent = bonesItems[boneData.parent.index];
bone = new Bone(boneData, this, parent);
parent.children.Add(bone);
}
- bones.Add(bone);
+ this.bones.Add(bone);
}
slots = new ExposedList(data.slots.Count);
drawOrder = new ExposedList(data.slots.Count);
foreach (SlotData slotData in data.slots) {
- Bone bone = bones.Items[slotData.boneData.index];
+ Bone bone = bonesItems[slotData.boneData.index];
Slot slot = new Slot(slotData, bone);
slots.Add(slot);
drawOrder.Add(slot);
@@ -115,7 +122,7 @@ namespace Spine {
pathConstraints.Add(new PathConstraint(pathConstraintData, this));
UpdateCache();
- UpdateWorldTransform();
+ //UpdateWorldTransform();
}
/// Caches information about bones and constraints. Must be called if the is modified or if bones, constraints, or
@@ -123,7 +130,6 @@ namespace Spine {
public void UpdateCache () {
var updateCache = this.updateCache;
updateCache.Clear();
- this.updateCacheReset.Clear();
int boneCount = this.bones.Items.Length;
var bones = this.bones;
@@ -191,16 +197,19 @@ namespace Spine {
Bone parent = constrained.Items[0];
SortBone(parent);
- if (constrained.Count > 1) {
- Bone child = constrained.Items[constrained.Count - 1];
- if (!updateCache.Contains(child))
- updateCacheReset.Add(child);
+ if (constrained.Count == 1) {
+ updateCache.Add(constraint);
+ SortReset(parent.children);
}
+ else {
+ Bone child = constrained.Items[constrained.Count - 1];
+ SortBone(child);
- updateCache.Add(constraint);
+ updateCache.Add(constraint);
- SortReset(parent.children);
- constrained.Items[constrained.Count - 1].sorted = true;
+ SortReset(parent.children);
+ child.sorted = true;
+ }
}
private void SortPathConstraint (PathConstraint constraint) {
@@ -218,17 +227,17 @@ namespace Spine {
Attachment attachment = slot.attachment;
if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone);
- var constrained = constraint.bones;
- int boneCount = constrained.Count;
+ var constrained = constraint.bones.Items;
+ int boneCount = constraint.bones.Count;
for (int i = 0; i < boneCount; i++)
- SortBone(constrained.Items[i]);
+ SortBone(constrained[i]);
updateCache.Add(constraint);
for (int i = 0; i < boneCount; i++)
- SortReset(constrained.Items[i].children);
+ SortReset(constrained[i].children);
for (int i = 0; i < boneCount; i++)
- constrained.Items[i].sorted = true;
+ constrained[i].sorted = true;
}
private void SortTransformConstraint (TransformConstraint constraint) {
@@ -238,25 +247,25 @@ namespace Spine {
SortBone(constraint.target);
- var constrained = constraint.bones;
- int boneCount = constrained.Count;
+ var constrained = constraint.bones.Items;
+ int boneCount = constraint.bones.Count;
if (constraint.data.local) {
for (int i = 0; i < boneCount; i++) {
- Bone child = constrained.Items[i];
+ Bone child = constrained[i];
SortBone(child.parent);
- if (!updateCache.Contains(child)) updateCacheReset.Add(child);
+ SortBone(child);
}
} else {
for (int i = 0; i < boneCount; i++)
- SortBone(constrained.Items[i]);
+ SortBone(constrained[i]);
}
updateCache.Add(constraint);
for (int i = 0; i < boneCount; i++)
- SortReset(constrained.Items[i].children);
+ SortReset(constrained[i].children);
for (int i = 0; i < boneCount; i++)
- constrained.Items[i].sorted = true;
+ constrained[i].sorted = true;
}
private void SortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) {
@@ -271,12 +280,12 @@ namespace Spine {
if (pathBones == null)
SortBone(slotBone);
else {
- var bones = this.bones;
+ var bones = this.bones.Items;
for (int i = 0, n = pathBones.Length; i < n;) {
int nn = pathBones[i++];
nn += i;
while (i < nn)
- SortBone(bones.Items[pathBones[i++]]);
+ SortBone(bones[pathBones[i++]]);
}
}
}
@@ -299,24 +308,17 @@ namespace Spine {
}
}
- /// Updates the world transform for each bone and applies constraints.
+
+ ///
+ /// Updates the world transform for each bone and applies all constraints.
+ ///
+ /// See World transforms in the Spine
+ /// Runtimes Guide.
+ ///
public void UpdateWorldTransform () {
- var updateCacheReset = this.updateCacheReset;
- var updateCacheResetItems = updateCacheReset.Items;
- for (int i = 0, n = updateCacheReset.Count; i < n; i++) {
- Bone bone = updateCacheResetItems[i];
- bone.ax = bone.x;
- bone.ay = bone.y;
- bone.arotation = bone.rotation;
- bone.ascaleX = bone.scaleX;
- bone.ascaleY = bone.scaleY;
- bone.ashearX = bone.shearX;
- bone.ashearY = bone.shearY;
- bone.appliedValid = true;
- }
- var updateItems = this.updateCache.Items;
- for (int i = 0, n = updateCache.Count; i < n; i++)
- updateItems[i].Update();
+ var updateCache = this.updateCache.Items;
+ for (int i = 0, n = this.updateCache.Count; i < n; i++)
+ updateCache[i].Update();
}
///
@@ -324,22 +326,7 @@ namespace Spine {
/// all constraints.
///
public void UpdateWorldTransform (Bone parent) {
- // This partial update avoids computing the world transform for constrained bones when 1) the bone is not updated
- // before the constraint, 2) the constraint only needs to access the applied local transform, and 3) the constraint calls
- // updateWorldTransform.
- var updateCacheReset = this.updateCacheReset;
- var updateCacheResetItems = updateCacheReset.Items;
- for (int i = 0, n = updateCacheReset.Count; i < n; i++) {
- Bone bone = updateCacheResetItems[i];
- bone.ax = bone.x;
- bone.ay = bone.y;
- bone.arotation = bone.rotation;
- bone.ascaleX = bone.scaleX;
- bone.ascaleY = bone.scaleY;
- bone.ashearX = bone.shearX;
- bone.ashearY = bone.shearY;
- bone.appliedValid = true;
- }
+ if (parent == null) throw new ArgumentNullException("parent", "parent cannot be null.");
// Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection.
Bone rootBone = this.RootBone;
@@ -358,10 +345,9 @@ namespace Spine {
rootBone.d = (pc * lb + pd * ld) * scaleY;
// Update everything except root bone.
- var updateCache = this.updateCache;
- var updateCacheItems = updateCache.Items;
- for (int i = 0, n = updateCache.Count; i < n; i++) {
- var updatable = updateCacheItems[i];
+ var updateCache = this.updateCache.Items;
+ for (int i = 0, n = this.updateCache.Count; i < n; i++) {
+ var updatable = updateCache[i];
if (updatable != rootBone)
updatable.Update();
}
@@ -375,13 +361,13 @@ namespace Spine {
/// Sets the bones and constraints to their setup pose values.
public void SetBonesToSetupPose () {
- var bonesItems = this.bones.Items;
- for (int i = 0, n = bones.Count; i < n; i++)
- bonesItems[i].SetToSetupPose();
+ var bones = this.bones.Items;
+ for (int i = 0, n = this.bones.Count; i < n; i++)
+ bones[i].SetToSetupPose();
- var ikConstraintsItems = this.ikConstraints.Items;
- for (int i = 0, n = ikConstraints.Count; i < n; i++) {
- IkConstraint constraint = ikConstraintsItems[i];
+ var ikConstraints = this.ikConstraints.Items;
+ for (int i = 0, n = this.ikConstraints.Count; i < n; i++) {
+ IkConstraint constraint = ikConstraints[i];
constraint.mix = constraint.data.mix;
constraint.softness = constraint.data.softness;
constraint.bendDirection = constraint.data.bendDirection;
@@ -389,9 +375,9 @@ namespace Spine {
constraint.stretch = constraint.data.stretch;
}
- var transformConstraintsItems = this.transformConstraints.Items;
- for (int i = 0, n = transformConstraints.Count; i < n; i++) {
- TransformConstraint constraint = transformConstraintsItems[i];
+ var transformConstraints = this.transformConstraints.Items;
+ for (int i = 0, n = this.transformConstraints.Count; i < n; i++) {
+ TransformConstraint constraint = transformConstraints[i];
TransformConstraintData constraintData = constraint.data;
constraint.rotateMix = constraintData.rotateMix;
constraint.translateMix = constraintData.translateMix;
@@ -399,9 +385,9 @@ namespace Spine {
constraint.shearMix = constraintData.shearMix;
}
- var pathConstraintItems = this.pathConstraints.Items;
- for (int i = 0, n = pathConstraints.Count; i < n; i++) {
- PathConstraint constraint = pathConstraintItems[i];
+ var pathConstraints = this.pathConstraints.Items;
+ for (int i = 0, n = this.pathConstraints.Count; i < n; i++) {
+ PathConstraint constraint = pathConstraints[i];
PathConstraintData constraintData = constraint.data;
constraint.position = constraintData.position;
constraint.spacing = constraintData.spacing;
@@ -411,23 +397,21 @@ namespace Spine {
}
public void SetSlotsToSetupPose () {
- var slots = this.slots;
- var slotsItems = slots.Items;
- drawOrder.Clear();
- for (int i = 0, n = slots.Count; i < n; i++)
- drawOrder.Add(slotsItems[i]);
-
- for (int i = 0, n = slots.Count; i < n; i++)
- slotsItems[i].SetToSetupPose();
+ var slots = this.slots.Items;
+ int n = this.slots.Count;
+ Array.Copy(slots, 0, drawOrder.Items, 0, n);
+ for (int i = 0; i < n; i++)
+ slots[i].SetToSetupPose();
}
+ /// Finds a bone by comparing each bone's name. It is more efficient to cache the results of this method than to call it
+ /// repeatedly.
/// May be null.
public Bone FindBone (string boneName) {
if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
- var bones = this.bones;
- var bonesItems = bones.Items;
- for (int i = 0, n = bones.Count; i < n; i++) {
- Bone bone = bonesItems[i];
+ var bones = this.bones.Items;
+ for (int i = 0, n = this.bones.Count; i < n; i++) {
+ Bone bone = bones[i];
if (bone.data.name == boneName) return bone;
}
return null;
@@ -443,13 +427,14 @@ namespace Spine {
return -1;
}
+ /// Finds a slot by comparing each slot's name. It is more efficient to cache the results of this method than to call it
+ /// repeatedly.
/// May be null.
public Slot FindSlot (string slotName) {
if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
- var slots = this.slots;
- var slotsItems = slots.Items;
- for (int i = 0, n = slots.Count; i < n; i++) {
- Slot slot = slotsItems[i];
+ var slots = this.slots.Items;
+ for (int i = 0, n = this.slots.Count; i < n; i++) {
+ Slot slot = slots[i];
if (slot.data.name == slotName) return slot;
}
return null;
@@ -461,11 +446,11 @@ namespace Spine {
var slots = this.slots;
var slotsItems = slots.Items;
for (int i = 0, n = slots.Count; i < n; i++)
- if (slotsItems[i].data.name.Equals(slotName)) return i;
+ if (slotsItems[i].data.name == slotName) return i;
return -1;
}
- /// Sets a skin by name (see SetSkin).
+ /// Sets a skin by name ().
public void SetSkin (string skinName) {
Skin foundSkin = data.FindSkin(skinName);
if (foundSkin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName");
@@ -506,7 +491,7 @@ namespace Spine {
UpdateCache();
}
- /// Finds an attachment by looking in the {@link #skin} and {@link SkeletonData#defaultSkin} using the slot name and attachment name.
+ /// Finds an attachment by looking in the and using the slot name and attachment name.
/// May be null.
public Attachment GetAttachment (string slotName, string attachmentName) {
return GetAttachment(data.FindSlotIndex(slotName), attachmentName);
@@ -543,34 +528,40 @@ namespace Spine {
throw new Exception("Slot not found: " + slotName);
}
+ /// Finds an IK constraint by comparing each IK constraint's name. It is more efficient to cache the results of this method
+ /// than to call it repeatedly.
/// May be null.
public IkConstraint FindIkConstraint (string constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
- ExposedList ikConstraints = this.ikConstraints;
- for (int i = 0, n = ikConstraints.Count; i < n; i++) {
- IkConstraint ikConstraint = ikConstraints.Items[i];
+ var ikConstraints = this.ikConstraints.Items;
+ for (int i = 0, n = this.ikConstraints.Count; i < n; i++) {
+ IkConstraint ikConstraint = ikConstraints[i];
if (ikConstraint.data.name == constraintName) return ikConstraint;
}
return null;
}
+ /// Finds a transform constraint by comparing each transform constraint's name. It is more efficient to cache the results of
+ /// this method than to call it repeatedly.
/// May be null.
public TransformConstraint FindTransformConstraint (string constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
- ExposedList transformConstraints = this.transformConstraints;
- for (int i = 0, n = transformConstraints.Count; i < n; i++) {
- TransformConstraint transformConstraint = transformConstraints.Items[i];
+ var transformConstraints = this.transformConstraints.Items;
+ for (int i = 0, n = this.transformConstraints.Count; i < n; i++) {
+ TransformConstraint transformConstraint = transformConstraints[i];
if (transformConstraint.data.Name == constraintName) return transformConstraint;
}
return null;
}
+ /// Finds a path constraint by comparing each path constraint's name. It is more efficient to cache the results of this method
+ /// than to call it repeatedly.
/// May be null.
public PathConstraint FindPathConstraint (string constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
- ExposedList pathConstraints = this.pathConstraints;
- for (int i = 0, n = pathConstraints.Count; i < n; i++) {
- PathConstraint constraint = pathConstraints.Items[i];
+ var pathConstraints = this.pathConstraints.Items;
+ for (int i = 0, n = this.pathConstraints.Count; i < n; i++) {
+ PathConstraint constraint = pathConstraints[i];
if (constraint.data.Name.Equals(constraintName)) return constraint;
}
return null;
@@ -589,10 +580,10 @@ namespace Spine {
public void GetBounds (out float x, out float y, out float width, out float height, ref float[] vertexBuffer) {
float[] temp = vertexBuffer;
temp = temp ?? new float[8];
- var drawOrderItems = this.drawOrder.Items;
+ var drawOrder = this.drawOrder.Items;
float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue;
- for (int i = 0, n = drawOrderItems.Length; i < n; i++) {
- Slot slot = drawOrderItems[i];
+ for (int i = 0, n = this.drawOrder.Count; i < n; i++) {
+ Slot slot = drawOrder[i];
if (!slot.bone.active) continue;
int verticesLength = 0;
float[] vertices = null;
diff --git a/spine-csharp/src/SkeletonBinary.cs b/spine-csharp/src/SkeletonBinary.cs
index 810c22b72..5816efd24 100644
--- a/spine-csharp/src/SkeletonBinary.cs
+++ b/spine-csharp/src/SkeletonBinary.cs
@@ -34,6 +34,7 @@
using System;
using System.IO;
using System.Collections.Generic;
+using System.Runtime.Serialization;
#if WINDOWS_STOREAPP
using System.Threading.Tasks;
@@ -41,7 +42,7 @@ using Windows.Storage;
#endif
namespace Spine {
- public class SkeletonBinary {
+ public class SkeletonBinary : SkeletonLoader {
public const int BONE_ROTATE = 0;
public const int BONE_TRANSLATE = 1;
public const int BONE_SCALE = 2;
@@ -59,22 +60,15 @@ namespace Spine {
public const int CURVE_STEPPED = 1;
public const int CURVE_BEZIER = 2;
- public float Scale { get; set; }
-
- private AttachmentLoader attachmentLoader;
- private List linkedMeshes = new List();
+ public SkeletonBinary (AttachmentLoader attachmentLoader)
+ :base(attachmentLoader) {
+ }
public SkeletonBinary (params Atlas[] atlasArray)
- : this(new AtlasAttachmentLoader(atlasArray)) {
+ : base(atlasArray) {
}
- public SkeletonBinary (AttachmentLoader attachmentLoader) {
- if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader");
- this.attachmentLoader = attachmentLoader;
- Scale = 1;
- }
-
- #if !ISUNITY && WINDOWS_STOREAPP
+#if !ISUNITY && WINDOWS_STOREAPP
private async Task ReadFile(string path) {
var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
using (var input = new BufferedStream(await folder.GetFileAsync(path).AsTask().ConfigureAwait(false))) {
@@ -84,11 +78,11 @@ namespace Spine {
}
}
- public SkeletonData ReadSkeletonData (String path) {
+ public override SkeletonData ReadSkeletonData (string path) {
return this.ReadFile(path).Result;
}
- #else
- public SkeletonData ReadSkeletonData (String path) {
+#else
+ public override SkeletonData ReadSkeletonData (string path) {
#if WINDOWS_PHONE
using (var input = new BufferedStream(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) {
#else
@@ -119,13 +113,13 @@ namespace Spine {
public SkeletonData ReadSkeletonData (Stream file) {
if (file == null) throw new ArgumentNullException("file");
- float scale = Scale;
+ float scale = this.scale;
var skeletonData = new SkeletonData();
SkeletonInput input = new SkeletonInput(file);
- skeletonData.hash = input.ReadString();
- if (skeletonData.hash.Length == 0) skeletonData.hash = null;
+ long hash = input.ReadLong();
+ skeletonData.hash = hash == 0 ? null : hash.ToString();
skeletonData.version = input.ReadString();
if (skeletonData.version.Length == 0) skeletonData.version = null;
if ("3.8.75" == skeletonData.version)
@@ -151,16 +145,15 @@ namespace Spine {
Object[] o;
// Strings.
- input.strings = new ExposedList(n = input.ReadInt(true));
- o = input.strings.Resize(n).Items;
+ o = input.strings = new String[n = input.ReadInt(true)];
for (int i = 0; i < n; i++)
o[i] = input.ReadString();
// Bones.
- o = skeletonData.bones.Resize(n = input.ReadInt(true)).Items;
+ var bones = skeletonData.bones.Resize(n = input.ReadInt(true)).Items;
for (int i = 0; i < n; i++) {
String name = input.ReadString();
- BoneData parent = i == 0 ? null : skeletonData.bones.Items[input.ReadInt(true)];
+ BoneData parent = i == 0 ? null : bones[input.ReadInt(true)];
BoneData data = new BoneData(i, name, parent);
data.rotation = input.ReadFloat();
data.x = input.ReadFloat() * scale;
@@ -169,18 +162,18 @@ namespace Spine {
data.scaleY = input.ReadFloat();
data.shearX = input.ReadFloat();
data.shearY = input.ReadFloat();
- data.length = input.ReadFloat() * scale;
+ data.Length = input.ReadFloat() * scale;
data.transformMode = TransformModeValues[input.ReadInt(true)];
data.skinRequired = input.ReadBoolean();
if (nonessential) input.ReadInt(); // Skip bone color.
- o[i] = data;
+ bones[i] = data;
}
// Slots.
- o = skeletonData.slots.Resize(n = input.ReadInt(true)).Items;
+ var slots = skeletonData.slots.Resize(n = input.ReadInt(true)).Items;
for (int i = 0; i < n; i++) {
String slotName = input.ReadString();
- BoneData boneData = skeletonData.bones.Items[input.ReadInt(true)];
+ BoneData boneData = bones[input.ReadInt(true)];
SlotData slotData = new SlotData(i, slotName, boneData);
int color = input.ReadInt();
slotData.r = ((color & 0xff000000) >> 24) / 255f;
@@ -198,7 +191,7 @@ namespace Spine {
slotData.attachmentName = input.ReadStringRef();
slotData.blendMode = (BlendMode)input.ReadInt(true);
- o[i] = slotData;
+ slots[i] = slotData;
}
// IK constraints.
@@ -207,10 +200,10 @@ namespace Spine {
IkConstraintData data = new IkConstraintData(input.ReadString());
data.order = input.ReadInt(true);
data.skinRequired = input.ReadBoolean();
- Object[] bones = data.bones.Resize(nn = input.ReadInt(true)).Items;
+ var constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items;
for (int ii = 0; ii < nn; ii++)
- bones[ii] = skeletonData.bones.Items[input.ReadInt(true)];
- data.target = skeletonData.bones.Items[input.ReadInt(true)];
+ constraintBones[ii] = bones[input.ReadInt(true)];
+ data.target = bones[input.ReadInt(true)];
data.mix = input.ReadFloat();
data.softness = input.ReadFloat() * scale;
data.bendDirection = input.ReadSByte();
@@ -226,10 +219,10 @@ namespace Spine {
TransformConstraintData data = new TransformConstraintData(input.ReadString());
data.order = input.ReadInt(true);
data.skinRequired = input.ReadBoolean();
- Object[] bones = data.bones.Resize(nn = input.ReadInt(true)).Items;
+ var constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items;
for (int ii = 0; ii < nn; ii++)
- bones[ii] = skeletonData.bones.Items[input.ReadInt(true)];
- data.target = skeletonData.bones.Items[input.ReadInt(true)];
+ constraintBones[ii] = bones[input.ReadInt(true)];
+ data.target = bones[input.ReadInt(true)];
data.local = input.ReadBoolean();
data.relative = input.ReadBoolean();
data.offsetRotation = input.ReadFloat();
@@ -251,10 +244,10 @@ namespace Spine {
PathConstraintData data = new PathConstraintData(input.ReadString());
data.order = input.ReadInt(true);
data.skinRequired = input.ReadBoolean();
- Object[] bones = data.bones.Resize(nn = input.ReadInt(true)).Items;
+ Object[] constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items;
for (int ii = 0; ii < nn; ii++)
- bones[ii] = skeletonData.bones.Items[input.ReadInt(true)];
- data.target = skeletonData.slots.Items[input.ReadInt(true)];
+ constraintBones[ii] = bones[input.ReadInt(true)];
+ data.target = slots[input.ReadInt(true)];
data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue(input.ReadInt(true));
data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue(input.ReadInt(true));
data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue(input.ReadInt(true));
@@ -286,7 +279,7 @@ namespace Spine {
// Linked meshes.
n = linkedMeshes.Count;
for (int i = 0; i < n; i++) {
- SkeletonJson.LinkedMesh linkedMesh = linkedMeshes[i];
+ LinkedMesh linkedMesh = linkedMeshes[i];
Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin);
if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin);
Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent);
@@ -334,16 +327,21 @@ namespace Spine {
} else {
skin = new Skin(input.ReadStringRef());
Object[] bones = skin.bones.Resize(input.ReadInt(true)).Items;
+ var bonesItems = skeletonData.bones.Items;
for (int i = 0, n = skin.bones.Count; i < n; i++)
- bones[i] = skeletonData.bones.Items[input.ReadInt(true)];
+ bones[i] = bonesItems[input.ReadInt(true)];
+ var ikConstraintsItems = skeletonData.ikConstraints.Items;
for (int i = 0, n = input.ReadInt(true); i < n; i++)
- skin.constraints.Add(skeletonData.ikConstraints.Items[input.ReadInt(true)]);
+ skin.constraints.Add(ikConstraintsItems[input.ReadInt(true)]);
+ var transformConstraintsItems = skeletonData.transformConstraints.Items;
for (int i = 0, n = input.ReadInt(true); i < n; i++)
- skin.constraints.Add(skeletonData.transformConstraints.Items[input.ReadInt(true)]);
+ skin.constraints.Add(transformConstraintsItems[input.ReadInt(true)]);
+ var pathConstraintsItems = skeletonData.pathConstraints.Items;
for (int i = 0, n = input.ReadInt(true); i < n; i++)
- skin.constraints.Add(skeletonData.pathConstraints.Items[input.ReadInt(true)]);
+ skin.constraints.Add(pathConstraintsItems[input.ReadInt(true)]);
skin.constraints.TrimExcess();
+
slotCount = input.ReadInt(true);
}
for (int i = 0; i < slotCount; i++) {
@@ -359,14 +357,12 @@ namespace Spine {
private Attachment ReadAttachment (SkeletonInput input, SkeletonData skeletonData, Skin skin, int slotIndex,
String attachmentName, bool nonessential) {
-
- float scale = Scale;
+ float scale = this.scale;
String name = input.ReadStringRef();
if (name == null) name = attachmentName;
- AttachmentType type = (AttachmentType)input.ReadByte();
- switch (type) {
+ switch ((AttachmentType)input.ReadByte()) {
case AttachmentType.Region: {
String path = input.ReadStringRef();
float rotation = input.ReadFloat();
@@ -529,7 +525,7 @@ namespace Spine {
}
private Vertices ReadVertices (SkeletonInput input, int vertexCount) {
- float scale = Scale;
+ float scale = this.scale;
int verticesLength = vertexCount << 1;
Vertices vertices = new Vertices();
if(!input.ReadBoolean()) {
@@ -574,66 +570,97 @@ namespace Spine {
return array;
}
+ /// SerializationException will be thrown when a Vertex attachment is not found.
+ /// Throws IOException when a read operation fails.
private Animation ReadAnimation (String name, SkeletonInput input, SkeletonData skeletonData) {
- var timelines = new ExposedList(32);
- float scale = Scale;
- float duration = 0;
+ var timelines = new ExposedList(input.ReadInt(true));
+ float scale = this.scale;
// Slot timelines.
for (int i = 0, n = input.ReadInt(true); i < n; i++) {
int slotIndex = input.ReadInt(true);
for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) {
- int timelineType = input.ReadByte();
- int frameCount = input.ReadInt(true);
+ int timelineType = input.ReadByte(), frameCount = input.ReadInt(true), frameLast = frameCount - 1;
switch (timelineType) {
- case SLOT_ATTACHMENT: {
- AttachmentTimeline timeline = new AttachmentTimeline(frameCount);
- timeline.slotIndex = slotIndex;
- for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
- timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadStringRef());
- timelines.Add(timeline);
- duration = Math.Max(duration, timeline.frames[frameCount - 1]);
- break;
- }
- case SLOT_COLOR: {
- ColorTimeline timeline = new ColorTimeline(frameCount);
- timeline.slotIndex = slotIndex;
- for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
- float time = input.ReadFloat();
- int color = input.ReadInt();
- float r = ((color & 0xff000000) >> 24) / 255f;
- float g = ((color & 0x00ff0000) >> 16) / 255f;
- float b = ((color & 0x0000ff00) >> 8) / 255f;
- float a = ((color & 0x000000ff)) / 255f;
- timeline.SetFrame(frameIndex, time, r, g, b, a);
- if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
+ case SLOT_ATTACHMENT: {
+ AttachmentTimeline timeline = new AttachmentTimeline(frameCount, slotIndex);
+ for (int frame = 0; frame < frameCount; frame++)
+ timeline.SetFrame(frame, input.ReadFloat(), input.ReadStringRef());
+ timelines.Add(timeline);
+ break;
}
- timelines.Add(timeline);
- duration = Math.Max(duration, timeline.frames[(frameCount - 1) * ColorTimeline.ENTRIES]);
- break;
- }
- case SLOT_TWO_COLOR: {
- TwoColorTimeline timeline = new TwoColorTimeline(frameCount);
- timeline.slotIndex = slotIndex;
- for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
+ case SLOT_COLOR: {
+ ColorTimeline timeline = new ColorTimeline(frameCount, input.ReadInt(true), slotIndex);
float time = input.ReadFloat();
- int color = input.ReadInt();
- float r = ((color & 0xff000000) >> 24) / 255f;
- float g = ((color & 0x00ff0000) >> 16) / 255f;
- float b = ((color & 0x0000ff00) >> 8) / 255f;
- float a = ((color & 0x000000ff)) / 255f;
- int color2 = input.ReadInt(); // 0x00rrggbb
- float r2 = ((color2 & 0x00ff0000) >> 16) / 255f;
- float g2 = ((color2 & 0x0000ff00) >> 8) / 255f;
- float b2 = ((color2 & 0x000000ff)) / 255f;
-
- timeline.SetFrame(frameIndex, time, r, g, b, a, r2, g2, b2);
- if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
+ float r = input.Read() / 255f, g = input.Read() / 255f;
+ float b = input.Read() / 255f, a = input.Read() / 255f;
+ for (int frame = 0, bezier = 0; ; frame++) {
+ timeline.SetFrame(frame, time, r, g, b, a);
+ if (frame == frameLast) break;
+ float time2 = input.ReadFloat();
+ float r2 = input.Read() / 255f, g2 = input.Read() / 255f;
+ float b2 = input.Read() / 255f, a2 = input.Read() / 255f;
+ switch (input.ReadByte()) {
+ case CURVE_STEPPED:
+ timeline.SetStepped(frame);
+ break;
+ case CURVE_BEZIER:
+ SetBezier(input, timeline, bezier++, frame, 0, time, time2, r, r2, 1);
+ SetBezier(input, timeline, bezier++, frame, 1, time, time2, g, g2, 1);
+ SetBezier(input, timeline, bezier++, frame, 2, time, time2, b, b2, 1);
+ SetBezier(input, timeline, bezier++, frame, 3, time, time2, a, a2, 1);
+ break;
+ }
+ time = time2;
+ r = r2;
+ g = g2;
+ b = b2;
+ a = a2;
+ }
+ timelines.Add(timeline);
+ break;
+ }
+ case SLOT_TWO_COLOR: {
+ TwoColorTimeline timeline = new TwoColorTimeline(frameCount, input.ReadInt(true), slotIndex);
+ float time = input.ReadFloat();
+ float r = input.Read() / 255f, g = input.Read() / 255f;
+ float b = input.Read() / 255f, a = input.Read() / 255f;
+ float r2 = input.Read() / 255f, g2 = input.Read() / 255f;
+ float b2 = input.Read() / 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();
+ float nr = input.Read() / 255f, ng = input.Read() / 255f;
+ float nb = input.Read() / 255f, na = input.Read() / 255f;
+ float nr2 = input.Read() / 255f, ng2 = input.Read() / 255f;
+ float nb2 = input.Read() / 255f;
+ switch (input.ReadByte()) {
+ case CURVE_STEPPED:
+ timeline.SetStepped(frame);
+ break;
+ case CURVE_BEZIER:
+ SetBezier(input, timeline, bezier++, frame, 0, time, time2, r, nr, 1);
+ SetBezier(input, timeline, bezier++, frame, 1, time, time2, g, ng, 1);
+ SetBezier(input, timeline, bezier++, frame, 2, time, time2, b, nb, 1);
+ SetBezier(input, timeline, bezier++, frame, 3, time, time2, a, na, 1);
+ SetBezier(input, timeline, bezier++, frame, 4, time, time2, r2, nr2, 1);
+ SetBezier(input, timeline, bezier++, frame, 5, time, time2, g2, ng2, 1);
+ SetBezier(input, timeline, bezier++, frame, 6, time, time2, b2, nb2, 1);
+ break;
+ }
+ time = time2;
+ r = nr;
+ g = ng;
+ b = nb;
+ a = na;
+ r2 = nr2;
+ g2 = ng2;
+ b2 = nb2;
+ }
+ timelines.Add(timeline);
+ break;
}
- timelines.Add(timeline);
- duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TwoColorTimeline.ENTRIES]);
- break;
- }
}
}
}
@@ -642,76 +669,78 @@ namespace Spine {
for (int i = 0, n = input.ReadInt(true); i < n; i++) {
int boneIndex = input.ReadInt(true);
for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) {
- int timelineType = input.ReadByte();
- int frameCount = input.ReadInt(true);
- switch (timelineType) {
- case BONE_ROTATE: {
- RotateTimeline timeline = new RotateTimeline(frameCount);
- timeline.boneIndex = boneIndex;
- for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
- timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat());
- if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
- }
- timelines.Add(timeline);
- duration = Math.Max(duration, timeline.frames[(frameCount - 1) * RotateTimeline.ENTRIES]);
+ switch (input.ReadByte()) {
+ case BONE_ROTATE:
+ timelines.Add(ReadTimeline(input, new RotateTimeline(input.ReadInt(true), input.ReadInt(true), boneIndex), 1));
break;
- }
- case BONE_TRANSLATE:
- case BONE_SCALE:
- case BONE_SHEAR: {
- TranslateTimeline timeline;
- float timelineScale = 1;
- if (timelineType == BONE_SCALE)
- timeline = new ScaleTimeline(frameCount);
- else if (timelineType == BONE_SHEAR)
- timeline = new ShearTimeline(frameCount);
- else {
- timeline = new TranslateTimeline(frameCount);
- timelineScale = scale;
- }
- timeline.boneIndex = boneIndex;
- for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
- timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat() * timelineScale,
- input.ReadFloat() * timelineScale);
- if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
- }
- timelines.Add(timeline);
- duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TranslateTimeline.ENTRIES]);
+ case BONE_TRANSLATE:
+ timelines
+ .Add(ReadTimeline(input, new TranslateTimeline(input.ReadInt(true), input.ReadInt(true), boneIndex), scale));
+ break;
+ case BONE_SCALE:
+ timelines.Add(ReadTimeline(input, new ScaleTimeline(input.ReadInt(true), input.ReadInt(true), boneIndex), 1));
+ break;
+ case BONE_SHEAR:
+ timelines.Add(ReadTimeline(input, new ShearTimeline(input.ReadInt(true), input.ReadInt(true), boneIndex), 1));
break;
- }
}
}
}
// IK constraint timelines.
for (int i = 0, n = input.ReadInt(true); i < n; i++) {
- int index = input.ReadInt(true);
- int frameCount = input.ReadInt(true);
- IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount) {
- ikConstraintIndex = index
- };
- for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
- timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat(), input.ReadFloat() * scale, input.ReadSByte(), input.ReadBoolean(),
- input.ReadBoolean());
- if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
+ 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(), mix = input.ReadFloat(), softness = input.ReadFloat() * scale;
+ for (int frame = 0, bezier = 0; ; frame++) {
+ timeline.SetFrame(frame, time, mix, softness, input.ReadSByte(), input.ReadBoolean(), input.ReadBoolean());
+ if (frame == frameLast) break;
+ float time2 = input.ReadFloat(), mix2 = input.ReadFloat(), 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, 1);
+ SetBezier(input, timeline, bezier++, frame, 1, time, time2, softness, softness2, scale);
+ break;
+ }
+ time = time2;
+ mix = mix2;
+ softness = softness2;
}
timelines.Add(timeline);
- duration = Math.Max(duration, timeline.frames[(frameCount - 1) * IkConstraintTimeline.ENTRIES]);
}
// Transform constraint timelines.
for (int i = 0, n = input.ReadInt(true); i < n; i++) {
- int index = input.ReadInt(true);
- int frameCount = input.ReadInt(true);
- TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount);
- timeline.transformConstraintIndex = index;
- for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
- timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat(), input.ReadFloat(), input.ReadFloat(),
- input.ReadFloat());
- if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
+ 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(), 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, 1);
+ SetBezier(input, timeline, bezier++, frame, 1, time, time2, translateMix, translateMix2, 1);
+ SetBezier(input, timeline, bezier++, frame, 2, time, time2, scaleMix, scaleMix2, 1);
+ SetBezier(input, timeline, bezier++, frame, 3, time, time2, shearMix, shearMix2, 1);
+ break;
+ }
+ time = time2;
+ rotateMix = rotateMix2;
+ translateMix = translateMix2;
+ scaleMix = scaleMix2;
+ shearMix = shearMix2;
}
timelines.Add(timeline);
- duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TransformConstraintTimeline.ENTRIES]);
}
// Path constraint timelines.
@@ -719,40 +748,21 @@ namespace Spine {
int index = input.ReadInt(true);
PathConstraintData data = skeletonData.pathConstraints.Items[index];
for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) {
- int timelineType = input.ReadSByte();
- int frameCount = input.ReadInt(true);
- switch(timelineType) {
+ switch (input.ReadByte()) {
case PATH_POSITION:
- case PATH_SPACING: {
- PathConstraintPositionTimeline timeline;
- float timelineScale = 1;
- if (timelineType == PATH_SPACING) {
- timeline = new PathConstraintSpacingTimeline(frameCount);
- if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale;
- } else {
- timeline = new PathConstraintPositionTimeline(frameCount);
- if (data.positionMode == PositionMode.Fixed) timelineScale = scale;
- }
- timeline.pathConstraintIndex = index;
- for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
- timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat() * timelineScale);
- if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
- }
- timelines.Add(timeline);
- duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintPositionTimeline.ENTRIES]);
- break;
- }
- case PATH_MIX: {
- PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frameCount);
- timeline.pathConstraintIndex = index;
- for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
- timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat(), input.ReadFloat());
- if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
- }
- timelines.Add(timeline);
- duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintMixTimeline.ENTRIES]);
- break;
- }
+ timelines
+ .Add(ReadTimeline(input, new PathConstraintPositionTimeline(input.ReadInt(true), input.ReadInt(true), index),
+ data.positionMode == PositionMode.Fixed ? scale : 1));
+ break;
+ case PATH_SPACING:
+ 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_MIX:
+ timelines
+ .Add(ReadTimeline(input, new PathConstraintMixTimeline(input.ReadInt(true), input.ReadInt(true), index), 1));
+ break;
}
}
}
@@ -763,18 +773,18 @@ namespace Spine {
for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) {
int slotIndex = input.ReadInt(true);
for (int iii = 0, nnn = input.ReadInt(true); iii < nnn; iii++) {
- VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, input.ReadStringRef());
- bool weighted = attachment.bones != null;
- float[] vertices = attachment.vertices;
- int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length;
+ String attachmentName = input.ReadStringRef();
+ VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, attachmentName);
+ if (attachment == null) throw new SerializationException("Vertex attachment not found: " + attachmentName);
+ bool weighted = attachment.Bones != null;
+ float[] vertices = attachment.Vertices;
+ int deformLength = weighted ? (vertices.Length / 3) << 1 : vertices.Length;
- int frameCount = input.ReadInt(true);
- DeformTimeline timeline = new DeformTimeline(frameCount);
- timeline.slotIndex = slotIndex;
- timeline.attachment = attachment;
+ int frameCount = input.ReadInt(true), frameLast = frameCount - 1;
+ DeformTimeline timeline = new DeformTimeline(frameCount, input.ReadInt(true), slotIndex, attachment);
- for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
- float time = input.ReadFloat();
+ float time = input.ReadFloat();
+ for (int frame = 0, bezier = 0; ; frame++) {
float[] deform;
int end = input.ReadInt(true);
if (end == 0)
@@ -786,7 +796,8 @@ namespace Spine {
if (scale == 1) {
for (int v = start; v < end; v++)
deform[v] = input.ReadFloat();
- } else {
+ }
+ else {
for (int v = start; v < end; v++)
deform[v] = input.ReadFloat() * scale;
}
@@ -795,12 +806,20 @@ namespace Spine {
deform[v] += vertices[v];
}
}
-
- timeline.SetFrame(frameIndex, time, deform);
- if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
+ 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, 1);
+ break;
+ }
+ time = time2;
}
timelines.Add(timeline);
- duration = Math.Max(duration, timeline.frames[frameCount - 1]);
}
}
}
@@ -835,7 +854,6 @@ namespace Spine {
timeline.SetFrame(i, time, drawOrder);
}
timelines.Add(timeline);
- duration = Math.Max(duration, timeline.frames[drawOrderCount - 1]);
}
// Event timeline.
@@ -845,34 +863,75 @@ namespace Spine {
for (int i = 0; i < eventCount; i++) {
float time = input.ReadFloat();
EventData eventData = skeletonData.events.Items[input.ReadInt(true)];
- Event e = new Event(time, eventData) {
- Int = input.ReadInt(false),
- Float = input.ReadFloat(),
- String = input.ReadBoolean() ? input.ReadString() : eventData.String
- };
- if (e.data.AudioPath != null) {
+ Event e = new Event(time, eventData);
+ e.intValue = input.ReadInt(false);
+ e.floatValue = input.ReadFloat();
+ e.stringValue = input.ReadBoolean() ? input.ReadString() : eventData.String;
+ if (e.Data.AudioPath != null) {
e.volume = input.ReadFloat();
e.balance = input.ReadFloat();
}
timeline.SetFrame(i, e);
}
timelines.Add(timeline);
- duration = Math.Max(duration, timeline.frames[eventCount - 1]);
}
- timelines.TrimExcess();
+ float duration = 0;
+ var items = timelines.Items;
+ for (int i = 0, n = timelines.Count; i < n; i++)
+ duration = Math.Max(duration, items[i].Duration);
return new Animation(name, timelines, duration);
}
- private void ReadCurve (SkeletonInput input, int frameIndex, CurveTimeline timeline) {
- switch (input.ReadByte()) {
- case CURVE_STEPPED:
- timeline.SetStepped(frameIndex);
- break;
- case CURVE_BEZIER:
- timeline.SetCurve(frameIndex, input.ReadFloat(), input.ReadFloat(), input.ReadFloat(), input.ReadFloat());
- break;
+ /// Throws IOException when a read operation fails.
+ private Timeline ReadTimeline (SkeletonInput input, CurveTimeline1 timeline, float scale) {
+ float time = input.ReadFloat(), value = input.ReadFloat() * scale;
+ for (int frame = 0, bezier = 0, frameLast = timeline.FrameCount - 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, 1);
+ break;
+ }
+ time = time2;
+ value = value2;
}
+ return timeline;
+ }
+
+ /// Throws IOException when a read operation fails.
+ private Timeline ReadTimeline (SkeletonInput input, CurveTimeline2 timeline, float scale) {
+ float time = input.ReadFloat(), value1 = input.ReadFloat() * scale, value2 = input.ReadFloat() * scale;
+ for (int frame = 0, bezier = 0, frameLast = timeline.FrameCount - 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, scale);
+ SetBezier(input, timeline, bezier++, frame, 1, time, time2, value2, nvalue2, scale);
+ break;
+ }
+ time = time2;
+ value1 = nvalue1;
+ value2 = nvalue2;
+ }
+ return timeline;
+ }
+
+ /// Throws IOException when a read operation fails.
+ void SetBezier (SkeletonInput input, CurveTimeline timeline, int bezier, int frame, int value, float time1, float time2,
+ float value1, float value2, float scale) {
+ timeline.SetBezier(bezier, frame, value, time1, value1, input.ReadFloat(), input.ReadFloat() * scale, input.ReadFloat(),
+ input.ReadFloat() * scale, time2, value2);
}
internal class Vertices
@@ -883,14 +942,18 @@ namespace Spine {
internal class SkeletonInput {
private byte[] chars = new byte[32];
- private byte[] bytesBigEndian = new byte[4];
- internal ExposedList strings;
+ private byte[] bytesBigEndian = new byte[8];
+ internal string[] strings;
Stream input;
public SkeletonInput (Stream input) {
this.input = input;
}
+ public int Read () {
+ return input.ReadByte();
+ }
+
public byte ReadByte () {
return (byte)input.ReadByte();
}
@@ -922,6 +985,18 @@ namespace Spine {
+ bytesBigEndian[3];
}
+ public long ReadLong () {
+ input.Read(bytesBigEndian, 0, 8);
+ return ((long)(bytesBigEndian[0]) << 56)
+ + ((long)(bytesBigEndian[1]) << 48)
+ + ((long)(bytesBigEndian[2]) << 40)
+ + ((long)(bytesBigEndian[3]) << 32)
+ + ((long)(bytesBigEndian[4]) << 24)
+ + ((long)(bytesBigEndian[5]) << 16)
+ + ((long)(bytesBigEndian[6]) << 8)
+ + (long)(bytesBigEndian[7]);
+ }
+
public int ReadInt (bool optimizePositive) {
int b = input.ReadByte();
int result = b & 0x7F;
@@ -959,7 +1034,7 @@ namespace Spine {
///May be null.
public String ReadStringRef () {
int index = ReadInt(true);
- return index == 0 ? null : strings.Items[index - 1];
+ return index == 0 ? null : strings[index - 1];
}
public void ReadFully (byte[] buffer, int offset, int length) {
@@ -974,20 +1049,9 @@ namespace Spine {
/// Returns the version string of binary skeleton data.
public string GetVersionString () {
try {
- // Hash.
- int byteCount = ReadInt(true);
- if (byteCount > 1) input.Position += byteCount - 1;
-
- // Version.
- byteCount = ReadInt(true);
- if (byteCount > 1) {
- byteCount--;
- var buffer = new byte[byteCount];
- ReadFully(buffer, 0, byteCount);
- return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount);
- }
-
- throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.", "input");
+ ReadLong(); // long hash
+ string version = ReadString();
+ return version;
} catch (Exception e) {
throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.\n" + e, "input");
}
diff --git a/spine-csharp/src/SkeletonBounds.cs b/spine-csharp/src/SkeletonBounds.cs
index fe247364f..7904dfa51 100644
--- a/spine-csharp/src/SkeletonBounds.cs
+++ b/spine-csharp/src/SkeletonBounds.cs
@@ -176,7 +176,7 @@ namespace Spine {
}
/// Returns the first bounding box attachment that contains the point, or null. When doing many checks, it is usually more
- /// efficient to only call this method if {@link #aabbContainsPoint(float, float)} returns true.
+ /// efficient to only call this method if returns true.
public BoundingBoxAttachment ContainsPoint (float x, float y) {
ExposedList polygons = Polygons;
for (int i = 0, n = polygons.Count; i < n; i++)
@@ -185,7 +185,7 @@ namespace Spine {
}
/// Returns the first bounding box attachment that contains the line segment, or null. When doing many checks, it is usually
- /// more efficient to only call this method if {@link #aabbIntersectsSegment(float, float, float, float)} returns true.
+ /// more efficient to only call this method if returns true.
public BoundingBoxAttachment IntersectsSegment (float x1, float y1, float x2, float y2) {
ExposedList polygons = Polygons;
for (int i = 0, n = polygons.Count; i < n; i++)
diff --git a/spine-csharp/src/SkeletonData.cs b/spine-csharp/src/SkeletonData.cs
index 05ec27229..5c390f3dc 100644
--- a/spine-csharp/src/SkeletonData.cs
+++ b/spine-csharp/src/SkeletonData.cs
@@ -50,6 +50,8 @@ namespace Spine {
internal float fps;
internal string imagesPath, audioPath;
+ ///The skeleton's name, which by default is the name of the skeleton data file when possible, or null when a name hasn't been
+ ///set.
public string Name { get { return name; } set { name = value; } }
/// The skeleton's bones, sorted parent first. The root bone is always the first bone.
@@ -79,16 +81,18 @@ namespace Spine {
public float Height { get { return height; } set { height = value; } }
/// The Spine version used to export this data, or null.
public string Version { get { return version; } set { version = value; } }
+
+ ///The skeleton data hash. This value will change if any of the skeleton data has changed.
+ ///May be null.
public string Hash { get { return hash; } set { hash = value; } }
- /// The path to the images directory as defined in Spine. Available only when nonessential data was exported. May be null
public string ImagesPath { get { return imagesPath; } set { imagesPath = value; } }
- /// The path to the audio directory defined in Spine. Available only when nonessential data was exported. May be null.
+ /// The path to the audio directory as defined in Spine. Available only when nonessential data was exported.
+ /// May be null.
public string AudioPath { get { return audioPath; } set { audioPath = value; } }
- ///
- /// The dopesheet FPS in Spine. Available only when nonessential data was exported.
+ /// The dopesheet FPS in Spine, or zero if nonessential data was not exported.
public float Fps { get { return fps; } set { fps = value; } }
// --- Bones.
@@ -99,10 +103,9 @@ namespace Spine {
/// May be null.
public BoneData FindBone (string boneName) {
if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
- var bones = this.bones;
- var bonesItems = bones.Items;
- for (int i = 0, n = bones.Count; i < n; i++) {
- BoneData bone = bonesItems[i];
+ var bones = this.bones.Items;
+ for (int i = 0, n = this.bones.Count; i < n; i++) {
+ BoneData bone = bones[i];
if (bone.name == boneName) return bone;
}
return null;
@@ -111,10 +114,9 @@ namespace Spine {
/// -1 if the bone was not found.
public int FindBoneIndex (string boneName) {
if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
- var bones = this.bones;
- var bonesItems = bones.Items;
- for (int i = 0, n = bones.Count; i < n; i++)
- if (bonesItems[i].name == boneName) return i;
+ var bones = this.bones.Items;
+ for (int i = 0, n = this.bones.Count; i < n; i++)
+ if (bones[i].name == boneName) return i;
return -1;
}
@@ -123,9 +125,9 @@ namespace Spine {
/// May be null.
public SlotData FindSlot (string slotName) {
if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
- ExposedList slots = this.slots;
- for (int i = 0, n = slots.Count; i < n; i++) {
- SlotData slot = slots.Items[i];
+ var slots = this.slots.Items;
+ for (int i = 0, n = this.slots.Count; i < n; i++) {
+ SlotData slot = slots[i];
if (slot.name == slotName) return slot;
}
return null;
@@ -165,9 +167,9 @@ namespace Spine {
/// May be null.
public Animation FindAnimation (string animationName) {
if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null.");
- ExposedList animations = this.animations;
- for (int i = 0, n = animations.Count; i < n; i++) {
- Animation animation = animations.Items[i];
+ var animations = this.animations.Items;
+ for (int i = 0, n = this.animations.Count; i < n; i++) {
+ Animation animation = animations[i];
if (animation.name == animationName) return animation;
}
return null;
@@ -178,9 +180,9 @@ namespace Spine {
/// May be null.
public IkConstraintData FindIkConstraint (string constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
- ExposedList ikConstraints = this.ikConstraints;
- for (int i = 0, n = ikConstraints.Count; i < n; i++) {
- IkConstraintData ikConstraint = ikConstraints.Items[i];
+ var ikConstraints = this.ikConstraints.Items;
+ for (int i = 0, n = this.ikConstraints.Count; i < n; i++) {
+ IkConstraintData ikConstraint = ikConstraints[i];
if (ikConstraint.name == constraintName) return ikConstraint;
}
return null;
@@ -191,9 +193,9 @@ namespace Spine {
/// May be null.
public TransformConstraintData FindTransformConstraint (string constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
- ExposedList transformConstraints = this.transformConstraints;
- for (int i = 0, n = transformConstraints.Count; i < n; i++) {
- TransformConstraintData transformConstraint = transformConstraints.Items[i];
+ var transformConstraints = this.transformConstraints.Items;
+ for (int i = 0, n = this.transformConstraints.Count; i < n; i++) {
+ TransformConstraintData transformConstraint = transformConstraints[i];
if (transformConstraint.name == constraintName) return transformConstraint;
}
return null;
@@ -204,9 +206,9 @@ namespace Spine {
/// May be null.
public PathConstraintData FindPathConstraint (string constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
- ExposedList pathConstraints = this.pathConstraints;
- for (int i = 0, n = pathConstraints.Count; i < n; i++) {
- PathConstraintData constraint = pathConstraints.Items[i];
+ var pathConstraints = this.pathConstraints.Items;
+ for (int i = 0, n = this.pathConstraints.Count; i < n; i++) {
+ PathConstraintData constraint = pathConstraints[i];
if (constraint.name.Equals(constraintName)) return constraint;
}
return null;
diff --git a/spine-csharp/src/SkeletonJson.cs b/spine-csharp/src/SkeletonJson.cs
index de7f2367e..628258311 100644
--- a/spine-csharp/src/SkeletonJson.cs
+++ b/spine-csharp/src/SkeletonJson.cs
@@ -41,23 +41,27 @@ using Windows.Storage;
#endif
namespace Spine {
- public class SkeletonJson {
- public float Scale { get; set; }
- private AttachmentLoader attachmentLoader;
- private List linkedMeshes = new List();
+ ///
+ /// Loads skeleton data in the Spine JSON format.
+ ///
+ /// JSON is human readable but the binary format is much smaller on disk and faster to load. See .
+ ///
+ /// See Spine JSON format and
+ /// JSON and binary data in the Spine
+ /// Runtimes Guide.
+ ///
+ public class SkeletonJson : SkeletonLoader {
+
+ public SkeletonJson (AttachmentLoader attachmentLoader)
+ : base(attachmentLoader) {
+ }
public SkeletonJson (params Atlas[] atlasArray)
- : this(new AtlasAttachmentLoader(atlasArray)) {
+ : base(atlasArray) {
}
- public SkeletonJson (AttachmentLoader attachmentLoader) {
- if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null.");
- this.attachmentLoader = attachmentLoader;
- Scale = 1;
- }
-
- #if !IS_UNITY && WINDOWS_STOREAPP
+#if !IS_UNITY && WINDOWS_STOREAPP
private async Task ReadFile(string path) {
var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false);
@@ -68,11 +72,11 @@ namespace Spine {
}
}
- public SkeletonData ReadSkeletonData (string path) {
+ public override SkeletonData ReadSkeletonData (string path) {
return this.ReadFile(path).Result;
}
- #else
- public SkeletonData ReadSkeletonData (string path) {
+#else
+ public override SkeletonData ReadSkeletonData (string path) {
#if WINDOWS_PHONE
using (var reader = new StreamReader(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) {
#else
@@ -88,7 +92,7 @@ namespace Spine {
public SkeletonData ReadSkeletonData (TextReader reader) {
if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null.");
- float scale = this.Scale;
+ float scale = this.scale;
var skeletonData = new SkeletonData();
var root = Json.Deserialize(reader) as Dictionary;
@@ -99,8 +103,6 @@ namespace Spine {
var skeletonMap = (Dictionary)root["skeleton"];
skeletonData.hash = (string)skeletonMap["hash"];
skeletonData.version = (string)skeletonMap["spine"];
- if ("3.8.75" == skeletonData.version)
- throw new Exception("Unsupported skeleton data, please export with a newer version of Spine.");
skeletonData.x = GetFloat(skeletonMap, "x", 0);
skeletonData.y = GetFloat(skeletonMap, "y", 0);
skeletonData.width = GetFloat(skeletonMap, "width", 0);
@@ -283,6 +285,7 @@ namespace Spine {
skin.bones.Add(bone);
}
}
+ skin.bones.TrimExcess();
if (skinMap.ContainsKey("ik")) {
foreach (string entryName in (List