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)skinMap["ik"]) { IkConstraintData constraint = skeletonData.FindIkConstraint(entryName); @@ -304,6 +307,7 @@ namespace Spine { skin.constraints.Add(constraint); } } + skin.constraints.TrimExcess(); if (skinMap.ContainsKey("attachments")) { foreach (KeyValuePair slotEntry in (Dictionary)skinMap["attachments"]) { int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key); @@ -358,7 +362,7 @@ namespace Spine { try { ReadAnimation((Dictionary)entry.Value, entry.Key, skeletonData); } catch (Exception e) { - throw new Exception("Error reading animation: " + entry.Key, e); + throw new Exception("Error reading animation: " + entry.Key + "\n" + e.Message, e); } } } @@ -373,7 +377,7 @@ namespace Spine { } private Attachment ReadAttachment (Dictionary map, Skin skin, int slotIndex, string name, SkeletonData skeletonData) { - float scale = this.Scale; + float scale = this.scale; name = GetString(map, "name", name); var typeName = GetString(map, "type", "region"); @@ -438,7 +442,7 @@ namespace Spine { mesh.regionUVs = uvs; mesh.UpdateUVs(); - if (map.ContainsKey("hull")) mesh.HullLength = GetInt(map, "hull", 0) * 2; + if (map.ContainsKey("hull")) mesh.HullLength = GetInt(map, "hull", 0) << 1; if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); return mesh; } @@ -505,7 +509,7 @@ namespace Spine { for (int i = 0, n = vertices.Length; i < n;) { int boneCount = (int)vertices[i++]; bones.Add(boneCount); - for (int nn = i + boneCount * 4; i < nn; i += 4) { + for (int nn = i + (boneCount << 2); i < nn; i += 4) { bones.Add((int)vertices[i]); weights.Add(vertices[i + 1] * this.Scale); weights.Add(vertices[i + 2] * this.Scale); @@ -517,9 +521,8 @@ namespace Spine { } private void ReadAnimation (Dictionary map, string name, SkeletonData skeletonData) { - var scale = this.Scale; + var scale = this.scale; var timelines = new ExposedList(); - float duration = 0; // Slot timelines. if (map.ContainsKey("slots")) { @@ -529,50 +532,117 @@ namespace Spine { var timelineMap = (Dictionary)entry.Value; foreach (KeyValuePair timelineEntry in timelineMap) { var values = (List)timelineEntry.Value; + if (values.Count == 0) continue; var timelineName = (string)timelineEntry.Key; if (timelineName == "attachment") { - var timeline = new AttachmentTimeline(values.Count); - timeline.slotIndex = slotIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = GetFloat(valueMap, "time", 0); - timeline.SetFrame(frameIndex++, time, (string)valueMap["name"]); + var timeline = new AttachmentTimeline(values.Count, slotIndex); + int frame = 0; + foreach (Dictionary keyMap in values) { + timeline.SetFrame(frame++, GetFloat(keyMap, "time", 0), (string)keyMap["name"]); } timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); } else if (timelineName == "color") { - var timeline = new ColorTimeline(values.Count); - timeline.slotIndex = slotIndex; + var timeline = new ColorTimeline(values.Count, values.Count << 2, slotIndex); - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = GetFloat(valueMap, "time", 0); - string c = (string)valueMap["color"]; - timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3)); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; + var keyMapEnumerator = values.GetEnumerator(); + keyMapEnumerator.MoveNext(); + var keyMap = (Dictionary)keyMapEnumerator.Current; + float time = GetFloat(keyMap, "time", 0); + string color = (string)keyMap["color"]; + float r = ToColor(color, 0); + float g = ToColor(color, 1); + float b = ToColor(color, 2); + float a = ToColor(color, 3); + for (int frame = 0, bezier = 0;; frame++) { + timeline.SetFrame(frame, time, r, g, b, a); + bool hasNext = keyMapEnumerator.MoveNext(); + if (!hasNext) { + timeline.Shrink(bezier); + break; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + + float time2 = GetFloat(nextMap, "time", 0); + color = (string)nextMap["color"]; + float nr = ToColor(color, 0); + float ng = ToColor(color, 1); + float nb = ToColor(color, 2); + float na = ToColor(color, 3); + + if (keyMap.ContainsKey("curve")) { + object curve = keyMap["curve"]; + bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 3, time, time2, a, na, 1); + } + time = time2; + r = nr; + g = ng; + b = nb; + a = na; + keyMap = nextMap; } timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]); } else if (timelineName == "twoColor") { - var timeline = new TwoColorTimeline(values.Count); - timeline.slotIndex = slotIndex; + var timeline = new TwoColorTimeline(values.Count, values.Count * 7, slotIndex); - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = GetFloat(valueMap, "time", 0); - string light = (string)valueMap["light"]; - string dark = (string)valueMap["dark"]; - timeline.SetFrame(frameIndex, time, ToColor(light, 0), ToColor(light, 1), ToColor(light, 2), ToColor(light, 3), - ToColor(dark, 0, 6), ToColor(dark, 1, 6), ToColor(dark, 2, 6)); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; + var keyMapEnumerator = values.GetEnumerator(); + keyMapEnumerator.MoveNext(); + var keyMap = (Dictionary)keyMapEnumerator.Current; + float time = GetFloat(keyMap, "time", 0); + string color = (string)keyMap["light"]; + float r = ToColor(color, 0); + float g = ToColor(color, 1); + float b = ToColor(color, 2); + float a = ToColor(color, 3); + color = (string)keyMap["dark"]; + float r2 = ToColor(color, 0); + float g2 = ToColor(color, 1); + float b2 = ToColor(color, 2); + for (int frame = 0, bezier = 0; ; frame++) { + timeline.SetFrame(frame, time, r, g, b, a, r2, g2, b2); + bool hasNext = keyMapEnumerator.MoveNext(); + if (!hasNext) { + timeline.Shrink(bezier); + break; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + + float time2 = GetFloat(nextMap, "time", 0); + color = (string)nextMap["light"]; + float nr = ToColor(color, 0); + float ng = ToColor(color, 1); + float nb = ToColor(color, 2); + float na = ToColor(color, 3); + color = (string)nextMap["dark"]; + float nr2 = ToColor(color, 0); + float ng2 = ToColor(color, 1); + float nb2 = ToColor(color, 2); + + if (keyMap.ContainsKey("curve")) { + object curve = keyMap["curve"]; + bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 3, time, time2, a, na, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 4, time, time2, r2, nr2, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 5, time, time2, g2, ng2, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 6, time, time2, b2, nb2, 1); + } + time = time2; + r = nr; + g = ng; + b = nb; + a = na; + r2 = nr2; + g2 = ng2; + b2 = nb2; + keyMap = nextMap; } timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TwoColorTimeline.ENTRIES]); } else throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); @@ -589,47 +659,23 @@ namespace Spine { var timelineMap = (Dictionary)entry.Value; foreach (KeyValuePair timelineEntry in timelineMap) { var values = (List)timelineEntry.Value; + var keyMapEnumerator = values.GetEnumerator(); + bool hasNext = keyMapEnumerator.MoveNext(); + if (!hasNext) continue; var timelineName = (string)timelineEntry.Key; - if (timelineName == "rotate") { - var timeline = new RotateTimeline(values.Count); - timeline.boneIndex = boneIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, "angle", 0)); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * RotateTimeline.ENTRIES]); - - } else if (timelineName == "translate" || timelineName == "scale" || timelineName == "shear") { - TranslateTimeline timeline; - float timelineScale = 1, defaultValue = 0; - if (timelineName == "scale") { - timeline = new ScaleTimeline(values.Count); - defaultValue = 1; - } - else if (timelineName == "shear") - timeline = new ShearTimeline(values.Count); - else { - timeline = new TranslateTimeline(values.Count); - timelineScale = scale; - } - timeline.boneIndex = boneIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = GetFloat(valueMap, "time", 0); - float x = GetFloat(valueMap, "x", defaultValue); - float y = GetFloat(valueMap, "y", defaultValue); - timeline.SetFrame(frameIndex, time, x * timelineScale, y * timelineScale); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TranslateTimeline.ENTRIES]); - + if (timelineName == "rotate") + timelines.Add(ReadTimeline(ref keyMapEnumerator, new RotateTimeline(values.Count, values.Count, boneIndex), 0, 1)); + else if (timelineName == "translate") { + TranslateTimeline timeline = new TranslateTimeline(values.Count, values.Count << 1, boneIndex); + timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "x", "y", 0, scale)); + } + else if (timelineName == "scale") { + ScaleTimeline timeline = new ScaleTimeline(values.Count, values.Count << 1, boneIndex); + timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "x", "y", 1, 1)); + } + else if (timelineName == "shear") { + ShearTimeline timeline = new ShearTimeline(values.Count, values.Count << 1, boneIndex); + timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "x", "y", 0, 1)); } else throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); } @@ -638,40 +684,82 @@ namespace Spine { // IK constraint timelines. if (map.ContainsKey("ik")) { - foreach (KeyValuePair constraintMap in (Dictionary)map["ik"]) { - IkConstraintData constraint = skeletonData.FindIkConstraint(constraintMap.Key); - var values = (List)constraintMap.Value; - var timeline = new IkConstraintTimeline(values.Count); - timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint); - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, "mix", 1), - GetFloat(valueMap, "softness", 0) * scale, GetBoolean(valueMap, "bendPositive", true) ? 1 : -1, - GetBoolean(valueMap, "compress", false), GetBoolean(valueMap, "stretch", false)); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; + foreach (KeyValuePair timelineMap in (Dictionary)map["ik"]) { + var timelineMapValues = (List)timelineMap.Value; + var keyMapEnumerator = timelineMapValues.GetEnumerator(); + bool hasNext = keyMapEnumerator.MoveNext(); + if (!hasNext) continue; + var keyMap = (Dictionary)keyMapEnumerator.Current; + IkConstraintData constraint = skeletonData.FindIkConstraint(timelineMap.Key); + IkConstraintTimeline timeline = new IkConstraintTimeline(timelineMapValues.Count, timelineMapValues.Count << 1, + skeletonData.IkConstraints.IndexOf(constraint)); + float time = GetFloat(keyMap, "time", 0); + float mix = GetFloat(keyMap, "mix", 1), softness = GetFloat(keyMap, "softness", 0) * scale; + for (int frame = 0, bezier = 0; ; frame++) { + timeline.SetFrame(frame, time, mix, softness, GetBoolean(keyMap, "bendPositive", true) ? 1 : -1, + GetBoolean(keyMap, "compress", false), GetBoolean(keyMap, "stretch", false)); + hasNext = keyMapEnumerator.MoveNext(); + if (!hasNext) { + timeline.Shrink(bezier); + break; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + float time2 = GetFloat(nextMap, "time", 0); + float mix2 = GetFloat(nextMap, "mix", 1), softness2 = GetFloat(nextMap, "softness", 0) * scale; + if (keyMap.ContainsKey("curve")) { + object curve = keyMap["curve"]; + bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, mix, mix2, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, softness, softness2, scale); + } + time = time2; + mix = mix2; + softness = softness2; + keyMap = nextMap; } timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * IkConstraintTimeline.ENTRIES]); } } // Transform constraint timelines. if (map.ContainsKey("transform")) { - foreach (KeyValuePair constraintMap in (Dictionary)map["transform"]) { - TransformConstraintData constraint = skeletonData.FindTransformConstraint(constraintMap.Key); - var values = (List)constraintMap.Value; - var timeline = new TransformConstraintTimeline(values.Count); - timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint); - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, "rotateMix", 1), - GetFloat(valueMap, "translateMix", 1), GetFloat(valueMap, "scaleMix", 1), GetFloat(valueMap, "shearMix", 1)); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; + foreach (KeyValuePair timelineMap in (Dictionary)map["transform"]) { + var timelineMapValues = (List)timelineMap.Value; + var keyMapEnumerator = timelineMapValues.GetEnumerator(); + bool hasNext = keyMapEnumerator.MoveNext(); + if (!hasNext) continue; + var keyMap = (Dictionary)keyMapEnumerator.Current; + TransformConstraintData constraint = skeletonData.FindTransformConstraint(timelineMap.Key); + TransformConstraintTimeline timeline = new TransformConstraintTimeline(timelineMapValues.Count, timelineMapValues.Count << 2, + skeletonData.TransformConstraints.IndexOf(constraint)); + float time = GetFloat(keyMap, "time", 0); + float rotateMix = GetFloat(keyMap, "rotateMix", 1), translateMix = GetFloat(keyMap, "translateMix", 1); + float scaleMix = GetFloat(keyMap, "scaleMix", 1), shearMix = GetFloat(keyMap, "shearMix", 1); + for (int frame = 0, bezier = 0; ; frame++) { + timeline.SetFrame(frame, time, rotateMix, translateMix, scaleMix, shearMix); + hasNext = keyMapEnumerator.MoveNext(); + if (!hasNext) { + timeline.Shrink(bezier); + break; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + float time2 = GetFloat(nextMap, "time", 0); + float rotateMix2 = GetFloat(nextMap, "rotateMix", 1), translateMix2 = GetFloat(nextMap, "translateMix", 1); + float scaleMix2 = GetFloat(nextMap, "scaleMix", 1), shearMix2 = GetFloat(nextMap, "shearMix", 1); + if (keyMap.ContainsKey("curve")) { + object curve = keyMap["curve"]; + bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, rotateMix, rotateMix2, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, translateMix, translateMix2, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, scaleMix, scaleMix2, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 3, time, time2, shearMix, shearMix2, 1); + } + time = time2; + rotateMix = rotateMix2; + translateMix = translateMix2; + scaleMix = scaleMix2; + shearMix = shearMix2; + keyMap = nextMap; } timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TransformConstraintTimeline.ENTRIES]); } } @@ -684,40 +772,22 @@ namespace Spine { var timelineMap = (Dictionary)constraintMap.Value; foreach (KeyValuePair timelineEntry in timelineMap) { var values = (List)timelineEntry.Value; + var keyMapEnumerator = values.GetEnumerator(); + bool hasNext = keyMapEnumerator.MoveNext(); + if (!hasNext) continue; var timelineName = (string)timelineEntry.Key; - if (timelineName == "position" || timelineName == "spacing") { - PathConstraintPositionTimeline timeline; - float timelineScale = 1; - if (timelineName == "spacing") { - timeline = new PathConstraintSpacingTimeline(values.Count); - if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; - } - else { - timeline = new PathConstraintPositionTimeline(values.Count); - if (data.positionMode == PositionMode.Fixed) timelineScale = scale; - } - timeline.pathConstraintIndex = index; - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, timelineName, 0) * timelineScale); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintPositionTimeline.ENTRIES]); + if (timelineName == "position") { + CurveTimeline1 timeline = new PathConstraintPositionTimeline(values.Count, values.Count, index); + timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, 0, data.positionMode == PositionMode.Fixed ? scale : 1)); + } + else if (timelineName == "spacing") { + CurveTimeline1 timeline = new PathConstraintSpacingTimeline(values.Count, values.Count, index); + timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, 0, + data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed ? scale : 1)); } else if (timelineName == "mix") { - PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(values.Count); - timeline.pathConstraintIndex = index; - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, "rotateMix", 1), - GetFloat(valueMap, "translateMix", 1)); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintMixTimeline.ENTRIES]); + CurveTimeline2 timeline = new PathConstraintMixTimeline(values.Count, values.Count << 1, index); + timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "rotateMix", "translateMix", 1, 1)); } } } @@ -731,26 +801,26 @@ namespace Spine { int slotIndex = skeletonData.FindSlotIndex(slotMap.Key); if (slotIndex == -1) throw new Exception("Slot not found: " + slotMap.Key); foreach (KeyValuePair timelineMap in (Dictionary)slotMap.Value) { - var values = (List)timelineMap.Value; + var timelineMapValues = (List)timelineMap.Value; + var keyMapEnumerator = timelineMapValues.GetEnumerator(); + bool hasNext = keyMapEnumerator.MoveNext(); + if (!hasNext) continue; + var keyMap = (Dictionary)keyMapEnumerator.Current; VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, timelineMap.Key); if (attachment == null) throw new Exception("Deform attachment not found: " + timelineMap.Key); bool weighted = attachment.bones != null; float[] vertices = attachment.vertices; - int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length; - - var timeline = new DeformTimeline(values.Count); - timeline.slotIndex = slotIndex; - timeline.attachment = attachment; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { + int deformLength = weighted ? (vertices.Length / 3) << 1 : vertices.Length; + DeformTimeline timeline = new DeformTimeline(timelineMapValues.Count, timelineMapValues.Count, slotIndex, attachment); + float time = GetFloat(keyMap, "time", 0); + for (int frame = 0, bezier = 0; ; frame++) { float[] deform; - if (!valueMap.ContainsKey("vertices")) { + if (!keyMap.ContainsKey("vertices")) { deform = weighted ? new float[deformLength] : vertices; } else { deform = new float[deformLength]; - int start = GetInt(valueMap, "offset", 0); - float[] verticesValue = GetFloatArray(valueMap, "vertices", 1); + int start = GetInt(keyMap, "offset", 0); + float[] verticesValue = GetFloatArray(keyMap, "vertices", 1); Array.Copy(verticesValue, 0, deform, start, verticesValue.Length); if (scale != 1) { for (int i = start, n = i + verticesValue.Length; i < n; i++) @@ -763,12 +833,22 @@ namespace Spine { } } - timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), deform); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; + timeline.SetFrame(frame, time, deform); + hasNext = keyMapEnumerator.MoveNext(); + if (!hasNext) { + timeline.Shrink(bezier); + break; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + float time2 = GetFloat(nextMap, "time", 0); + if (keyMap.ContainsKey("curve")) { + object curve = keyMap["curve"]; + bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, 0, 1, 1); + } + time = time2; + keyMap = nextMap; } timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); } } } @@ -779,7 +859,7 @@ namespace Spine { var values = (List)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"]; var timeline = new DrawOrderTimeline(values.Count); int slotCount = skeletonData.slots.Count; - int frameIndex = 0; + int frame = 0; foreach (Dictionary drawOrderMap in values) { int[] drawOrder = null; if (drawOrderMap.ContainsKey("offsets")) { @@ -806,17 +886,17 @@ namespace Spine { for (int i = slotCount - 1; i >= 0; i--) if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; } - timeline.SetFrame(frameIndex++, GetFloat(drawOrderMap, "time", 0), drawOrder); + timeline.SetFrame(frame, GetFloat(drawOrderMap, "time", 0), drawOrder); + ++frame; } timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); } // Event timeline. if (map.ContainsKey("events")) { var eventsMap = (List)map["events"]; var timeline = new EventTimeline(eventsMap.Count); - int frameIndex = 0; + int frame = 0; foreach (Dictionary eventMap in eventsMap) { EventData eventData = skeletonData.FindEvent((string)eventMap["name"]); if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]); @@ -829,39 +909,97 @@ namespace Spine { e.volume = GetFloat(eventMap, "volume", eventData.Volume); e.balance = GetFloat(eventMap, "balance", eventData.Balance); } - timeline.SetFrame(frameIndex++, e); + timeline.SetFrame(frame, e); + ++frame; } timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 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); skeletonData.animations.Add(new Animation(name, timelines, duration)); } - static void ReadCurve (Dictionary valueMap, CurveTimeline timeline, int frameIndex) { - if (!valueMap.ContainsKey("curve")) - return; - Object curveObject = valueMap["curve"]; - if (curveObject is string) - timeline.SetStepped(frameIndex); - else - timeline.SetCurve(frameIndex, (float)curveObject, GetFloat(valueMap, "c2", 0), GetFloat(valueMap, "c3", 1), GetFloat(valueMap, "c4", 1)); + static Timeline ReadTimeline (ref List.Enumerator keyMapEnumerator, CurveTimeline1 timeline, float defaultValue, float scale) { + var keyMap = (Dictionary)keyMapEnumerator.Current; + float time = GetFloat(keyMap, "time", 0); + float value = GetFloat(keyMap, "value", defaultValue) * scale; + int bezier = 0; + for (int frame = 0; ; frame++) { + timeline.SetFrame(frame, time, value); + bool hasNext = keyMapEnumerator.MoveNext(); + if (!hasNext) + break; + var nextMap = (Dictionary)keyMapEnumerator.Current; + float time2 = GetFloat(nextMap, "time", 0); + float value2 = GetFloat(nextMap, "value", defaultValue) * scale; + if (keyMap.ContainsKey("curve")) { + object curve = keyMap["curve"]; + bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, value, value2, scale); + } + time = time2; + value = value2; + keyMap = nextMap; + } + timeline.Shrink(bezier); + return timeline; } - internal class LinkedMesh { - internal string parent, skin; - internal int slotIndex; - internal MeshAttachment mesh; - internal bool inheritDeform; + static Timeline ReadTimeline (ref List.Enumerator keyMapEnumerator, CurveTimeline2 timeline, String name1, String name2, float defaultValue, + float scale) { - public LinkedMesh (MeshAttachment mesh, string skin, int slotIndex, string parent, bool inheritDeform) { - this.mesh = mesh; - this.skin = skin; - this.slotIndex = slotIndex; - this.parent = parent; - this.inheritDeform = inheritDeform; + var keyMap = (Dictionary)keyMapEnumerator.Current; + float time = GetFloat(keyMap, "time", 0); + float value1 = GetFloat(keyMap, name1, defaultValue) * scale, value2 = GetFloat(keyMap, name2, defaultValue) * scale; + int bezier = 0; + for (int frame = 0; ; frame++) { + timeline.SetFrame(frame, time, value1, value2); + bool hasNext = keyMapEnumerator.MoveNext(); + if (!hasNext) + break; + var nextMap = (Dictionary)keyMapEnumerator.Current; + float time2 = GetFloat(nextMap, "time", 0); + float nvalue1 = GetFloat(nextMap, name1, defaultValue) * scale, nvalue2 = GetFloat(nextMap, name2, defaultValue) * scale; + if (keyMap.ContainsKey("curve")) { + object curve = keyMap["curve"]; + bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, value1, nvalue1, scale); + bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, value2, nvalue2, scale); + } + time = time2; + value1 = nvalue1; + value2 = nvalue2; + keyMap = nextMap; } + timeline.Shrink(bezier); + return timeline; + } + + static int ReadCurve (object curve, CurveTimeline timeline, int bezier, int frame, int value, float time1, float time2, + float value1, float value2, float scale) { + + if (curve is string) { + if (value != 0) timeline.SetStepped(frame); + } + else { + var curveValues = (List)curve; + int index = value << 2; + float cx1 = (float)curveValues[index]; + ++index; + float cy1 = ((float)curveValues[index]) * scale; + ++index; + float cx2 = (float)curveValues[index]; + ++index; + float cy2 = (float)curveValues[index] * scale; + SetBezier(timeline, frame, value, bezier++, time1, value1, cx1, cy1, cx2, cy2, time2, value2); + } + return bezier; + } + + static void SetBezier (CurveTimeline timeline, int frame, int value, int bezier, float time1, float value1, float cx1, float cy1, + float cx2, float cy2, float time2, float value2) { + timeline.SetBezier(bezier, frame, value, time1, value1, cx1, cy1, cx2, cy2, time2, value2); } static float[] GetFloatArray(Dictionary map, string name, float scale) { diff --git a/spine-csharp/src/SkeletonLoader.cs b/spine-csharp/src/SkeletonLoader.cs new file mode 100644 index 000000000..f6cbffdcd --- /dev/null +++ b/spine-csharp/src/SkeletonLoader.cs @@ -0,0 +1,92 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +using System; +using System.IO; +using System.Collections.Generic; + +namespace Spine { + + /// + /// Base class for loading skeleton data from a file. + /// + /// SeeJSON and binary data in the + /// Spine Runtimes Guide. + /// + public abstract class SkeletonLoader { + protected readonly AttachmentLoader attachmentLoader; + protected float scale = 1; + protected readonly List linkedMeshes = new List(); + + /// Creates a skeleton loader that loads attachments using an with the specified atlas. + /// + public SkeletonLoader (params Atlas[] atlasArray) { + attachmentLoader = new AtlasAttachmentLoader(atlasArray); + } + + /// Creates a skeleton loader that loads attachments using the specified attachment loader. + /// See Loading skeleton data in the + /// Spine Runtimes Guide. + public SkeletonLoader (AttachmentLoader attachmentLoader) { + if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null."); + this.attachmentLoader = attachmentLoader; + } + + /// Scales bone positions, image sizes, and translations as they are loaded. This allows different size images to be used at + /// runtime than were used in Spine. + /// + /// See Scaling in the Spine Runtimes Guide. + /// + public float Scale { + get { return scale; } + set { + if (scale == 0) throw new ArgumentNullException("scale", "scale cannot be 0."); + this.scale = value; + } + } + + public abstract SkeletonData ReadSkeletonData (string path); + + protected class LinkedMesh { + internal string parent, skin; + internal int slotIndex; + internal MeshAttachment mesh; + internal bool inheritDeform; + + public LinkedMesh (MeshAttachment mesh, string skin, int slotIndex, string parent, bool inheritDeform) { + this.mesh = mesh; + this.skin = skin; + this.slotIndex = slotIndex; + this.parent = parent; + this.inheritDeform = inheritDeform; + } + } + + } +} diff --git a/spine-csharp/src/Skin.cs b/spine-csharp/src/Skin.cs index 1ee9fac7a..6e06b98ec 100644 --- a/spine-csharp/src/Skin.cs +++ b/spine-csharp/src/Skin.cs @@ -39,6 +39,8 @@ namespace Spine { /// public class Skin { internal string name; + // Difference to reference implementation: using Dictionary instead of HashSet. + // Reason is that there is no efficient way to replace or access an already added element, losing any benefits. private Dictionary attachments = new Dictionary(SkinKeyComparer.Instance); internal readonly ExposedList bones = new ExposedList(); internal readonly ExposedList constraints = new ExposedList(); @@ -58,7 +60,6 @@ namespace Spine { /// If the name already exists for the slot, the previous value is replaced. public void SetAttachment (int slotIndex, string name, Attachment attachment) { if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null."); - if (slotIndex < 0) throw new ArgumentNullException("slotIndex", "slotIndex must be >= 0."); attachments[new SkinKey(slotIndex, name)] = new SkinEntry(slotIndex, name, attachment); } @@ -104,13 +105,14 @@ namespace Spine { /// Removes the attachment in the skin for the specified slot index and name, if any. public void RemoveAttachment (int slotIndex, string name) { - if (slotIndex < 0) throw new ArgumentOutOfRangeException("slotIndex", "slotIndex must be >= 0"); attachments.Remove(new SkinKey(slotIndex, name)); } /// Returns all attachments in this skin for the specified slot index. /// The target slotIndex. To find the slot index, use or public void GetAttachments (int slotIndex, List attachments) { + if (slotIndex < 0) throw new ArgumentException("slotIndex must be >= 0."); + if (attachments == null) throw new ArgumentNullException("attachments", "attachments cannot be null."); foreach (var item in this.attachments) { SkinEntry entry = item.Value; if (entry.slotIndex == slotIndex) attachments.Add(entry); @@ -176,10 +178,14 @@ namespace Spine { private struct SkinKey { internal readonly int slotIndex; internal readonly string name; + internal readonly int hashCode; public SkinKey (int slotIndex, string name) { + if (slotIndex < 0) throw new ArgumentException("slotIndex must be >= 0."); + if (name == null) throw new ArgumentNullException("name", "name cannot be null"); this.slotIndex = slotIndex; this.name = name; + this.hashCode = name.GetHashCode() + slotIndex * 37; } } @@ -191,7 +197,7 @@ namespace Spine { } int IEqualityComparer.GetHashCode (SkinKey e) { - return e.name.GetHashCode() + e.slotIndex * 37; + return e.hashCode; } } } diff --git a/spine-csharp/src/Slot.cs b/spine-csharp/src/Slot.cs index c7e45a886..c050f1b32 100644 --- a/spine-csharp/src/Slot.cs +++ b/spine-csharp/src/Slot.cs @@ -32,7 +32,7 @@ using System; namespace Spine { /// - /// Stores a slot's current pose. Slots organize attachments for {@link Skeleton#drawOrder} purposes and provide a place to store + /// Stores a slot's current pose. Slots organize attachments for purposes and provide a place to store /// state for an attachment.State cannot be stored in an attachment itself because attachments are stateless and may be shared /// across multiple skeletons. /// diff --git a/spine-csharp/src/TransformConstraint.cs b/spine-csharp/src/TransformConstraint.cs index 25761029c..4305e5e09 100644 --- a/spine-csharp/src/TransformConstraint.cs +++ b/spine-csharp/src/TransformConstraint.cs @@ -76,12 +76,8 @@ namespace Spine { shearMix = constraint.shearMix; } - /// Applies the constraint to the constrained bones. - public void Apply () { - Update(); - } - public void Update () { + if (rotateMix == 0 && translateMix == 0 && scaleMix == 0 && shearMix == 0) return; if (data.local) { if (data.relative) ApplyRelativeLocal(); @@ -101,10 +97,9 @@ namespace Spine { float ta = target.a, tb = target.b, tc = target.c, td = target.d; float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; - var bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bones.Items[i]; - bool modified = false; + var bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) { + Bone bone = bones[i]; if (rotateMix != 0) { float a = bone.a, b = bone.b, c = bone.c, d = bone.d; @@ -118,7 +113,6 @@ namespace Spine { bone.b = cos * b - sin * d; bone.c = sin * a + cos * c; bone.d = sin * b + cos * d; - modified = true; } if (translateMix != 0) { @@ -126,7 +120,6 @@ namespace Spine { target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); bone.worldX += (tx - bone.worldX) * translateMix; bone.worldY += (ty - bone.worldY) * translateMix; - modified = true; } if (scaleMix > 0) { @@ -138,7 +131,6 @@ namespace Spine { if (s != 0) s = (s + ((float)Math.Sqrt(tb * tb + td * td) - s + data.offsetScaleY) * scaleMix) / s; bone.b *= s; bone.d *= s; - modified = true; } if (shearMix > 0) { @@ -152,10 +144,9 @@ namespace Spine { float s = (float)Math.Sqrt(b * b + d * d); bone.b = MathUtils.Cos(r) * s; bone.d = MathUtils.Sin(r) * s; - modified = true; } - if (modified) bone.appliedValid = false; + bone.appliedValid = false; } } @@ -165,10 +156,9 @@ namespace Spine { float ta = target.a, tb = target.b, tc = target.c, td = target.d; float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; - var bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bones.Items[i]; - bool modified = false; + var bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) { + Bone bone = bones[i]; if (rotateMix != 0) { float a = bone.a, b = bone.b, c = bone.c, d = bone.d; @@ -182,7 +172,6 @@ namespace Spine { bone.b = cos * b - sin * d; bone.c = sin * a + cos * c; bone.d = sin * b + cos * d; - modified = true; } if (translateMix != 0) { @@ -190,7 +179,6 @@ namespace Spine { target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); bone.worldX += tx * translateMix; bone.worldY += ty * translateMix; - modified = true; } if (scaleMix > 0) { @@ -200,7 +188,6 @@ namespace Spine { s = ((float)Math.Sqrt(tb * tb + td * td) - 1 + data.offsetScaleY) * scaleMix + 1; bone.b *= s; bone.d *= s; - modified = true; } if (shearMix > 0) { @@ -213,10 +200,9 @@ namespace Spine { float s = (float)Math.Sqrt(b * b + d * d); bone.b = MathUtils.Cos(r) * s; bone.d = MathUtils.Sin(r) * s; - modified = true; } - if (modified) bone.appliedValid = false; + bone.appliedValid = false; } } @@ -224,9 +210,9 @@ namespace Spine { float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; Bone target = this.target; if (!target.appliedValid) target.UpdateAppliedTransform(); - var bonesItems = this.bones.Items; + var bones = this.bones.Items; for (int i = 0, n = this.bones.Count; i < n; i++) { - Bone bone = bonesItems[i]; + Bone bone = bones[i]; if (!bone.appliedValid) bone.UpdateAppliedTransform(); float rotation = bone.arotation; @@ -263,9 +249,9 @@ namespace Spine { float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; Bone target = this.target; if (!target.appliedValid) target.UpdateAppliedTransform(); - var bonesItems = this.bones.Items; + var bones = this.bones.Items; for (int i = 0, n = this.bones.Count; i < n; i++) { - Bone bone = bonesItems[i]; + Bone bone = bones[i]; if (!bone.appliedValid) bone.UpdateAppliedTransform(); float rotation = bone.arotation; diff --git a/spine-unity/Assets/Spine Examples/Scripts/MecanimAnimationMatchModifier/AnimationMatchModifierAsset.cs b/spine-unity/Assets/Spine Examples/Scripts/MecanimAnimationMatchModifier/AnimationMatchModifierAsset.cs index 275973f1c..9fed459fd 100644 --- a/spine-unity/Assets/Spine Examples/Scripts/MecanimAnimationMatchModifier/AnimationMatchModifierAsset.cs +++ b/spine-unity/Assets/Spine Examples/Scripts/MecanimAnimationMatchModifier/AnimationMatchModifierAsset.cs @@ -61,32 +61,35 @@ namespace Spine.Unity.Examples { // Build a reference collection of timelines to match // and a collection of dummy timelines that can be used to fill-in missing items. - var timelineDictionary = new Dictionary(); + var timelineDictionary = new Dictionary(); foreach (var animation in animations) { foreach (var timeline in animation.Timelines) { if (timeline is EventTimeline) continue; - int propertyID = timeline.PropertyId; - if (!timelineDictionary.ContainsKey(propertyID)) { - timelineDictionary.Add(propertyID, GetFillerTimeline(timeline, skeletonData)); + foreach (string propertyId in timeline.PropertyIds) { + if (!timelineDictionary.ContainsKey(propertyId)) { + timelineDictionary.Add(propertyId, GetFillerTimeline(timeline, skeletonData)); + } } } } - var idsToMatch = new List(timelineDictionary.Keys); + var idsToMatch = new List(timelineDictionary.Keys); // For each animation in the list, check for and add missing timelines. - var currentAnimationIDs = new HashSet(); + var currentAnimationIDs = new HashSet(); foreach (var animation in animations) { currentAnimationIDs.Clear(); foreach (var timeline in animation.Timelines) { if (timeline is EventTimeline) continue; - currentAnimationIDs.Add(timeline.PropertyId); + foreach (string propertyId in timeline.PropertyIds) { + currentAnimationIDs.Add(propertyId); + } } var animationTimelines = animation.Timelines; - foreach (int propertyID in idsToMatch) { - if (!currentAnimationIDs.Contains(propertyID)) - animationTimelines.Add(timelineDictionary[propertyID]); + foreach (string propertyId in idsToMatch) { + if (!currentAnimationIDs.Contains(propertyId)) + animationTimelines.Add(timelineDictionary[propertyId]); } } @@ -132,62 +135,52 @@ namespace Spine.Unity.Examples { } static RotateTimeline GetFillerTimeline (RotateTimeline timeline, SkeletonData skeletonData) { - var t = new RotateTimeline(1); - t.BoneIndex = timeline.BoneIndex; + var t = new RotateTimeline(1, 0, timeline.BoneIndex); t.SetFrame(0, 0, 0); return t; } static TranslateTimeline GetFillerTimeline (TranslateTimeline timeline, SkeletonData skeletonData) { - var t = new TranslateTimeline(1); - t.BoneIndex = timeline.BoneIndex; + var t = new TranslateTimeline(1, 0, timeline.BoneIndex); t.SetFrame(0, 0, 0, 0); return t; } static ScaleTimeline GetFillerTimeline (ScaleTimeline timeline, SkeletonData skeletonData) { - var t = new ScaleTimeline(1); - t.BoneIndex = timeline.BoneIndex; + var t = new ScaleTimeline(1, 0, timeline.BoneIndex); t.SetFrame(0, 0, 0, 0); return t; } static ShearTimeline GetFillerTimeline (ShearTimeline timeline, SkeletonData skeletonData) { - var t = new ShearTimeline(1); - t.BoneIndex = timeline.BoneIndex; + var t = new ShearTimeline(1, 0, timeline.BoneIndex); t.SetFrame(0, 0, 0, 0); return t; } static AttachmentTimeline GetFillerTimeline (AttachmentTimeline timeline, SkeletonData skeletonData) { - var t = new AttachmentTimeline(1); - t.SlotIndex = timeline.SlotIndex; + var t = new AttachmentTimeline(1, timeline.SlotIndex); var slotData = skeletonData.Slots.Items[t.SlotIndex]; t.SetFrame(0, 0, slotData.AttachmentName); return t; } static ColorTimeline GetFillerTimeline (ColorTimeline timeline, SkeletonData skeletonData) { - var t = new ColorTimeline(1); - t.SlotIndex = timeline.SlotIndex; + var t = new ColorTimeline(1, 0, timeline.SlotIndex); var slotData = skeletonData.Slots.Items[t.SlotIndex]; t.SetFrame(0, 0, slotData.R, slotData.G, slotData.B, slotData.A); return t; } static TwoColorTimeline GetFillerTimeline (TwoColorTimeline timeline, SkeletonData skeletonData) { - var t = new TwoColorTimeline(1); - t.SlotIndex = timeline.SlotIndex; + var t = new TwoColorTimeline(1, 0, timeline.SlotIndex); var slotData = skeletonData.Slots.Items[t.SlotIndex]; t.SetFrame(0, 0, slotData.R, slotData.G, slotData.B, slotData.A, slotData.R2, slotData.G2, slotData.B2); return t; } static DeformTimeline GetFillerTimeline (DeformTimeline timeline, SkeletonData skeletonData) { - var t = new DeformTimeline(1); - t.SlotIndex = timeline.SlotIndex; - t.Attachment = timeline.Attachment; - + var t = new DeformTimeline(1, 0, timeline.SlotIndex, timeline.Attachment); if (t.Attachment.IsWeighted()) { t.SetFrame(0, 0, new float[t.Attachment.Vertices.Length]); } else { @@ -204,35 +197,35 @@ namespace Spine.Unity.Examples { } static IkConstraintTimeline GetFillerTimeline (IkConstraintTimeline timeline, SkeletonData skeletonData) { - var t = new IkConstraintTimeline(1); + var t = new IkConstraintTimeline(1, 0, timeline.IkConstraintIndex); var ikConstraintData = skeletonData.IkConstraints.Items[timeline.IkConstraintIndex]; t.SetFrame(0, 0, ikConstraintData.Mix, ikConstraintData.Softness, ikConstraintData.BendDirection, ikConstraintData.Compress, ikConstraintData.Stretch); return t; } static TransformConstraintTimeline GetFillerTimeline (TransformConstraintTimeline timeline, SkeletonData skeletonData) { - var t = new TransformConstraintTimeline(1); + var t = new TransformConstraintTimeline(1, 0, timeline.TransformConstraintIndex); var data = skeletonData.TransformConstraints.Items[timeline.TransformConstraintIndex]; t.SetFrame(0, 0, data.RotateMix, data.TranslateMix, data.ScaleMix, data.ShearMix); return t; } static PathConstraintPositionTimeline GetFillerTimeline (PathConstraintPositionTimeline timeline, SkeletonData skeletonData) { - var t = new PathConstraintPositionTimeline(1); + var t = new PathConstraintPositionTimeline(1, 0, timeline.PathConstraintIndex); var data = skeletonData.PathConstraints.Items[timeline.PathConstraintIndex]; t.SetFrame(0, 0, data.Position); return t; } static PathConstraintSpacingTimeline GetFillerTimeline (PathConstraintSpacingTimeline timeline, SkeletonData skeletonData) { - var t = new PathConstraintSpacingTimeline(1); + var t = new PathConstraintSpacingTimeline(1, 0, timeline.PathConstraintIndex); var data = skeletonData.PathConstraints.Items[timeline.PathConstraintIndex]; t.SetFrame(0, 0, data.Spacing); return t; } static PathConstraintMixTimeline GetFillerTimeline (PathConstraintMixTimeline timeline, SkeletonData skeletonData) { - var t = new PathConstraintMixTimeline(1); + var t = new PathConstraintMixTimeline(1, 0, timeline.PathConstraintIndex); var data = skeletonData.PathConstraints.Items[timeline.PathConstraintIndex]; t.SetFrame(0, 0, data.RotateMix, data.TranslateMix); return t; diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimInspector.cs index 65929f084..c322a8ece 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimInspector.cs @@ -123,7 +123,6 @@ namespace Spine.Unity.Editor { EditorGUI.indentLevel = 0; var mixMode = layerMixModes.GetArrayElementAtIndex(i); - var blendMode = layerBlendModes.GetArrayElementAtIndex(i); rect.position += new Vector2(rect.width, 0); rect.width = widthMixColumn; EditorGUI.PropertyField(rect, mixMode, GUIContent.none); diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Modules/SlotBlendModes.meta b/spine-unity/Assets/Spine/Editor/spine-unity/Modules/SlotBlendModes.meta deleted file mode 100644 index 34d8d7e02..000000000 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Modules/SlotBlendModes.meta +++ /dev/null @@ -1,9 +0,0 @@ -fileFormatVersion: 2 -guid: 18ee2876d53412642bbfa1070a1b947f -folderAsset: yes -timeCreated: 1527569487 -licenseType: Free -DefaultImporter: - userData: - assetBundleName: - assetBundleVariant: diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Modules/SlotBlendModes/Editor.meta b/spine-unity/Assets/Spine/Editor/spine-unity/Modules/SlotBlendModes/Editor.meta deleted file mode 100644 index 80a6cfcf3..000000000 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Modules/SlotBlendModes/Editor.meta +++ /dev/null @@ -1,9 +0,0 @@ -fileFormatVersion: 2 -guid: 1ad4318c20ec5674a9f4d7f786afd681 -folderAsset: yes -timeCreated: 1496449217 -licenseType: Free -DefaultImporter: - userData: - assetBundleName: - assetBundleVariant: diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Modules/SlotBlendModes/Editor/SlotBlendModesEditor.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Modules/SlotBlendModes/Editor/SlotBlendModesEditor.cs deleted file mode 100644 index 889995202..000000000 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Modules/SlotBlendModes/Editor/SlotBlendModesEditor.cs +++ /dev/null @@ -1,47 +0,0 @@ -/****************************************************************************** - * Spine Runtimes License Agreement - * Last updated January 1, 2020. Replaces all prior versions. - * - * Copyright (c) 2013-2020, Esoteric Software LLC - * - * Integration of the Spine Runtimes into software or otherwise creating - * derivative works of the Spine Runtimes is permitted under the terms and - * conditions of Section 2 of the Spine Editor License Agreement: - * http://esotericsoftware.com/spine-editor-license - * - * Otherwise, it is permitted to integrate the Spine Runtimes into software - * or otherwise create derivative works of the Spine Runtimes (collectively, - * "Products"), provided that each user of the Products must obtain their own - * Spine Editor license and redistribution of the Products in any form must - * include this license and copyright notice. - * - * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, - * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - *****************************************************************************/ - -using UnityEngine; -using UnityEditor; -using Spine.Unity.Deprecated; -using System; - -namespace Spine.Unity.Editor { - using Editor = UnityEditor.Editor; - - [Obsolete("The spine-unity 3.7 runtime introduced SkeletonDataModifierAssets BlendModeMaterials which replaced SlotBlendModes. Will be removed in spine-unity 3.9.", false)] - public class SlotBlendModesEditor : Editor { - - [MenuItem("CONTEXT/SkeletonRenderer/Add Slot Blend Modes Component")] - static void AddSlotBlendModesComponent (MenuCommand command) { - var skeletonRenderer = (SkeletonRenderer)command.context; - skeletonRenderer.gameObject.AddComponent(); - } - } -} diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Modules/SlotBlendModes/Editor/SlotBlendModesEditor.cs.meta b/spine-unity/Assets/Spine/Editor/spine-unity/Modules/SlotBlendModes/Editor/SlotBlendModesEditor.cs.meta deleted file mode 100644 index 21d0e26fe..000000000 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Modules/SlotBlendModes/Editor/SlotBlendModesEditor.cs.meta +++ /dev/null @@ -1,12 +0,0 @@ -fileFormatVersion: 2 -guid: cbec7dc66dca80a419477536c23b7a0d -timeCreated: 1496449255 -licenseType: Free -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SkeletonDataCompatibility.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SkeletonDataCompatibility.cs index db9605dc2..46d3472c3 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SkeletonDataCompatibility.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SkeletonDataCompatibility.cs @@ -40,8 +40,8 @@ namespace Spine.Unity { public static class SkeletonDataCompatibility { #if UNITY_EDITOR - static readonly int[][] compatibleBinaryVersions = { new[] { 3, 9, 0 }, new[] { 3, 8, 0 } }; - static readonly int[][] compatibleJsonVersions = { new[] { 3, 9, 0 }, new[] { 3, 8, 0 } }; + static readonly int[][] compatibleBinaryVersions = { new[] { 4, 0, 0 } }; + static readonly int[][] compatibleJsonVersions = { new[] { 4, 0, 0 } }; static bool wasVersionDialogShown = false; static readonly Regex jsonVersionRegex = new Regex(@"""spine""\s*:\s*""([^""]+)""", RegexOptions.CultureInvariant); diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs index f3291c538..48e27a424 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs @@ -254,7 +254,6 @@ namespace Spine.Unity { private void OnClipAppliedCallback (Spine.Animation clip, AnimatorStateInfo stateInfo, int layerIndex, float time, bool isLooping, float weight) { - float clipDuration = clip.duration == 0 ? 1 : clip.duration; float speedFactor = stateInfo.speedMultiplier * stateInfo.speed; float lastTime = time - (Time.deltaTime * speedFactor); if (isLooping && clip.duration != 0) { diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Deprecated.meta b/spine-unity/Assets/Spine/Runtime/spine-unity/Deprecated.meta deleted file mode 100644 index 099ea4ab9..000000000 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Deprecated.meta +++ /dev/null @@ -1,9 +0,0 @@ -fileFormatVersion: 2 -guid: 04817e31b917de6489f349dd332d7468 -folderAsset: yes -timeCreated: 1563295668 -licenseType: Free -DefaultImporter: - userData: - assetBundleName: - assetBundleVariant: diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Deprecated/SlotBlendModes.meta b/spine-unity/Assets/Spine/Runtime/spine-unity/Deprecated/SlotBlendModes.meta deleted file mode 100644 index c3ef039c0..000000000 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Deprecated/SlotBlendModes.meta +++ /dev/null @@ -1,9 +0,0 @@ -fileFormatVersion: 2 -guid: dfdd78a071ca1a04bb64c6cc41e14aa0 -folderAsset: yes -timeCreated: 1496447038 -licenseType: Free -DefaultImporter: - userData: - assetBundleName: - assetBundleVariant: diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Deprecated/SlotBlendModes/SlotBlendModes.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Deprecated/SlotBlendModes/SlotBlendModes.cs deleted file mode 100644 index 69b01b62a..000000000 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Deprecated/SlotBlendModes/SlotBlendModes.cs +++ /dev/null @@ -1,230 +0,0 @@ -/****************************************************************************** - * Spine Runtimes License Agreement - * Last updated January 1, 2020. Replaces all prior versions. - * - * Copyright (c) 2013-2020, Esoteric Software LLC - * - * Integration of the Spine Runtimes into software or otherwise creating - * derivative works of the Spine Runtimes is permitted under the terms and - * conditions of Section 2 of the Spine Editor License Agreement: - * http://esotericsoftware.com/spine-editor-license - * - * Otherwise, it is permitted to integrate the Spine Runtimes into software - * or otherwise create derivative works of the Spine Runtimes (collectively, - * "Products"), provided that each user of the Products must obtain their own - * Spine Editor license and redistribution of the Products in any form must - * include this license and copyright notice. - * - * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, - * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - *****************************************************************************/ - -using System.Collections.Generic; -using UnityEngine; -using System; - -namespace Spine.Unity.Deprecated { - - /// - /// Deprecated. The spine-unity 3.7 runtime introduced SkeletonDataModifierAssets BlendModeMaterials which replaced SlotBlendModes. See the - /// SkeletonDataModifierAssets BlendModeMaterials documentation page and - /// this forum thread for further information. - /// This class will be removed in the spine-unity 3.9 runtime. - /// - [Obsolete("The spine-unity 3.7 runtime introduced SkeletonDataModifierAssets BlendModeMaterials which replaced SlotBlendModes. Will be removed in spine-unity 3.9.", false)] - [DisallowMultipleComponent] - public class SlotBlendModes : MonoBehaviour { - - #region Internal Material Dictionary - public struct MaterialTexturePair { - public Texture2D texture2D; - public Material material; - } - - internal class MaterialWithRefcount { - public Material materialClone; - public int refcount = 1; - - public MaterialWithRefcount(Material mat) { - this.materialClone = mat; - } - } - static Dictionary materialTable; - internal static Dictionary MaterialTable { - get { - if (materialTable == null) materialTable = new Dictionary(); - return materialTable; - } - } - - internal struct SlotMaterialTextureTuple { - public Slot slot; - public Texture2D texture2D; - public Material material; - - public SlotMaterialTextureTuple(Slot slot, Material material, Texture2D texture) { - this.slot = slot; - this.material = material; - this.texture2D = texture; - } - } - - internal static Material GetOrAddMaterialFor(Material materialSource, Texture2D texture) { - if (materialSource == null || texture == null) return null; - - var mt = SlotBlendModes.MaterialTable; - MaterialWithRefcount matWithRefcount; - var key = new MaterialTexturePair { material = materialSource, texture2D = texture }; - if (!mt.TryGetValue(key, out matWithRefcount)) { - matWithRefcount = new MaterialWithRefcount(new Material(materialSource)); - var m = matWithRefcount.materialClone; - m.name = "(Clone)" + texture.name + "-" + materialSource.name; - m.mainTexture = texture; - mt[key] = matWithRefcount; - } - else { - matWithRefcount.refcount++; - } - return matWithRefcount.materialClone; - } - - internal static MaterialWithRefcount GetExistingMaterialFor(Material materialSource, Texture2D texture) - { - if (materialSource == null || texture == null) return null; - - var mt = SlotBlendModes.MaterialTable; - MaterialWithRefcount matWithRefcount; - var key = new MaterialTexturePair { material = materialSource, texture2D = texture }; - if (!mt.TryGetValue(key, out matWithRefcount)) { - return null; - } - return matWithRefcount; - } - - internal static void RemoveMaterialFromTable(Material materialSource, Texture2D texture) { - var mt = SlotBlendModes.MaterialTable; - var key = new MaterialTexturePair { material = materialSource, texture2D = texture }; - mt.Remove(key); - } - #endregion - - #region Inspector - public Material multiplyMaterialSource; - public Material screenMaterialSource; - - Texture2D texture; - #endregion - - SlotMaterialTextureTuple[] slotsWithCustomMaterial = new SlotMaterialTextureTuple[0]; - - public bool Applied { get; private set; } - - void Start() { - if (!Applied) Apply(); - } - - void OnDestroy() { - if (Applied) Remove(); - } - - public void Apply() { - GetTexture(); - if (texture == null) return; - - var skeletonRenderer = GetComponent(); - if (skeletonRenderer == null) return; - - var slotMaterials = skeletonRenderer.CustomSlotMaterials; - - int numSlotsWithCustomMaterial = 0; - foreach (var s in skeletonRenderer.Skeleton.Slots) { - switch (s.data.blendMode) { - case BlendMode.Multiply: - if (multiplyMaterialSource != null) { - slotMaterials[s] = GetOrAddMaterialFor(multiplyMaterialSource, texture); - ++numSlotsWithCustomMaterial; - } - break; - case BlendMode.Screen: - if (screenMaterialSource != null) { - slotMaterials[s] = GetOrAddMaterialFor(screenMaterialSource, texture); - ++numSlotsWithCustomMaterial; - } - break; - } - } - slotsWithCustomMaterial = new SlotMaterialTextureTuple[numSlotsWithCustomMaterial]; - int storedSlotIndex = 0; - foreach (var s in skeletonRenderer.Skeleton.Slots) { - switch (s.data.blendMode) { - case BlendMode.Multiply: - if (multiplyMaterialSource != null) { - slotsWithCustomMaterial[storedSlotIndex++] = new SlotMaterialTextureTuple(s, multiplyMaterialSource, texture); - } - break; - case BlendMode.Screen: - if (screenMaterialSource != null) { - slotsWithCustomMaterial[storedSlotIndex++] = new SlotMaterialTextureTuple(s, screenMaterialSource, texture); - } - break; - } - } - - Applied = true; - skeletonRenderer.LateUpdate(); - } - - public void Remove() { - GetTexture(); - if (texture == null) return; - - var skeletonRenderer = GetComponent(); - if (skeletonRenderer == null) return; - - var slotMaterials = skeletonRenderer.CustomSlotMaterials; - - foreach (var slotWithCustomMat in slotsWithCustomMaterial) { - - Slot s = slotWithCustomMat.slot; - Material storedMaterialSource = slotWithCustomMat.material; - Texture2D storedTexture = slotWithCustomMat.texture2D; - - var matWithRefcount = GetExistingMaterialFor(storedMaterialSource, storedTexture); - if (--matWithRefcount.refcount == 0) { - RemoveMaterialFromTable(storedMaterialSource, storedTexture); - } - // we don't want to remove slotMaterials[s] if it has been changed in the meantime. - Material m; - if (slotMaterials.TryGetValue(s, out m)) { - var existingMat = matWithRefcount == null ? null : matWithRefcount.materialClone; - if (Material.ReferenceEquals(m, existingMat)) { - slotMaterials.Remove(s); - } - } - } - slotsWithCustomMaterial = null; - - Applied = false; - if (skeletonRenderer.valid) skeletonRenderer.LateUpdate(); - } - - public void GetTexture() { - if (texture == null) { - var sr = GetComponent(); if (sr == null) return; - var sda = sr.skeletonDataAsset; if (sda == null) return; - var aa = sda.atlasAssets[0]; if (aa == null) return; - var am = aa.PrimaryMaterial; if (am == null) return; - texture = am.mainTexture as Texture2D; - } - } - - } -} diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Deprecated/SlotBlendModes/SlotBlendModes.cs.meta b/spine-unity/Assets/Spine/Runtime/spine-unity/Deprecated/SlotBlendModes/SlotBlendModes.cs.meta deleted file mode 100644 index 6750f1105..000000000 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Deprecated/SlotBlendModes/SlotBlendModes.cs.meta +++ /dev/null @@ -1,16 +0,0 @@ -fileFormatVersion: 2 -guid: f1f8243645ba2e74aa3564bd956eed89 -timeCreated: 1496794038 -licenseType: Free -MonoImporter: - serializedVersion: 2 - defaultReferences: - - multiplyMaterialSource: {fileID: 2100000, guid: 53bf0ab317d032d418cf1252d68f51df, - type: 2} - - screenMaterialSource: {fileID: 2100000, guid: 73f0f46d3177c614baf0fa48d646a9be, - type: 2} - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/TimelineExtensions.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/TimelineExtensions.cs index 03f14aa55..57f0b59ea 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/TimelineExtensions.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/TimelineExtensions.cs @@ -38,28 +38,28 @@ namespace Spine.Unity.AnimationTools { /// SkeletonData can be accessed from Skeleton.Data or from SkeletonDataAsset.GetSkeletonData. /// If no SkeletonData is given, values are computed relative to setup pose instead of local-absolute. public static Vector2 Evaluate (this TranslateTimeline timeline, float time, SkeletonData skeletonData = null) { - const int PREV_TIME = -3, PREV_X = -2, PREV_Y = -1; - const int X = 1, Y = 2; - var frames = timeline.frames; if (time < frames[0]) return Vector2.zero; float x, y; - if (time >= frames[frames.Length - TranslateTimeline.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, TranslateTimeline.ENTRIES); - x = frames[frame + PREV_X]; - y = frames[frame + PREV_Y]; - float frameTime = frames[frame]; - float percent = timeline.GetCurvePercent(frame / TranslateTimeline.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, TranslateTimeline.ENTRIES), curveType = (int)timeline.curves[i / TranslateTimeline.ENTRIES]; + switch (curveType) { + case TranslateTimeline.LINEAR: + float before = frames[i]; + x = frames[i + TranslateTimeline.VALUE1]; + y = frames[i + TranslateTimeline.VALUE2]; + float t = (time - before) / (frames[i + TranslateTimeline.ENTRIES] - before); + x += (frames[i + TranslateTimeline.ENTRIES + TranslateTimeline.VALUE1] - x) * t; + y += (frames[i + TranslateTimeline.ENTRIES + TranslateTimeline.VALUE2] - y) * t; + break; + case TranslateTimeline.STEPPED: + x = frames[i + TranslateTimeline.VALUE1]; + y = frames[i + TranslateTimeline.VALUE2]; + break; + default: + x = timeline.GetBezierValue(time, i, TranslateTimeline.VALUE1, curveType - TranslateTimeline.BEZIER); + y = timeline.GetBezierValue(time, i, TranslateTimeline.VALUE2, curveType + TranslateTimeline.BEZIER_SIZE - TranslateTimeline.BEZIER); + break; } Vector2 xy = new Vector2(x, y); @@ -67,7 +67,7 @@ namespace Spine.Unity.AnimationTools { return xy; } else { - var boneData = skeletonData.bones.Items[timeline.boneIndex]; + var boneData = skeletonData.bones.Items[timeline.BoneIndex]; return xy + new Vector2(boneData.x, boneData.y); } } @@ -82,7 +82,7 @@ namespace Spine.Unity.AnimationTools { continue; var translateTimeline = timeline as TranslateTimeline; - if (translateTimeline != null && translateTimeline.boneIndex == boneIndex) + if (translateTimeline != null && translateTimeline.BoneIndex == boneIndex) return translateTimeline; } return null; diff --git a/spine-unity/Assets/Spine/version.txt b/spine-unity/Assets/Spine/version.txt index 1037e0c9d..6f08af033 100644 --- a/spine-unity/Assets/Spine/version.txt +++ b/spine-unity/Assets/Spine/version.txt @@ -1 +1 @@ -This Spine-Unity runtime works with data exported from Spine Editor version: 3.8.xx and 3.9.xx \ No newline at end of file +This Spine-Unity runtime works with data exported from Spine Editor version: 4.0.xx \ No newline at end of file