diff --git a/spine-csharp/README.md b/spine-csharp/README.md
index 3e72b28b8..a9de99082 100644
--- a/spine-csharp/README.md
+++ b/spine-csharp/README.md
@@ -10,7 +10,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f
## Spine version
-spine-csharp works with data exported from Spine 3.4.02.
+spine-csharp works with data exported from Spine 3.5.x.
spine-csharp supports all Spine features.
diff --git a/spine-csharp/spine-csharp.csproj b/spine-csharp/spine-csharp.csproj
index b4916ee9b..ae5b583b9 100644
--- a/spine-csharp/spine-csharp.csproj
+++ b/spine-csharp/spine-csharp.csproj
@@ -53,18 +53,10 @@
-
- Code
-
-
- Code
-
-
- Code
-
-
- Code
-
+
+
+
+
@@ -74,69 +66,30 @@
-
- Code
-
-
- Code
-
-
- Code
-
-
- Code
-
-
- Code
-
-
- Code
-
-
- Code
-
-
- Code
-
-
- Code
-
+
+
+
+
+
+
+
+
+
+
-
- Code
-
+
-
- Code
-
-
- Code
-
-
- Code
-
-
- Code
-
-
- Code
-
-
- Code
-
-
- Code
-
-
- Code
-
-
- Code
-
-
- Code
-
+
+
+
+
+
+
+
+
+
+
diff --git a/spine-csharp/src/Animation.cs b/spine-csharp/src/Animation.cs
index 80b9630a7..31e32c79a 100644
--- a/spine-csharp/src/Animation.cs
+++ b/spine-csharp/src/Animation.cs
@@ -49,10 +49,17 @@ namespace Spine {
this.duration = duration;
}
- /// Poses the skeleton at the specified time for this animation.
+ /// Applies all the animation's timelines to the specified skeleton.
+ /// The skeleton to be posed.
/// The last time the animation was applied.
+ /// The point in time in the animation to apply to the skeleton.
+ /// If true, time wraps within the animation duration.
/// Any triggered events are added. May be null.
- public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events) {
+ /// The percentage between this animation's pose and the current pose.
+ /// If true, the animation is mixed with the setup pose, else it is mixed with the current pose. Passing true when alpha is 1 is slightly more efficient.
+ /// True when mixing over time toward the setup or current pose, false when mixing toward the keyed pose. Irrelevant when alpha is 1.
+ ///
+ public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events, float alpha, bool setupPose, bool mixingOut) {
if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
if (loop && duration != 0) {
@@ -62,28 +69,11 @@ namespace Spine {
ExposedList timelines = this.timelines;
for (int i = 0, n = timelines.Count; i < n; i++)
- timelines.Items[i].Apply(skeleton, lastTime, time, events, 1);
- }
-
- /// Poses the skeleton at the specified time for this animation mixed with the current pose.
- /// The last time the animation was applied.
- /// Any triggered events are added. May be null.
- /// The amount of this animation that affects the current pose.
- public void Mix (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events, float alpha) {
- if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
-
- if (loop && duration != 0) {
- time %= duration;
- 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);
+ timelines.Items[i].Apply(skeleton, lastTime, time, events, alpha, setupPose, mixingOut);
}
/// After the first and before the last entry.
- internal static int binarySearch (float[] values, float target, int step) {
+ internal static int BinarySearch (float[] values, float target, int step) {
int low = 0;
int high = values.Length / step - 2;
if (high == 0) return step;
@@ -99,7 +89,7 @@ namespace Spine {
}
/// After the first and before the last entry.
- internal static int binarySearch (float[] values, float target) {
+ internal static int BinarySearch (float[] values, float target) {
int low = 0;
int high = values.Length - 2;
if (high == 0) return 1;
@@ -114,7 +104,7 @@ namespace Spine {
}
}
- internal static int linearSearch (float[] values, float target, int step) {
+ 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;
@@ -123,8 +113,20 @@ namespace Spine {
public interface Timeline {
/// Sets the value(s) for the specified time.
- /// May be null to not collect fired events.
- void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha);
+ /// Any triggered events are added. May be null.
+ /// True when the timeline is mixed with the setup pose, false when it is mixed with the current pose. Passing true when alpha is 1 is slightly more efficient.
+ /// True when mixing over time toward the setup or current pose, false when mixing toward the keyed pose.
+ /// Used for timelines with instant transitions, eg draw order, attachment visibility, scale sign.
+ void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, bool setupPose, bool mixingOut);
+ int PropertyId { get; }
+ }
+
+ internal enum TimelineType {
+ Rotate = 0, Translate, Scale, Shear, //
+ Attachment, Color, Deform, //
+ Event, DrawOrder, //
+ IkConstraint, TransformConstraint, //
+ PathConstraintPosition, PathConstraintSpacing, PathConstraintMix
}
/// Base class for frames that use an interpolation bezier curve.
@@ -140,7 +142,9 @@ namespace Spine {
curves = new float[(frameCount - 1) * BEZIER_SIZE];
}
- abstract public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha);
+ abstract public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut);
+
+ abstract public int PropertyId { get; }
public void SetLinear (int frameIndex) {
curves[frameIndex * BEZIER_SIZE] = LINEAR;
@@ -218,6 +222,10 @@ namespace Spine {
public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } }
public float[] Frames { get { return frames; } set { frames = value; } } // time, angle, ...
+ override public int PropertyId {
+ get { return ((int)TimelineType.Rotate << 24) + boneIndex; }
+ }
+
public RotateTimeline (int frameCount)
: base(frameCount) {
frames = new float[frameCount << 1];
@@ -230,41 +238,42 @@ namespace Spine {
frames[frameIndex + ROTATION] = degrees;
}
- override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) {
+ override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
Bone bone = skeleton.bones.Items[boneIndex];
- float amount;
+ float r;
if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
- amount = bone.data.rotation + frames[frames.Length + PREV_ROTATION] - bone.rotation;
- while (amount > 180)
- amount -= 360;
- while (amount < -180)
- amount += 360;
- bone.rotation += amount * alpha;
+ if (setupPose) {
+ bone.rotation = bone.data.rotation + frames[frames.Length + PREV_ROTATION] * alpha;
+ } else {
+ r = bone.data.rotation + frames[frames.Length + PREV_ROTATION] - bone.rotation;
+ r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; // Wrap within -180 and 180.
+ bone.rotation += r * alpha;
+ }
return;
}
// Interpolate between the previous frame and the current frame.
- int frame = Animation.binarySearch(frames, time, ENTRIES);
+ int frame = Animation.BinarySearch(frames, time, ENTRIES);
float prevRotation = frames[frame + PREV_ROTATION];
float frameTime = frames[frame];
float percent = GetCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
- amount = frames[frame + ROTATION] - prevRotation;
- while (amount > 180)
- amount -= 360;
- while (amount < -180)
- amount += 360;
- amount = bone.data.rotation + (prevRotation + amount * percent) - bone.rotation;
- while (amount > 180)
- amount -= 360;
- while (amount < -180)
- amount += 360;
- bone.rotation += amount * alpha;
+ r = frames[frame + ROTATION] - prevRotation;
+ r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360;
+ r = prevRotation + r * percent;
+ if (setupPose) {
+ r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360;
+ bone.rotation = bone.data.rotation + r * alpha;
+ } else {
+ r = bone.data.rotation + r - bone.rotation;
+ r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360;
+ bone.rotation += r * alpha;
+ }
}
}
@@ -279,6 +288,10 @@ namespace Spine {
public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } }
public float[] Frames { get { return frames; } set { frames = value; } } // time, value, value, ...
+ override public int PropertyId {
+ get { return ((int)TimelineType.Translate << 24) + boneIndex; }
+ }
+
public TranslateTimeline (int frameCount)
: base(frameCount) {
frames = new float[frameCount * ENTRIES];
@@ -292,83 +305,132 @@ namespace Spine {
frames[frameIndex + Y] = y;
}
- override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) {
+ override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
Bone bone = skeleton.bones.Items[boneIndex];
+ float x, y;
if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
- bone.x += (bone.data.x + frames[frames.Length + PREV_X] - bone.x) * alpha;
- bone.y += (bone.data.y + frames[frames.Length + PREV_Y] - bone.y) * alpha;
- return;
+ 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;
+ }
+ if (setupPose) {
+ bone.x = bone.data.x + x * alpha;
+ bone.y = bone.data.y + y * alpha;
+ } else {
+ bone.x += (bone.data.x + x - bone.x) * alpha;
+ bone.y += (bone.data.y + y - bone.y) * alpha;
}
-
- // Interpolate between the previous frame and the current frame.
- int frame = Animation.binarySearch(frames, time, ENTRIES);
- float prevX = frames[frame + PREV_X];
- float prevY = frames[frame + PREV_Y];
- float frameTime = frames[frame];
- float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
-
- bone.x += (bone.data.x + prevX + (frames[frame + X] - prevX) * percent - bone.x) * alpha;
- bone.y += (bone.data.y + prevY + (frames[frame + Y] - prevY) * percent - bone.y) * alpha;
}
}
public class ScaleTimeline : TranslateTimeline {
+ override public int PropertyId {
+ get { return ((int)TimelineType.Scale << 24) + boneIndex; }
+ }
+
public ScaleTimeline (int frameCount)
: base(frameCount) {
}
- override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) {
+ override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
Bone bone = skeleton.bones.Items[boneIndex];
+
+ float x, y;
if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
- bone.scaleX += (bone.data.scaleX * frames[frames.Length + PREV_X] - bone.scaleX) * alpha;
- bone.scaleY += (bone.data.scaleY * frames[frames.Length + PREV_Y] - bone.scaleY) * alpha;
- return;
+ 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;
+ }
+ if (alpha == 1) {
+ bone.scaleX = x;
+ bone.scaleY = y;
+ } else {
+ float bx, by;
+ if (setupPose) {
+ bx = bone.data.scaleX;
+ by = bone.data.scaleY;
+ } else {
+ bx = bone.scaleX;
+ by = bone.scaleY;
+ }
+ // Mixing out uses sign of setup or current pose, else use sign of key.
+ if (mixingOut) {
+ x = Math.Abs(x) * Math.Sign(bx);
+ y = Math.Abs(y) * Math.Sign(by);
+ } else {
+ bx = Math.Abs(bx) * Math.Sign(x);
+ by = Math.Abs(by) * Math.Sign(y);
+ }
+ bone.scaleX = bx + (x - bx) * alpha;
+ bone.scaleY = by + (y - by) * alpha;
}
-
- // Interpolate between the previous frame and the current frame.
- int frame = Animation.binarySearch(frames, time, ENTRIES);
- float prevX = frames[frame + PREV_X];
- float prevY = frames[frame + PREV_Y];
- float frameTime = frames[frame];
- float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
-
- bone.scaleX += (bone.data.scaleX * (prevX + (frames[frame + X] - prevX) * percent) - bone.scaleX) * alpha;
- bone.scaleY += (bone.data.scaleY * (prevY + (frames[frame + Y] - prevY) * percent) - bone.scaleY) * alpha;
}
}
public class ShearTimeline : TranslateTimeline {
+ override public int PropertyId {
+ get { return ((int)TimelineType.Shear << 24) + boneIndex; }
+ }
+
public ShearTimeline (int frameCount)
: base(frameCount) {
}
- override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) {
+ override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
Bone bone = skeleton.bones.Items[boneIndex];
+ float x, y;
if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
- bone.shearX += (bone.data.shearX + frames[frames.Length + PREV_X] - bone.shearX) * alpha;
- bone.shearY += (bone.data.shearY + frames[frames.Length + PREV_Y] - bone.shearY) * alpha;
- return;
+ 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;
+ }
+ if (setupPose) {
+ bone.shearX = bone.data.shearX + x * alpha;
+ bone.shearY = bone.data.shearY + y * alpha;
+ } else {
+ bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha;
+ bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha;
}
-
- // Interpolate between the previous frame and the current frame.
- int frame = Animation.binarySearch(frames, time, ENTRIES);
- float prevX = frames[frame + PREV_X];
- float prevY = frames[frame + PREV_Y];
- float frameTime = frames[frame];
- float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
-
- bone.shearX += (bone.data.shearX + (prevX + (frames[frame + X] - prevX) * percent) - bone.shearX) * alpha;
- bone.shearY += (bone.data.shearY + (prevY + (frames[frame + Y] - prevY) * percent) - bone.shearY) * alpha;
}
}
@@ -383,6 +445,10 @@ namespace Spine {
public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } }
public float[] Frames { get { return frames; } set { frames = value; } } // time, r, g, b, a, ...
+ override public int PropertyId {
+ get { return ((int)TimelineType.Color << 24) + slotIndex; }
+ }
+
public ColorTimeline (int frameCount)
: base(frameCount) {
frames = new float[frameCount * ENTRIES];
@@ -398,7 +464,7 @@ namespace Spine {
frames[frameIndex + A] = a;
}
- override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) {
+ override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
@@ -411,7 +477,7 @@ namespace Spine {
a = frames[i + PREV_A];
} else {
// Interpolate between the previous frame and the current frame.
- int frame = Animation.binarySearch(frames, time, ENTRIES);
+ int frame = Animation.BinarySearch(frames, time, ENTRIES);
r = frames[frame + PREV_R];
g = frames[frame + PREV_G];
b = frames[frame + PREV_B];
@@ -426,16 +492,28 @@ namespace Spine {
a += (frames[frame + A] - a) * percent;
}
Slot slot = skeleton.slots.Items[slotIndex];
- if (alpha < 1) {
- slot.r += (r - slot.r) * alpha;
- slot.g += (g - slot.g) * alpha;
- slot.b += (b - slot.b) * alpha;
- slot.a += (a - slot.a) * alpha;
- } else {
+ if (alpha == 1) {
slot.r = r;
slot.g = g;
slot.b = b;
slot.a = a;
+ } else {
+ float br, bg, bb, ba;
+ if (setupPose) {
+ br = slot.data.r;
+ bg = slot.data.g;
+ bb = slot.data.b;
+ ba = slot.data.a;
+ } else {
+ br = slot.r;
+ bg = slot.g;
+ bb = slot.b;
+ ba = slot.a;
+ }
+ slot.r = br + ((r - br) * alpha);
+ slot.g = bg + ((g - bg) * alpha);
+ slot.b = bb + ((b - bb) * alpha);
+ slot.a = ba + ((a - ba) * alpha);
}
}
}
@@ -450,6 +528,10 @@ namespace Spine {
public String[] AttachmentNames { get { return attachmentNames; } set { attachmentNames = value; } }
public int FrameCount { get { return frames.Length; } }
+ public int PropertyId {
+ get { return ((int)TimelineType.Attachment << 24) + slotIndex; }
+ }
+
public AttachmentTimeline (int frameCount) {
frames = new float[frameCount];
attachmentNames = new String[frameCount];
@@ -461,7 +543,15 @@ namespace Spine {
attachmentNames[frameIndex] = attachmentName;
}
- public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) {
+ public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) {
+ string attachmentName;
+ if (mixingOut && setupPose) {
+ Slot slot = skeleton.slots.Items[slotIndex];
+ attachmentName = slot.data.attachmentName;
+ slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName);
+ return;
+ }
+
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
@@ -469,112 +559,18 @@ namespace Spine {
if (time >= frames[frames.Length - 1]) // Time is after last frame.
frameIndex = frames.Length - 1;
else
- frameIndex = Animation.binarySearch(frames, time, 1) - 1;
+ frameIndex = Animation.BinarySearch(frames, time, 1) - 1;
- String attachmentName = attachmentNames[frameIndex];
+ attachmentName = attachmentNames[frameIndex];
skeleton.slots.Items[slotIndex]
.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName);
}
}
- public class EventTimeline : Timeline {
- internal float[] frames;
- private Event[] events;
-
- public float[] Frames { get { return frames; } set { frames = value; } } // time, ...
- public Event[] Events { get { return events; } set { events = value; } }
- public int FrameCount { get { return frames.Length; } }
-
- public EventTimeline (int frameCount) {
- frames = new float[frameCount];
- events = new Event[frameCount];
- }
-
- /// Sets the time and value of the specified keyframe.
- public void SetFrame (int frameIndex, Event e) {
- frames[frameIndex] = e.Time;
- events[frameIndex] = e;
- }
-
- /// Fires events for frames > lastTime and <= time.
- public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) {
- if (firedEvents == null) return;
- float[] frames = this.frames;
- int frameCount = frames.Length;
-
- if (lastTime > time) { // Fire events after last time for looped animations.
- Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha);
- lastTime = -1f;
- } else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame.
- return;
- if (time < frames[0]) return; // Time is before first frame.
-
- int frame;
- if (lastTime < frames[0])
- frame = 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--;
- }
- }
- for (; frame < frameCount && time >= frames[frame]; frame++)
- firedEvents.Add(events[frame]);
- }
- }
-
- public class DrawOrderTimeline : Timeline {
- internal float[] frames;
- private int[][] drawOrders;
-
- public float[] Frames { get { return frames; } set { frames = value; } } // time, ...
- public int[][] DrawOrders { get { return drawOrders; } set { drawOrders = value; } }
- public int FrameCount { get { return frames.Length; } }
-
- public DrawOrderTimeline (int frameCount) {
- frames = new float[frameCount];
- drawOrders = new int[frameCount][];
- }
-
- /// Sets the time and value of the specified keyframe.
- /// May be null to use bind pose draw order.
- public void SetFrame (int frameIndex, float time, int[] drawOrder) {
- frames[frameIndex] = time;
- drawOrders[frameIndex] = drawOrder;
- }
-
- public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) {
- float[] frames = this.frames;
- if (time < frames[0]) return; // Time is before first frame.
-
- int frame;
- if (time >= frames[frames.Length - 1]) // Time is after last frame.
- frame = frames.Length - 1;
- else
- frame = Animation.binarySearch(frames, time) - 1;
-
- ExposedList drawOrder = skeleton.drawOrder;
- ExposedList slots = skeleton.slots;
- int[] drawOrderToSetupIndex = drawOrders[frame];
- if (drawOrderToSetupIndex == null) {
- drawOrder.Clear();
- for (int i = 0, n = slots.Count; i < n; i++)
- drawOrder.Add(slots.Items[i]);
- } else {
- var drawOrderItems = drawOrder.Items;
- var slotsItems = slots.Items;
- for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++)
- drawOrderItems[i] = slotsItems[drawOrderToSetupIndex[i]];
- }
- }
- }
-
public class DeformTimeline : CurveTimeline {
internal int slotIndex;
internal float[] frames;
- private float[][] frameVertices;
+ internal float[][] frameVertices;
internal VertexAttachment attachment;
public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } }
@@ -582,6 +578,10 @@ namespace Spine {
public float[][] Vertices { get { return frameVertices; } set { frameVertices = value; } }
public VertexAttachment Attachment { get { return attachment; } set { attachment = value; } }
+ override public int PropertyId {
+ get { return ((int)TimelineType.Deform << 24) + slotIndex; }
+ }
+
public DeformTimeline (int frameCount)
: base(frameCount) {
frames = new float[frameCount];
@@ -594,7 +594,7 @@ namespace Spine {
frameVertices[frameIndex] = vertices;
}
- override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) {
+ override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) {
Slot slot = skeleton.slots.Items[slotIndex];
VertexAttachment slotAttachment = slot.attachment as VertexAttachment;
if (slotAttachment == null || !slotAttachment.ApplyDeform(attachment)) return;
@@ -614,34 +614,173 @@ namespace Spine {
if (time >= frames[frames.Length - 1]) { // Time is after last frame.
float[] lastVertices = frameVertices[frames.Length - 1];
- if (alpha < 1) {
- for (int i = 0; i < vertexCount; i++) {
- float vertex = vertices[i];
- vertices[i] = vertex + (lastVertices[i] - vertex) * alpha;
- }
- } else
+ if (alpha == 1) {
+ // Vertex positions or deform offsets, no alpha.
Array.Copy(lastVertices, 0, vertices, 0, vertexCount);
+ } else if (setupPose) {
+ VertexAttachment vertexAttachment = slotAttachment;
+ if (vertexAttachment.bones == null) {
+ // Unweighted vertex positions, with alpha.
+ float[] setupVertices = vertexAttachment.vertices;
+ for (int i = 0; i < vertexCount; i++) {
+ float setup = setupVertices[i];
+ vertices[i] = setup + (lastVertices[i] - setup) * alpha;
+ }
+ } else {
+ // Weighted deform offsets, with alpha.
+ for (int i = 0; i < vertexCount; i++)
+ vertices[i] = lastVertices[i] * alpha;
+ }
+ } else {
+ // Vertex positions or deform offsets, with alpha.
+ for (int i = 0; i < vertexCount; i++)
+ vertices[i] += (lastVertices[i] - vertices[i]) * alpha;
+ }
return;
}
// Interpolate between the previous frame and the current frame.
- int frame = Animation.binarySearch(frames, time);
+ 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));
- if (alpha < 1) {
- for (int i = 0; i < vertexCount; i++) {
- float prev = prevVertices[i];
- float vertex = vertices[i];
- vertices[i] = vertex + (prev + (nextVertices[i] - prev) * percent - vertex) * alpha;
- }
- } else {
+ if (alpha == 1) {
+ // Vertex positions or deform offsets, no alpha.
for (int i = 0; i < vertexCount; i++) {
float prev = prevVertices[i];
vertices[i] = prev + (nextVertices[i] - prev) * percent;
}
+ } else if (setupPose) {
+ VertexAttachment vertexAttachment = (VertexAttachment)slotAttachment;
+ if (vertexAttachment.bones == null) {
+ // Unweighted vertex positions, with alpha.
+ var setupVertices = vertexAttachment.vertices;
+ for (int i = 0; i < vertexCount; i++) {
+ float prev = prevVertices[i], setup = setupVertices[i];
+ vertices[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha;
+ }
+ } else {
+ // Weighted deform offsets, with alpha.
+ for (int i = 0; i < vertexCount; i++) {
+ float prev = prevVertices[i];
+ vertices[i] = (prev + (nextVertices[i] - prev) * percent) * alpha;
+ }
+ }
+ } else {
+ // Vertex positions or deform offsets, with alpha.
+ for (int i = 0; i < vertexCount; i++) {
+ float prev = prevVertices[i];
+ vertices[i] += (prev + (nextVertices[i] - prev) * percent - vertices[i]) * alpha;
+ }
+ }
+ }
+ }
+
+ public class EventTimeline : Timeline {
+ internal float[] frames;
+ private Event[] events;
+
+ public float[] Frames { get { return frames; } set { frames = value; } } // time, ...
+ public Event[] Events { get { return events; } set { events = value; } }
+ public int FrameCount { get { return frames.Length; } }
+
+ public int PropertyId {
+ get { return ((int)TimelineType.Event << 24); }
+ }
+
+ public EventTimeline (int frameCount) {
+ frames = new float[frameCount];
+ events = new Event[frameCount];
+ }
+
+ /// Sets the time and value of the specified keyframe.
+ public void SetFrame (int frameIndex, Event e) {
+ frames[frameIndex] = e.Time;
+ events[frameIndex] = e;
+ }
+
+ /// Fires events for frames > lastTime and <= time.
+ public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) {
+ if (firedEvents == null) return;
+ float[] frames = this.frames;
+ int frameCount = frames.Length;
+
+ if (lastTime > time) { // Fire events after last time for looped animations.
+ Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha, setupPose, mixingOut);
+ lastTime = -1f;
+ } else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame.
+ return;
+ if (time < frames[0]) return; // Time is before first frame.
+
+ int frame;
+ if (lastTime < frames[0])
+ frame = 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--;
+ }
+ }
+ for (; frame < frameCount && time >= frames[frame]; frame++)
+ firedEvents.Add(events[frame]);
+ }
+ }
+
+ public class DrawOrderTimeline : Timeline {
+ internal float[] frames;
+ private int[][] drawOrders;
+
+ public float[] Frames { get { return frames; } set { frames = value; } } // time, ...
+ public int[][] DrawOrders { get { return drawOrders; } set { drawOrders = value; } }
+ public int FrameCount { get { return frames.Length; } }
+
+ public int PropertyId {
+ get { return ((int)TimelineType.DrawOrder << 24); }
+ }
+
+ public DrawOrderTimeline (int frameCount) {
+ frames = new float[frameCount];
+ drawOrders = new int[frameCount][];
+ }
+
+ /// Sets the time and value of the specified keyframe.
+ /// May be null to use bind pose draw order.
+ public void SetFrame (int frameIndex, float time, int[] drawOrder) {
+ frames[frameIndex] = time;
+ drawOrders[frameIndex] = drawOrder;
+ }
+
+ public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) {
+ if (mixingOut && setupPose) {
+ Array.Copy(skeleton.slots.Items, 0, skeleton.drawOrder.Items, 0, skeleton.slots.Count);
+ return;
+ }
+
+ float[] frames = this.frames;
+ if (time < frames[0]) return; // Time is before first frame.
+
+ int frame;
+ if (time >= frames[frames.Length - 1]) // Time is after last frame.
+ frame = frames.Length - 1;
+ else
+ frame = Animation.BinarySearch(frames, time) - 1;
+
+ ExposedList drawOrder = skeleton.drawOrder;
+ ExposedList slots = skeleton.slots;
+ int[] drawOrderToSetupIndex = drawOrders[frame];
+ if (drawOrderToSetupIndex == null) {
+ drawOrder.Clear();
+ for (int i = 0, n = slots.Count; i < n; i++)
+ drawOrder.Add(slots.Items[i]);
+ } else {
+ var drawOrderItems = drawOrder.Items;
+ var slotsItems = slots.Items;
+ for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++)
+ drawOrderItems[i] = slotsItems[drawOrderToSetupIndex[i]];
}
}
}
@@ -657,11 +796,15 @@ namespace Spine {
public int IkConstraintIndex { get { return ikConstraintIndex; } set { ikConstraintIndex = value; } }
public float[] Frames { get { return frames; } set { frames = value; } } // time, mix, bendDirection, ...
+ override public int PropertyId {
+ get { return ((int)TimelineType.IkConstraint << 24) + ikConstraintIndex; }
+ }
+
public IkConstraintTimeline (int frameCount)
: base(frameCount) {
frames = new float[frameCount * ENTRIES];
}
-
+
/// Sets the time, mix and bend direction of the specified keyframe.
public void SetFrame (int frameIndex, float time, float mix, int bendDirection) {
frameIndex *= ENTRIES;
@@ -670,26 +813,37 @@ namespace Spine {
frames[frameIndex + BEND_DIRECTION] = bendDirection;
}
- override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) {
+ override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex];
if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
- constraint.mix += (frames[frames.Length + PREV_MIX] - constraint.mix) * alpha;
- constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION];
+ if (setupPose) {
+ constraint.mix = constraint.data.mix + (frames[frames.Length + PREV_MIX] - constraint.data.mix) * alpha;
+ constraint.bendDirection = mixingOut ? constraint.data.bendDirection
+ : (int)frames[frames.Length + PREV_BEND_DIRECTION];
+ } else {
+ constraint.mix += (frames[frames.Length + PREV_MIX] - constraint.mix) * alpha;
+ if (!mixingOut) constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION];
+ }
return;
}
// Interpolate between the previous frame and the current frame.
- int frame = Animation.binarySearch(frames, time, ENTRIES);
+ int frame = Animation.BinarySearch(frames, time, ENTRIES);
float mix = frames[frame + PREV_MIX];
float frameTime = frames[frame];
float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
- constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha;
- constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION];
+ if (setupPose) {
+ constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha;
+ constraint.bendDirection = mixingOut ? constraint.data.bendDirection : (int)frames[frame + PREV_BEND_DIRECTION];
+ } else {
+ constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha;
+ if (!mixingOut) constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION];
+ }
}
}
@@ -704,6 +858,10 @@ namespace Spine {
public int TransformConstraintIndex { get { return transformConstraintIndex; } set { transformConstraintIndex = value; } }
public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, scale mix, shear mix, ...
+ override public int PropertyId {
+ get { return ((int)TimelineType.TransformConstraint << 24) + transformConstraintIndex; }
+ }
+
public TransformConstraintTimeline (int frameCount)
: base(frameCount) {
frames = new float[frameCount * ENTRIES];
@@ -718,35 +876,47 @@ namespace Spine {
frames[frameIndex + SHEAR] = shearMix;
}
- override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) {
+ override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex];
+ float rotate, translate, scale, shear;
if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
int i = frames.Length;
- constraint.rotateMix += (frames[i + PREV_ROTATE] - constraint.rotateMix) * alpha;
- constraint.translateMix += (frames[i + PREV_TRANSLATE] - constraint.translateMix) * alpha;
- constraint.scaleMix += (frames[i + PREV_SCALE] - constraint.scaleMix) * alpha;
- constraint.shearMix += (frames[i + PREV_SHEAR] - constraint.shearMix) * alpha;
- return;
+ 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;
+ }
+ if (setupPose) {
+ TransformConstraintData data = constraint.data;
+ constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha;
+ constraint.translateMix = data.translateMix + (translate - data.translateMix) * alpha;
+ constraint.scaleMix = data.scaleMix + (scale - data.scaleMix) * alpha;
+ constraint.shearMix = data.shearMix + (shear - data.shearMix) * alpha;
+ } else {
+ constraint.rotateMix += (rotate - constraint.rotateMix) * alpha;
+ constraint.translateMix += (translate - constraint.translateMix) * alpha;
+ constraint.scaleMix += (scale - constraint.scaleMix) * alpha;
+ constraint.shearMix += (shear - constraint.shearMix) * alpha;
}
-
- // Interpolate between the previous frame and the current frame.
- int frame = Animation.binarySearch(frames, time, ENTRIES);
- float frameTime = frames[frame];
- float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
-
- float rotate = frames[frame + PREV_ROTATE];
- float translate = frames[frame + PREV_TRANSLATE];
- float scale = frames[frame + PREV_SCALE];
- float shear = frames[frame + PREV_SHEAR];
- constraint.rotateMix += (rotate + (frames[frame + ROTATE] - rotate) * percent - constraint.rotateMix) * alpha;
- constraint.translateMix += (translate + (frames[frame + TRANSLATE] - translate) * percent - constraint.translateMix)
- * alpha;
- constraint.scaleMix += (scale + (frames[frame + SCALE] - scale) * percent - constraint.scaleMix) * alpha;
- constraint.shearMix += (shear + (frames[frame + SHEAR] - shear) * percent - constraint.shearMix) * alpha;
}
}
@@ -758,6 +928,10 @@ namespace Spine {
internal int pathConstraintIndex;
internal float[] frames;
+ override public int PropertyId {
+ get { return ((int)TimelineType.PathConstraintPosition << 24) + pathConstraintIndex; }
+ }
+
public PathConstraintPositionTimeline (int frameCount)
: base(frameCount) {
frames = new float[frameCount * ENTRIES];
@@ -773,52 +947,65 @@ namespace Spine {
frames[frameIndex + VALUE] = value;
}
- override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha) {
+ override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex];
- if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
- int i = frames.Length;
- constraint.position += (frames[i + PREV_VALUE] - constraint.position) * alpha;
- return;
+ 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;
}
-
- // Interpolate between the previous frame and the current frame.
- int frame = Animation.binarySearch(frames, time, ENTRIES);
- float position = frames[frame + PREV_VALUE];
- float frameTime = frames[frame];
- float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
-
- constraint.position += (position + (frames[frame + VALUE] - position) * percent - constraint.position) * alpha;
+ if (setupPose)
+ constraint.position = constraint.data.position + (position - constraint.data.position) * alpha;
+ else
+ constraint.position += (position - constraint.position) * alpha;
}
}
public class PathConstraintSpacingTimeline : PathConstraintPositionTimeline {
+ override public int PropertyId {
+ get { return ((int)TimelineType.PathConstraintSpacing << 24) + pathConstraintIndex; }
+ }
+
public PathConstraintSpacingTimeline (int frameCount)
: base(frameCount) {
}
- override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha) {
+ override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex];
- if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
- int i = frames.Length;
- constraint.spacing += (frames[i + PREV_VALUE] - constraint.spacing) * alpha;
- return;
+ 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;
}
- // Interpolate between the previous frame and the current frame.
- int frame = Animation.binarySearch(frames, time, ENTRIES);
- float spacing = frames[frame + PREV_VALUE];
- float frameTime = frames[frame];
- float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
-
- constraint.spacing += (spacing + (frames[frame + VALUE] - spacing) * percent - constraint.spacing) * alpha;
+ if (setupPose)
+ constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha;
+ else
+ constraint.spacing += (spacing - constraint.spacing) * alpha;
}
}
@@ -833,12 +1020,16 @@ namespace Spine {
public int PathConstraintIndex { get { return pathConstraintIndex; } set { pathConstraintIndex = value; } }
public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, ...
+ override public int PropertyId {
+ get { return ((int)TimelineType.PathConstraintMix << 24) + pathConstraintIndex; }
+ }
+
public PathConstraintMixTimeline (int frameCount)
: base(frameCount) {
frames = new float[frameCount * ENTRIES];
- }
+ }
- /** Sets the time and mixes of the specified keyframe. */
+ /// Sets the time and mixes of the specified keyframe.
public void SetFrame (int frameIndex, float time, float rotateMix, float translateMix) {
frameIndex *= ENTRIES;
frames[frameIndex] = time;
@@ -846,29 +1037,36 @@ namespace Spine {
frames[frameIndex + TRANSLATE] = translateMix;
}
- override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha) {
+ override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex];
+ float rotate, translate;
if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
- int i = frames.Length;
- constraint.rotateMix += (frames[i + PREV_ROTATE] - constraint.rotateMix) * alpha;
- constraint.translateMix += (frames[i + PREV_TRANSLATE] - constraint.translateMix) * alpha;
- return;
+ 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;
}
- // Interpolate between the previous frame and the current frame.
- int frame = Animation.binarySearch(frames, time, ENTRIES);
- float rotate = frames[frame + PREV_ROTATE];
- float translate = frames[frame + PREV_TRANSLATE];
- float frameTime = frames[frame];
- float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
-
- constraint.rotateMix += (rotate + (frames[frame + ROTATE] - rotate) * percent - constraint.rotateMix) * alpha;
- constraint.translateMix += (translate + (frames[frame + TRANSLATE] - translate) * percent - constraint.translateMix)
- * alpha;
+ if (setupPose) {
+ constraint.rotateMix = constraint.data.rotateMix + (rotate - constraint.data.rotateMix) * alpha;
+ constraint.translateMix = constraint.data.translateMix + (translate - constraint.data.translateMix) * alpha;
+ } else {
+ constraint.rotateMix += (rotate - constraint.rotateMix) * alpha;
+ constraint.translateMix += (translate - constraint.translateMix) * alpha;
+ }
}
}
}
diff --git a/spine-csharp/src/AnimationState.cs b/spine-csharp/src/AnimationState.cs
index 5cfd6f058..1eb7bed27 100644
--- a/spine-csharp/src/AnimationState.cs
+++ b/spine-csharp/src/AnimationState.cs
@@ -34,9 +34,14 @@ using System.Text;
namespace Spine {
public class AnimationState {
+ private static Animation EmptyAnimation = new Animation("", new ExposedList(), 0);
+
private AnimationStateData data;
- private ExposedList tracks = new ExposedList();
- private ExposedList events = new ExposedList();
+ private readonly ExposedList tracks = new ExposedList();
+ private readonly HashSet propertyIDs = new HashSet();
+ private readonly ExposedList events = new ExposedList();
+ private readonly EventQueue queue;
+ private bool animationsChanged;
private float timeScale = 1;
public AnimationStateData Data { get { return data; } }
@@ -44,169 +49,391 @@ namespace Spine {
public ExposedList Tracks { get { return tracks; } }
public float TimeScale { get { return timeScale; } set { timeScale = value; } }
- public delegate void StartEndDelegate (AnimationState state, int trackIndex);
- public event StartEndDelegate Start;
- public event StartEndDelegate End;
+ public delegate void TrackEntryDelegate (TrackEntry trackEntry);
+ public event TrackEntryDelegate Start, Interrupt, End, Dispose, Complete;
- public delegate void EventDelegate (AnimationState state, int trackIndex, Event e);
- public event EventDelegate Event;
-
- public delegate void CompleteDelegate (AnimationState state, int trackIndex, int loopCount);
- public event CompleteDelegate Complete;
+ public delegate void TrackEntryEventDelegate (TrackEntry trackEntry, Event e);
+ public event TrackEntryEventDelegate Event;
public AnimationState (AnimationStateData data) {
if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
this.data = data;
+ this.queue = new EventQueue(this, HandleAnimationsChanged);
}
+ void HandleAnimationsChanged () {
+ this.animationsChanged = true;
+ }
+
+ ///
+ /// Increments the track entry times, setting queued animations as current if needed
+ /// delta time
public void Update (float delta) {
delta *= timeScale;
- for (int i = 0; i < tracks.Count; i++) {
- TrackEntry current = tracks.Items[i];
+ var tracksItems = tracks.Items;
+ for (int i = 0, n = tracks.Count; i < n; i++) {
+ TrackEntry current = tracksItems[i];
if (current == null) continue;
- float trackDelta = delta * current.timeScale;
- float time = current.time + trackDelta;
- float endTime = current.endTime;
+ current.animationLast = current.nextAnimationLast;
+ current.trackLast = current.nextTrackLast;
- current.time = time;
- if (current.previous != null) {
- current.previous.time += trackDelta;
- current.mixTime += trackDelta;
- }
+ float currentDelta = delta * current.timeScale;
- // Check if completed the animation or a loop iteration.
- if (current.loop ? (current.lastTime % endTime > time % endTime) : (current.lastTime < endTime && time >= endTime)) {
- int count = (int)(time / endTime);
- current.OnComplete(this, i, count);
- if (Complete != null) Complete(this, i, count);
+ if (current.delay > 0) {
+ current.delay -= currentDelta;
+ if (current.delay > 0) continue;
+ currentDelta = -current.delay;
+ current.delay = 0;
}
TrackEntry next = current.next;
if (next != null) {
- next.time = current.lastTime - next.delay;
- if (next.time >= 0) SetCurrent(i, next);
- } else {
- // End non-looping animation when it reaches its end time and there is no next entry.
- if (!current.loop && current.lastTime >= current.endTime) ClearTrack(i);
- }
- }
- }
-
- public void Apply (Skeleton skeleton) {
- ExposedList events = this.events;
-
- for (int i = 0; i < tracks.Count; i++) {
- TrackEntry current = tracks.Items[i];
- if (current == null) continue;
-
- events.Clear();
-
- float time = current.time;
- bool loop = current.loop;
- if (!loop && time > current.endTime) time = current.endTime;
-
- TrackEntry previous = current.previous;
- if (previous == null) {
- if (current.mix == 1)
- current.animation.Apply(skeleton, current.lastTime, time, loop, events);
- else
- current.animation.Mix(skeleton, current.lastTime, time, loop, events, current.mix);
- } else {
- float previousTime = previous.time;
- if (!previous.loop && previousTime > previous.endTime) previousTime = previous.endTime;
- previous.animation.Apply(skeleton, previous.lastTime, previousTime, previous.loop, null);
- // Remove the line above, and uncomment the line below, to allow previous animations to fire events during mixing.
- //previous.animation.Apply(skeleton, previous.lastTime, previousTime, previous.loop, events);
- previous.lastTime = previousTime;
-
- float alpha = current.mixTime / current.mixDuration * current.mix;
- if (alpha >= 1) {
- alpha = 1;
- current.previous = null;
+ // When the next entry's delay is passed, change to the next entry, preserving leftover time.
+ float nextTime = current.trackLast - next.delay;
+ if (nextTime >= 0) {
+ next.delay = 0;
+ next.trackTime = nextTime + (delta * next.timeScale);
+ current.trackTime += currentDelta;
+ SetCurrent(i, next);
+ while (next.mixingFrom != null) {
+ next.mixTime += currentDelta;
+ next = next.mixingFrom;
+ }
+ continue;
+ }
+ UpdateMixingFrom(current, delta, true);
+ } else {
+ UpdateMixingFrom(current, delta, true);
+ // Clear the track when there is no next entry, the track end time is reached, and there is no mixingFrom.
+ if (current.trackLast >= current.trackEnd && current.mixingFrom == null) {
+ tracksItems[i] = null;
+ queue.End(current);
+ DisposeNext(current);
+ continue;
}
- current.animation.Mix(skeleton, current.lastTime, time, loop, events, alpha);
}
- for (int ii = 0, nn = events.Count; ii < nn; ii++) {
- Event e = events.Items[ii];
- current.OnEvent(this, i, e);
- if (Event != null) Event(this, i, e);
- }
-
- current.lastTime = current.time;
+ current.trackTime += currentDelta;
}
+
+ queue.Drain();
}
+ private void UpdateMixingFrom (TrackEntry entry, float delta, bool canEnd) {
+ TrackEntry from = entry.mixingFrom;
+ if (from == null) return;
+
+ if (canEnd && entry.mixTime >= entry.mixDuration && entry.mixTime > 0) {
+ queue.End(from);
+ TrackEntry newFrom = from.mixingFrom;
+ entry.mixingFrom = newFrom;
+ if (newFrom == null) return;
+ entry.mixTime = from.mixTime;
+ entry.mixDuration = from.mixDuration;
+ from = newFrom;
+ }
+
+ from.animationLast = from.nextAnimationLast;
+ from.trackLast = from.nextTrackLast;
+ float mixingFromDelta = delta * from.timeScale;
+ from.trackTime += mixingFromDelta;
+ entry.mixTime += mixingFromDelta;
+
+ UpdateMixingFrom(from, delta, canEnd && from.alpha == 1);
+ }
+
+
+ ///
+ /// Poses the skeleton using the track entry animations. There are no side effects other than invoking listeners, so the
+ /// animation state can be applied to multiple skeletons to pose them identically.
+ public void Apply (Skeleton skeleton) {
+ if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
+ if (animationsChanged) AnimationsChanged();
+
+ var events = this.events;
+
+ var tracksItems = tracks.Items;
+ for (int i = 0, m = tracks.Count; i < m; i++) {
+ TrackEntry current = tracksItems[i];
+ if (current == null || current.delay > 0) continue;
+
+ // Apply mixing from entries first.
+ float mix = current.alpha;
+ if (current.mixingFrom != null) mix *= ApplyMixingFrom(current, skeleton);
+
+ // Apply current entry.
+ float animationLast = current.animationLast, animationTime = current.AnimationTime;
+ int timelineCount = current.animation.timelines.Count;
+ var timelines = current.animation.timelines;
+ var timelinesItems = timelines.Items;
+ if (mix == 1) {
+ for (int ii = 0; ii < timelineCount; ii++)
+ timelinesItems[ii].Apply(skeleton, animationLast, animationTime, events, 1, true, false);
+ } else {
+ bool firstFrame = current.timelinesRotation.Count == 0;
+ if (firstFrame) current.timelinesRotation.EnsureCapacity(timelines.Count << 1);
+ var timelinesRotation = current.timelinesRotation.Items;
+
+ var timelinesFirstItems = current.timelinesFirst.Items;
+ for (int ii = 0; ii < timelineCount; ii++) {
+ Timeline timeline = timelinesItems[ii];
+ var rotateTimeline = timeline as RotateTimeline;
+ if (rotateTimeline != null) {
+ ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, mix, timelinesFirstItems[ii], timelinesRotation, ii << 1,
+ firstFrame);
+ } else {
+ timeline.Apply(skeleton, animationLast, animationTime, events, mix, timelinesFirstItems[ii], false);
+ }
+ }
+ }
+ QueueEvents(current, animationTime);
+ current.nextAnimationLast = animationTime;
+ current.nextTrackLast = current.trackTime;
+ }
+
+ queue.Drain();
+ }
+
+ private float ApplyMixingFrom (TrackEntry entry, Skeleton skeleton) {
+ TrackEntry from = entry.mixingFrom;
+ if (from.mixingFrom != null) ApplyMixingFrom(from, skeleton);
+
+ float mix;
+ if (entry.mixDuration == 0) // Single frame mix to undo mixingFrom changes.
+ mix = 1;
+ else {
+ mix = entry.mixTime / entry.mixDuration;
+ if (mix > 1) mix = 1;
+ }
+
+ 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;
+ var timelinesItems = timelines.Items;
+ int timelineCount = timelines.Count;
+ var timelinesFirst = from.timelinesFirst;
+ var timelinesFirstItems = timelinesFirst.Items;
+ float alpha = from.alpha * entry.mixAlpha * (1 - mix);
+
+ bool firstFrame = entry.timelinesRotation.Count == 0;
+ if (firstFrame) entry.timelinesRotation.Capacity = timelineCount << 1;
+ var timelinesRotation = entry.timelinesRotation.Items;
+
+ for (int i = 0; i < timelineCount; i++) {
+ Timeline timeline = timelinesItems[i];
+ bool setupPose = timelinesFirstItems[i];
+ var rotateTimeline = timeline as RotateTimeline;
+ if (rotateTimeline != null) {
+ ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, alpha, setupPose, timelinesRotation, i << 1, firstFrame);
+ } else {
+ if (setupPose) {
+ if (!attachments && timeline is AttachmentTimeline) continue;
+ if (!drawOrder && timeline is DrawOrderTimeline) continue;
+ }
+ timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, alpha, setupPose, true);
+ }
+ }
+
+ QueueEvents(entry, animationTime);
+ from.nextAnimationLast = animationTime;
+ from.nextTrackLast = from.trackTime;
+
+ return mix;
+ }
+
+ static private void ApplyRotateTimeline (RotateTimeline rotateTimeline, Skeleton skeleton, float time, float alpha, bool setupPose,
+ float[] timelinesRotation, int i, bool firstFrame) {
+ if (alpha == 1) {
+ rotateTimeline.Apply(skeleton, 0, time, null, 1, setupPose, false);
+ return;
+ }
+
+ float[] frames = rotateTimeline.frames;
+ if (time < frames[0]) return; // Time is before first frame.
+
+ Bone bone = skeleton.bones.Items[rotateTimeline.boneIndex];
+
+ float r2;
+ 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 = rotateTimeline.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;
+ }
+
+ // Mix between rotations using the direction of the shortest route on the first frame while detecting crosses.
+ float r1 = setupPose ? bone.data.rotation : bone.rotation;
+ float total, diff = r2 - r1;
+ if (diff == 0) {
+ if (firstFrame) {
+ timelinesRotation[i] = 0;
+ total = 0;
+ } else
+ total = timelinesRotation[i];
+ } else {
+ diff -= (16384 - (int)(16384.499999999996 - diff / 360)) * 360;
+ float lastTotal, lastDiff;
+ if (firstFrame) {
+ lastTotal = 0;
+ lastDiff = diff;
+ } else {
+ lastTotal = timelinesRotation[i]; // Angle and direction of mix, including loops.
+ lastDiff = timelinesRotation[i + 1]; // Difference between bones.
+ }
+ bool current = diff > 0, dir = lastTotal >= 0;
+ // Detect cross at 0 (not 180).
+ if (Math.Sign(lastDiff) != Math.Sign(diff) && Math.Abs(lastDiff) <= 90) {
+ // A cross after a 360 rotation is a loop.
+ if (Math.Abs(lastTotal) > 180) lastTotal += 360 * Math.Sign(lastTotal);
+ dir = current;
+ }
+ total = diff + lastTotal - lastTotal % 360; // Store loops as part of lastTotal.
+ if (dir != current) total += 360 * Math.Sign(lastTotal);
+ timelinesRotation[i] = total;
+ }
+ timelinesRotation[i + 1] = diff;
+ r1 += total * alpha;
+ bone.rotation = r1 - (16384 - (int)(16384.499999999996 - r1 / 360)) * 360;
+ }
+
+ private void QueueEvents (TrackEntry entry, float animationTime) {
+ float animationStart = entry.animationStart, animationEnd = entry.animationEnd;
+ float duration = animationEnd - animationStart;
+ float trackLastWrapped = entry.trackLast % duration;
+
+ // Queue events before complete.
+ var events = this.events;
+ var eventsItems = events.Items;
+ int i = 0, n = events.Count;
+ for (; i < n; i++) {
+ var e = eventsItems[i];
+ if (e.time < trackLastWrapped) break;
+ if (e.time > animationEnd) continue; // Discard events outside animation start/end.
+ queue.Event(entry, e);
+ }
+
+ // Queue complete if completed a loop iteration or the animation.
+ if (entry.loop ? (trackLastWrapped > entry.trackTime % duration)
+ : (animationTime >= animationEnd && entry.animationLast < animationEnd)) {
+ queue.Complete(entry);
+ }
+
+ // Queue events after complete.
+ for (; i < n; i++) {
+ Event e = eventsItems[i];
+ if (e.time < animationStart) continue; // Discard events outside animation start/end.
+ queue.Event(entry, eventsItems[i]);
+ }
+ events.Clear(false);
+ }
+
+ ///
+ /// Removes all animations from all tracks, leaving skeletons in their previous pose.
+ /// It may be desired to use to mix the skeletons back to the setup pose,
+ /// rather than leaving them in their previous pose.
public void ClearTracks () {
- for (int i = 0, n = tracks.Count; i < n; i++)
+ queue.drainDisabled = true;
+ for (int i = 0, n = tracks.Count; i < n; i++) {
ClearTrack(i);
+ }
tracks.Clear();
+ queue.drainDisabled = false;
+ queue.Drain();
}
+ ///
+ /// Removes all animations from the tracks, leaving skeletons in their previous pose.
+ /// It may be desired to use to mix the skeletons back to the setup pose,
+ /// rather than leaving them in their previous pose.
public void ClearTrack (int trackIndex) {
if (trackIndex >= tracks.Count) return;
TrackEntry current = tracks.Items[trackIndex];
if (current == null) return;
- current.OnEnd(this, trackIndex);
- if (End != null) End(this, trackIndex);
+ queue.End(current);
- tracks.Items[trackIndex] = null;
- }
+ DisposeNext(current);
- private TrackEntry ExpandToIndex (int index) {
- if (index < tracks.Count) return tracks.Items[index];
- while (index >= tracks.Count)
- tracks.Add(null);
- return null;
- }
-
- private void SetCurrent (int index, TrackEntry entry) {
- TrackEntry current = ExpandToIndex(index);
- if (current != null) {
- TrackEntry previous = current.previous;
- current.previous = null;
-
- current.OnEnd(this, index);
- if (End != null) End(this, index);
-
- entry.mixDuration = data.GetMix(current.animation, entry.animation);
- if (entry.mixDuration > 0) {
- entry.mixTime = 0;
- // If a mix is in progress, mix from the closest animation.
- if (previous != null && current.mixTime / current.mixDuration < 0.5f)
- entry.previous = previous;
- else
- entry.previous = current;
- }
+ TrackEntry entry = current;
+ while (true) {
+ TrackEntry from = entry.mixingFrom;
+ if (from == null) break;
+ queue.End(from);
+ entry.mixingFrom = null;
+ entry = from;
}
- tracks.Items[index] = entry;
+ tracks.Items[current.trackIndex] = null;
- entry.OnStart(this, index);
- if (Start != null) Start(this, index);
+ queue.Drain();
}
- ///
+ private void SetCurrent (int index, TrackEntry current) {
+ TrackEntry from = ExpandToIndex(index);
+ tracks.Items[index] = current;
+
+ if (from != null) {
+ queue.Interrupt(from);
+ current.mixingFrom = from;
+ current.mixTime = 0;
+
+ from.timelinesRotation.Clear();
+
+ // If not completely mixed in, set mixAlpha so mixing out happens from current mix to zero.
+ if (from.mixingFrom != null) from.mixAlpha *= Math.Min(from.mixTime / from.mixDuration, 1);
+ }
+
+ queue.Start(current);
+ }
+
+
+ /// Sets an animation by name.
public TrackEntry SetAnimation (int trackIndex, String animationName, bool loop) {
Animation animation = data.skeletonData.FindAnimation(animationName);
if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName");
return SetAnimation(trackIndex, animation, loop);
}
- /// Set the current animation. Any queued animations are cleared.
+ /// Sets the current animation for a track, discarding any queued animations.
+ /// If true, the animation will repeat.
+ /// If false, it will not, instead its last frame is applied if played beyond its duration.
+ /// In either case determines when the track is cleared.
+ ///
+ /// A track entry to allow further customization of animation playback. References to the track entry must not be kept
+ /// after .
public TrackEntry SetAnimation (int trackIndex, Animation animation, bool loop) {
if (animation == null) throw new ArgumentNullException("animation", "animation cannot be null.");
- TrackEntry entry = new TrackEntry();
- entry.animation = animation;
- entry.loop = loop;
- entry.time = 0;
- entry.endTime = animation.Duration;
+ TrackEntry current = ExpandToIndex(trackIndex);
+ if (current != null) {
+ if (current.nextTrackLast == -1) {
+ // Don't mix from an entry that was never applied.
+ tracks.Items[trackIndex] = null;
+ queue.Interrupt(current);
+ queue.End(current);
+ DisposeNext(current);
+ current = null;
+ } else {
+ DisposeNext(current);
+ }
+ }
+ TrackEntry entry = NewTrackEntry(trackIndex, animation, loop, current);
SetCurrent(trackIndex, entry);
+ queue.Drain();
return entry;
}
+ /// Queues an animation by name.
///
public TrackEntry AddAnimation (int trackIndex, String animationName, bool loop, float delay) {
Animation animation = data.skeletonData.FindAnimation(animationName);
@@ -214,93 +441,483 @@ namespace Spine {
return AddAnimation(trackIndex, animation, loop, delay);
}
- /// Adds an animation to be played delay seconds after the current or last queued animation.
- /// May be <= 0 to use duration of previous animation minus any mix duration plus the negative delay.
+ /// Adds an animation to be played delay seconds after the current or last queued animation
+ /// for a track. If the track is empty, it is equivalent to calling .
+ ///
+ /// Seconds to begin this animation after the start of the previous animation. May be <= 0 to use the animation
+ /// duration of the previous track minus any mix duration plus the negative delay.
+ ///
+ /// A track entry to allow further customization of animation playback. References to the track entry must not be kept
+ /// after
public TrackEntry AddAnimation (int trackIndex, Animation animation, bool loop, float delay) {
if (animation == null) throw new ArgumentNullException("animation", "animation cannot be null.");
- TrackEntry entry = new TrackEntry();
- entry.animation = animation;
- entry.loop = loop;
- entry.time = 0;
- entry.endTime = animation.Duration;
TrackEntry last = ExpandToIndex(trackIndex);
if (last != null) {
while (last.next != null)
last = last.next;
- last.next = entry;
- } else
- tracks.Items[trackIndex] = entry;
-
- if (delay <= 0) {
- if (last != null)
- delay += last.endTime - data.GetMix(last.animation, animation);
- else
- delay = 0;
}
- entry.delay = delay;
+ TrackEntry entry = NewTrackEntry(trackIndex, animation, loop, last);
+
+ if (last == null) {
+ SetCurrent(trackIndex, entry);
+ queue.Drain();
+ } else {
+ last.next = entry;
+ if (delay <= 0) {
+ float duration = last.animationEnd - last.animationStart;
+ if (duration != 0)
+ delay += duration * (1 + (int)(last.trackTime / duration)) - data.GetMix(last.animation, animation);
+ else
+ delay = 0;
+ }
+ }
+
+ entry.delay = delay;
return entry;
}
- /// May be null.
+ ///
+ /// Sets an empty animation for a track, discarding any queued animations, and mixes to it over the specified mix duration.
+ public TrackEntry SetEmptyAnimation (int trackIndex, float mixDuration) {
+ TrackEntry entry = SetAnimation(trackIndex, AnimationState.EmptyAnimation, false);
+ entry.mixDuration = mixDuration;
+ entry.trackEnd = mixDuration;
+ return entry;
+ }
+
+ ///
+ /// Adds an empty animation to be played after the current or last queued animation for a track, and mixes to it over the
+ /// specified mix duration.
+ ///
+ /// A track entry to allow further customization of animation playback. References to the track entry must not be kept after .
+ ///
+ /// Track number.
+ /// Mix duration.
+ /// Seconds to begin this animation after the start of the previous animation. May be <= 0 to use the animation
+ /// duration of the previous track minus any mix duration plus the negative delay.
+ public TrackEntry AddEmptyAnimation (int trackIndex, float mixDuration, float delay) {
+ if (delay <= 0) delay -= mixDuration;
+ TrackEntry entry = AddAnimation(trackIndex, AnimationState.EmptyAnimation, false, delay);
+ entry.mixDuration = mixDuration;
+ entry.trackEnd = mixDuration;
+ return entry;
+ }
+
+ ///
+ /// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix duration.
+ public void SetEmptyAnimations (float mixDuration) {
+ queue.drainDisabled = true;
+ for (int i = 0, n = tracks.Count; i < n; i++) {
+ TrackEntry current = tracks.Items[i];
+ if (current != null) SetEmptyAnimation(i, mixDuration);
+ }
+ queue.drainDisabled = false;
+ queue.Drain();
+ }
+
+ private TrackEntry ExpandToIndex (int index) {
+ if (index < tracks.Count) return tracks.Items[index];
+ while (index >= tracks.Count)
+ tracks.Add(null);
+ return null;
+ }
+
+ /// May be null.
+ private TrackEntry NewTrackEntry (int trackIndex, Animation animation, bool loop, TrackEntry last) {
+ return new TrackEntry {
+ trackIndex = trackIndex,
+ animation = animation,
+ loop = loop,
+
+ eventThreshold = 0,
+ attachmentThreshold = 0,
+ drawOrderThreshold = 0,
+
+ animationStart = 0,
+ animationEnd = animation.duration,
+ animationLast = -1,
+ nextAnimationLast = -1,
+
+ delay = 0,
+ trackTime = 0,
+ trackLast = -1,
+ nextTrackLast = -1,
+ trackEnd = loop ? int.MaxValue : animation.duration,
+ timeScale = 1,
+
+ alpha = 1,
+ mixAlpha = 1,
+ mixTime = 0,
+ mixDuration = (last == null) ? 0 : data.GetMix(last.animation, animation),
+ };
+ }
+
+ private void DisposeNext (TrackEntry entry) {
+ TrackEntry next = entry.next;
+ while (next != null) {
+ queue.Dispose(next);
+ next = next.next;
+ }
+ entry.next = null;
+ }
+
+ private void AnimationsChanged () {
+ animationsChanged = false;
+
+ var propertyIDs = this.propertyIDs;
+
+ // Set timelinesFirst for all entries, from lowest track to highest.
+ int i = 0, n = tracks.Count;
+ propertyIDs.Clear();
+ for (; i < n; i++) { // Find first non-null entry.
+ TrackEntry entry = tracks.Items[i];
+ if (entry == null) continue;
+ SetTimelinesFirst(entry);
+ i++;
+ break;
+ }
+ for (; i < n; i++) { // Rest of entries.
+ TrackEntry entry = tracks.Items[i];
+ if (entry != null) CheckTimelinesFirst(entry);
+ }
+ }
+
+ /// From last to first mixingFrom entries, sets timelinesFirst to true on last, calls checkTimelineUsage on rest.
+ private void SetTimelinesFirst (TrackEntry entry) {
+ if (entry.mixingFrom != null) {
+ SetTimelinesFirst(entry.mixingFrom);
+ CheckTimelinesUsage(entry, entry.timelinesFirst);
+ return;
+ }
+ var propertyIDs = this.propertyIDs;
+ var timelines = entry.animation.timelines;
+ int n = timelines.Count;
+ entry.timelinesFirst.EnsureCapacity(n); // entry.timelinesFirst.setSize(n);
+ var usage = entry.timelinesFirst.Items;
+ var timelinesItems = timelines.Items;
+ for (int i = 0; i < n; i++) {
+ propertyIDs.Add(timelinesItems[i].PropertyId);
+ usage[i] = true;
+ }
+ }
+
+ /// From last to first mixingFrom entries, calls checkTimelineUsage.
+ private void CheckTimelinesFirst (TrackEntry entry) {
+ if (entry.mixingFrom != null) CheckTimelinesFirst(entry.mixingFrom);
+ CheckTimelinesUsage(entry, entry.timelinesFirst);
+ }
+
+ private void CheckTimelinesUsage (TrackEntry entry, ExposedList usageArray) {
+ var propertyIDs = this.propertyIDs;
+ var timelines = entry.animation.timelines;
+ int n = timelines.Count;
+ usageArray.EnsureCapacity(n);
+ var usage = usageArray.Items;
+ var timelinesItems = timelines.Items;
+ for (int i = 0; i < n; i++)
+ usage[i] = propertyIDs.Add(timelinesItems[i].PropertyId);
+ }
+
+ /// The track entry for the animation currently playing on the track, or null if no animation is currently playing.
public TrackEntry GetCurrent (int trackIndex) {
- if (trackIndex >= tracks.Count) return null;
- return tracks.Items[trackIndex];
+ return (trackIndex >= tracks.Count) ? null : tracks.Items[trackIndex];
}
override public String ToString () {
- StringBuilder buffer = new StringBuilder();
+ var buffer = new StringBuilder();
for (int i = 0, n = tracks.Count; i < n; i++) {
TrackEntry entry = tracks.Items[i];
if (entry == null) continue;
if (buffer.Length > 0) buffer.Append(", ");
buffer.Append(entry.ToString());
}
- if (buffer.Length == 0) return "";
- return buffer.ToString();
+ return buffer.Length == 0 ? "" : buffer.ToString();
}
+
+ internal void OnStart (TrackEntry entry) { if (Start != null) Start(entry); }
+ internal void OnInterrupt (TrackEntry entry) { if (Interrupt != null) Interrupt(entry); }
+ internal void OnEnd (TrackEntry entry) { if (End != null) End(entry); }
+ internal void OnDispose (TrackEntry entry) { if (Dispose != null) Dispose(entry); }
+ internal void OnComplete (TrackEntry entry) { if (Complete != null) Complete(entry); }
+ internal void OnEvent (TrackEntry entry, Event e) { if (Event != null) Event(entry, e); }
}
+ /// State for the playback of an animation.
public class TrackEntry {
- internal TrackEntry next, previous;
internal Animation animation;
- internal bool loop;
- internal float delay, time, lastTime = -1, endTime, timeScale = 1;
- internal float mixTime, mixDuration, mix = 1;
+ internal TrackEntry next, mixingFrom;
+ internal int trackIndex;
+
+ internal bool loop;
+ internal float eventThreshold, attachmentThreshold, drawOrderThreshold;
+ internal float animationStart, animationEnd, animationLast, nextAnimationLast;
+ internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f;
+ internal float alpha, mixTime, mixDuration, mixAlpha;
+ internal readonly ExposedList timelinesFirst = new ExposedList(), timelinesLast = new ExposedList();
+ internal readonly ExposedList timelinesRotation = new ExposedList();
+
+ /// The index of the track where this entry is either current or queued.
+ public int TrackIndex { get { return trackIndex; } }
+
+ /// The animation to apply for this track entry.
public Animation Animation { get { return animation; } }
- public float Delay { get { return delay; } set { delay = value; } }
- public float Time { get { return time; } set { time = value; } }
- public float LastTime { get { return lastTime; } set { lastTime = value; } }
- public float EndTime { get { return endTime; } set { endTime = value; } }
- public float TimeScale { get { return timeScale; } set { timeScale = value; } }
- public float Mix { get { return mix; } set { mix = value; } }
+
+ ///
+ /// If true, the animation will repeat. If false, it will not, instead its last frame is applied if played beyond its duration.
public bool Loop { get { return loop; } set { loop = value; } }
- public event AnimationState.StartEndDelegate Start;
- public event AnimationState.StartEndDelegate End;
- public event AnimationState.EventDelegate Event;
- public event AnimationState.CompleteDelegate Complete;
+ ///
+ /// Seconds to postpone playing the animation. When a track entry is the current track entry, delay postpones incrementing
+ /// the track time. When a track entry is queued, delay is the time from the start of the previous animation to when the
+ /// track entry will become the current track entry.
+ public float Delay { get { return delay; } set { delay = value; } }
- internal void OnStart (AnimationState state, int index) {
- if (Start != null) Start(state, index);
+ ///
+ /// Current time in seconds this track entry has been the current track entry. The track time determines
+ /// . The track time can be set to start the animation at a time other than 0, without affecting looping.
+ public float TrackTime { get { return trackTime; } set { trackTime = value; } }
+
+ ///
+ /// The track time in seconds when this animation will be removed from the track. Defaults to the animation duration for
+ /// non-looping animations and to for looping animations. If the track end time is reached and no
+ /// other animations are queued for playback, and mixing from any previous animations is complete, then the track is cleared,
+ /// leaving skeletons in their previous pose.
+ ///
+ /// It may be desired to use to mix the skeletons back to the
+ /// setup pose, rather than leaving them in their previous pose.
+ ///
+ public float TrackEnd { get { return trackEnd; } set { trackEnd = value; } }
+
+ ///
+ /// Seconds when this animation starts, both initially and after looping. Defaults to 0.
+ ///
+ /// When changing the animation start time, it often makes sense to set to the same value to
+ /// prevent timeline keys before the start time from triggering.
+ ///
+ public float AnimationStart { get { return animationStart; } set { animationStart = value; } }
+
+ ///
+ /// Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animations will
+ /// loop back to at this time. Defaults to the animation duration.
+ public float AnimationEnd { get { return animationEnd; } }
+
+ ///
+ /// The time in seconds this animation was last applied. Some timelines use this for one-time triggers. Eg, when this
+ /// animation is applied, event timelines will fire all events between the animation last time (exclusive) and animation time
+ /// (inclusive). Defaults to -1 to ensure triggers on frame 0 happen the first time this animation is applied.
+ public float AnimationLast {
+ get { return animationLast; }
+ set {
+ animationLast = value;
+ nextAnimationLast = value;
+ }
}
- internal void OnEnd (AnimationState state, int index) {
- if (End != null) End(state, index);
+ ///
+ /// Uses to compute the animation time between . and
+ /// . When the track time is 0, the animation time is equal to the animation start time.
+ ///
+ public float AnimationTime {
+ get {
+ if (loop) {
+ float duration = animationEnd - animationStart;
+ if (duration == 0) return animationStart;
+ return (trackTime % duration) + animationStart;
+ }
+ return Math.Min(trackTime + animationStart, animationEnd);
+ }
}
- internal void OnEvent (AnimationState state, int index, Event e) {
- if (Event != null) Event(state, index, e);
+ ///
+ /// Multiplier for the delta time when the animation state is updated, causing time for this animation to play slower or
+ /// faster. Defaults to 1.
+ ///
+ public float TimeScale { get { return timeScale; } set { timeScale = value; } }
+
+ ///
+ /// Values less than 1 mix this animation with the last skeleton pose. Defaults to 1, which overwrites the last skeleton pose with
+ /// this animation.
+ ///
+ /// Typically track 0 is used to completely pose the skeleton, then alpha can be used on higher tracks. It doesn't make sense
+ /// to use alpha on track 0 if the skeleton pose is from the last frame render.
+ ///
+ public float Alpha { get { return alpha; } set { alpha = value; } }
+
+ ///
+ /// When the mix percentage (mix time / mix duration) is less than the event threshold, event timelines for the animation
+ /// being mixed out will be applied. Defaults to 0, so event timelines are not applied for an animation being mixed out.
+ public float EventThreshold { get { return eventThreshold; } set { eventThreshold = value; } }
+
+ ///
+ /// When the mix percentage (mix time / mix duration) is less than the attachment threshold, attachment timelines for the
+ /// animation being mixed out will be applied. Defaults to 0, so attachment timelines are not applied for an animation being
+ /// mixed out.
+ public float AttachmentThreshold { get { return attachmentThreshold; } set { attachmentThreshold = value; } }
+
+ ///
+ /// When the mix percentage (mix time / mix duration) is less than the draw order threshold, draw order timelines for the
+ /// animation being mixed out will be applied. Defaults to 0, so draw order timelines are not applied for an animation being
+ /// mixed out.
+ ///
+ public float DrawOrderThreshold { get { return drawOrderThreshold; } set { drawOrderThreshold = value; } }
+
+ ///
+ /// The animation queued to start after this animation, or null.
+ public TrackEntry Next { get { return next; } }
+
+ ///
+ /// Returns true if at least one loop has been completed.
+ public bool IsComplete {
+ get { return trackTime >= animationEnd - animationStart; }
}
- internal void OnComplete (AnimationState state, int index, int loopCount) {
- if (Complete != null) Complete(state, index, loopCount);
+ ///
+ /// Seconds from 0 to the mix duration when mixing from the previous animation to this animation. May be slightly more than
+ /// .
+ public float MixTime { get { return mixTime; } set { mixTime = value; } }
+
+ ///
+ /// Seconds for mixing from the previous animation to this animation. Defaults to the value provided by
+ /// based on the animation before this animation (if any).
+ ///
+ /// The mix duration must be set before is next called.
+ ///
+ public float MixDuration { get { return mixDuration; } set { mixDuration = value; } }
+
+ ///
+ /// The track entry for the previous animation when mixing from the previous animation to this animation, or null if no
+ /// mixing is currently occuring.
+ public TrackEntry MixingFrom { get { return mixingFrom; } }
+
+ public event AnimationState.TrackEntryDelegate Start, Interrupt, End, Dispose, Complete;
+ public event AnimationState.TrackEntryEventDelegate Event;
+ internal void OnStart () { if (Start != null) Start(this); }
+ internal void OnInterrupt () { if (Interrupt != null) Interrupt(this); }
+ internal void OnEnd () { if (End != null) End(this); }
+ internal void OnDispose () { if (Dispose != null) Dispose(this); }
+ internal void OnComplete () { if (Complete != null) Complete(this); }
+ internal void OnEvent (Event e) { if (Event != null) Event(this, e); }
+
+ ///
+ /// Resets the rotation directions for mixing this entry's rotate timelines. This can be useful to avoid bones rotating the
+ /// long way around when using and starting animations on other tracks.
+ ///
+ /// Mixing involves finding a rotation between two others, which has two possible solutions: the short way or the long way around.
+ /// The two rotations likely change over time, so which direction is the short or long way also changes.
+ /// If the short way was always chosen, bones would flip to the other side when that direction became the long way.
+ /// TrackEntry chooses the short way the first time it is applied and remembers that direction.
+ public void ResetRotationDirections () {
+ timelinesRotation.Clear();
}
override public String ToString () {
return animation == null ? "" : animation.name;
}
}
+
+ class EventQueue {
+ private readonly ExposedList eventQueueEntries = new ExposedList();
+ public bool drainDisabled;
+
+ private readonly AnimationState state;
+ public event Action AnimationsChanged;
+
+ public EventQueue (AnimationState state, Action HandleAnimationsChanged) {
+ this.state = state;
+ this.AnimationsChanged += HandleAnimationsChanged;
+ }
+
+ struct EventQueueEntry {
+ public EventType type;
+ public TrackEntry entry;
+ public Event e;
+
+ public EventQueueEntry (EventType eventType, TrackEntry trackEntry, Event e = null) {
+ this.type = eventType;
+ this.entry = trackEntry;
+ this.e = e;
+ }
+ }
+
+ enum EventType {
+ Start, Interrupt, End, Dispose, Complete, Event
+ }
+
+ public void Start (TrackEntry entry) {
+ eventQueueEntries.Add(new EventQueueEntry(EventType.Start, entry));
+ if (AnimationsChanged != null) AnimationsChanged();
+ }
+
+ public void Interrupt (TrackEntry entry) {
+ eventQueueEntries.Add(new EventQueueEntry(EventType.Interrupt, entry));
+ }
+
+ public void End (TrackEntry entry) {
+ eventQueueEntries.Add(new EventQueueEntry(EventType.End, entry));
+ if (AnimationsChanged != null) AnimationsChanged();
+ }
+
+ public void Dispose (TrackEntry entry) {
+ eventQueueEntries.Add(new EventQueueEntry(EventType.Dispose, entry));
+ }
+
+ public void Complete (TrackEntry entry) {
+ eventQueueEntries.Add(new EventQueueEntry(EventType.Complete, entry));
+ }
+
+ public void Event (TrackEntry entry, Event e) {
+ eventQueueEntries.Add(new EventQueueEntry(EventType.Event, entry, e));
+ }
+
+ public void Drain () {
+ if (drainDisabled) return;
+ drainDisabled = true;
+
+ var entries = this.eventQueueEntries;
+ var entriesItems = entries.Items;
+ AnimationState state = this.state;
+
+ for (int i = 0, n = entries.Count; i < n; i++) {
+ var queueEntry = entriesItems[i];
+ TrackEntry trackEntry = queueEntry.entry;
+
+ switch (queueEntry.type) {
+ case EventType.Start:
+ trackEntry.OnStart();
+ state.OnStart(trackEntry);
+ break;
+ case EventType.Interrupt:
+ trackEntry.OnInterrupt();
+ state.OnInterrupt(trackEntry);
+ break;
+ case EventType.End:
+ trackEntry.OnEnd();
+ state.OnEnd(trackEntry);
+ goto case EventType.Dispose; // Fall through. (C#)
+ case EventType.Dispose:
+ trackEntry.OnDispose();
+ state.OnDispose(trackEntry);
+ break;
+ case EventType.Complete:
+ trackEntry.OnComplete();
+ state.OnComplete(trackEntry);
+ break;
+ case EventType.Event:
+ trackEntry.OnEvent(queueEntry.e);
+ state.OnEvent(trackEntry, queueEntry.e);
+ break;
+ }
+ }
+ eventQueueEntries.Clear();
+
+ drainDisabled = false;
+ }
+
+ public void Clear () {
+ eventQueueEntries.Clear();
+ }
+ }
}
diff --git a/spine-csharp/src/AnimationStateData.cs b/spine-csharp/src/AnimationStateData.cs
index c3350e347..af3b82e95 100644
--- a/spine-csharp/src/AnimationStateData.cs
+++ b/spine-csharp/src/AnimationStateData.cs
@@ -62,6 +62,8 @@ namespace Spine {
}
public float GetMix (Animation from, Animation to) {
+ if (from == null) throw new ArgumentNullException("from", "from cannot be null.");
+ if (to == null) throw new ArgumentNullException("to", "to cannot be null.");
AnimationPair key = new AnimationPair(from, to);
float duration;
if (animationToMixTime.TryGetValue(key, out duration)) return duration;
@@ -76,6 +78,10 @@ namespace Spine {
this.a1 = a1;
this.a2 = a2;
}
+
+ public override string ToString () {
+ return a1.name + "->" + a2.name;
+ }
}
// Avoids boxing in the dictionary.
diff --git a/spine-csharp/src/Attachments/AtlasAttachmentLoader.cs b/spine-csharp/src/Attachments/AtlasAttachmentLoader.cs
index e184eeb2f..345282847 100644
--- a/spine-csharp/src/Attachments/AtlasAttachmentLoader.cs
+++ b/spine-csharp/src/Attachments/AtlasAttachmentLoader.cs
@@ -71,7 +71,7 @@ namespace Spine {
attachment.regionOriginalWidth = region.originalWidth;
attachment.regionOriginalHeight = region.originalHeight;
return attachment;
- }
+ }
public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, String name) {
return new BoundingBoxAttachment(name);
diff --git a/spine-csharp/src/Attachments/PathAttachment.cs b/spine-csharp/src/Attachments/PathAttachment.cs
index 0ead00e79..b084de4ee 100644
--- a/spine-csharp/src/Attachments/PathAttachment.cs
+++ b/spine-csharp/src/Attachments/PathAttachment.cs
@@ -43,6 +43,6 @@ namespace Spine {
public PathAttachment (String name)
: base(name) {
- }
+ }
}
}
diff --git a/spine-csharp/src/Attachments/RegionAttachment.cs b/spine-csharp/src/Attachments/RegionAttachment.cs
index 6db406b6e..145ce486f 100644
--- a/spine-csharp/src/Attachments/RegionAttachment.cs
+++ b/spine-csharp/src/Attachments/RegionAttachment.cs
@@ -135,8 +135,7 @@ namespace Spine {
}
public void ComputeWorldVertices (Bone bone, float[] worldVertices) {
- Skeleton skeleton = bone.skeleton;
- float x = skeleton.x + bone.worldX, y = skeleton.y + bone.worldY;
+ float x = bone.worldX, y = bone.worldY;
float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
float[] offset = this.offset;
worldVertices[X1] = offset[X1] * a + offset[Y1] * b + x;
diff --git a/spine-csharp/src/Attachments/VertexAttachment.cs b/spine-csharp/src/Attachments/VertexAttachment.cs
index 64f8d5bc5..ffdcd9b28 100644
--- a/spine-csharp/src/Attachments/VertexAttachment.cs
+++ b/spine-csharp/src/Attachments/VertexAttachment.cs
@@ -32,7 +32,7 @@ using System;
using System.Collections.Generic;
namespace Spine {
- /// An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's vertices.
+ /// >An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's vertices.
public class VertexAttachment : Attachment {
internal int[] bones;
internal float[] vertices;
@@ -50,18 +50,21 @@ namespace Spine {
ComputeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0);
}
+ /// Transforms local vertices to world coordinates.
+ /// The index of the first value to transform. Each vertex has 2 values, x and y.
+ /// The number of world vertex values to output. Must be less than or equal to - start.
+ /// The output world vertices. Must have a length greater than or equal to + .
+ /// The index to begin writing values.
public void ComputeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset) {
count += offset;
Skeleton skeleton = slot.Skeleton;
- float x = skeleton.x, y = skeleton.y;
var deformArray = slot.attachmentVertices;
float[] vertices = this.vertices;
int[] bones = this.bones;
if (bones == null) {
if (deformArray.Count > 0) vertices = deformArray.Items;
Bone bone = slot.bone;
- x += bone.worldX;
- y += bone.worldY;
+ float x = bone.worldX, y = bone.worldY;
float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
for (int vv = start, w = offset; w < count; vv += 2, w += 2) {
float vx = vertices[vv], vy = vertices[vv + 1];
@@ -79,7 +82,7 @@ namespace Spine {
Bone[] skeletonBones = skeleton.Bones.Items;
if (deformArray.Count == 0) {
for (int w = offset, b = skip * 3; w < count; w += 2) {
- float wx = x, wy = y;
+ float wx = 0, wy = 0;
int n = bones[v++];
n += v;
for (; v < n; v++, b += 3) {
@@ -94,7 +97,7 @@ namespace Spine {
} else {
float[] deform = deformArray.Items;
for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += 2) {
- float wx = x, wy = y;
+ float wx = 0, wy = 0;
int n = bones[v++];
n += v;
for (; v < n; v++, b += 3, f += 2) {
@@ -112,6 +115,6 @@ namespace Spine {
/// Returns true if a deform originally applied to the specified attachment should be applied to this attachment.
virtual public bool ApplyDeform (VertexAttachment sourceAttachment) {
return this == sourceAttachment;
- }
+ }
}
}
diff --git a/spine-csharp/src/Bone.cs b/spine-csharp/src/Bone.cs
index b588a9b5f..c760ec6ff 100644
--- a/spine-csharp/src/Bone.cs
+++ b/spine-csharp/src/Bone.cs
@@ -39,11 +39,15 @@ namespace Spine {
internal Bone parent;
internal ExposedList children = new ExposedList();
internal float x, y, rotation, scaleX, scaleY, shearX, shearY;
- internal float appliedRotation;
+ internal float ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY;
+ internal bool appliedValid;
internal float a, b, worldX;
internal float c, d, worldY;
- internal float worldSignX, worldSignY;
+
+// internal float worldSignX, worldSignY;
+// public float WorldSignX { get { return worldSignX; } }
+// public float WorldSignY { get { return worldSignY; } }
internal bool sorted;
@@ -55,7 +59,7 @@ namespace Spine {
public float Y { get { return y; } set { y = value; } }
public float Rotation { get { return rotation; } set { rotation = value; } }
/// The rotation, as calculated by any constraints.
- public float AppliedRotation { get { return appliedRotation; } set { appliedRotation = value; } }
+ public float AppliedRotation { get { return arotation; } set { arotation = value; } }
public float ScaleX { get { return scaleX; } set { scaleX = value; } }
public float ScaleY { get { return scaleY; } set { scaleY = value; } }
public float ShearX { get { return shearX; } set { shearX = value; } }
@@ -67,12 +71,13 @@ namespace Spine {
public float D { get { return d; } }
public float WorldX { get { return worldX; } }
public float WorldY { get { return worldY; } }
- public float WorldSignX { get { return worldSignX; } }
- public float WorldSignY { get { return worldSignY; } }
- public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.radDeg; } }
- public float WorldRotationY { get { return MathUtils.Atan2(d, b) * MathUtils.radDeg; } }
- public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c) * worldSignX; } }
- public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d) * worldSignY; } }
+ public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.RadDeg; } }
+ public float WorldRotationY { get { return MathUtils.Atan2(d, b) * MathUtils.RadDeg; } }
+
+ /// Returns the magnitide (always positive) of the world scale X.
+ public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c); } }
+ /// Returns the magnitide (always positive) of the world scale Y.
+ public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d); } }
/// May be null.
public Bone (BoneData data, Skeleton skeleton, Bone parent) {
@@ -96,15 +101,23 @@ namespace Spine {
/// Computes the world transform using the parent bone and the specified local transform.
public void UpdateWorldTransform (float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) {
- appliedRotation = rotation;
-
- float rotationY = rotation + 90 + shearY;
- float la = MathUtils.CosDeg(rotation + shearX) * scaleX, lb = MathUtils.CosDeg(rotationY) * scaleY;
- float lc = MathUtils.SinDeg(rotation + shearX) * scaleX, ld = MathUtils.SinDeg(rotationY) * scaleY;
+ ax = x;
+ ay = y;
+ arotation = rotation;
+ ascaleX = scaleX;
+ ascaleY = scaleY;
+ ashearX = shearX;
+ ashearY = shearY;
+ appliedValid = true;
+ Skeleton skeleton = this.skeleton;
Bone parent = this.parent;
if (parent == null) { // Root bone.
- Skeleton skeleton = this.skeleton;
+ float rotationY = rotation + 90 + shearY;
+ float la = MathUtils.CosDeg(rotation + shearX) * scaleX;
+ float lb = MathUtils.CosDeg(rotationY) * scaleY;
+ float lc = MathUtils.SinDeg(rotation + shearX) * scaleX;
+ float ld = MathUtils.SinDeg(rotationY) * scaleY;
if (skeleton.flipX) {
x = -x;
la = -la;
@@ -119,92 +132,101 @@ namespace Spine {
b = lb;
c = lc;
d = ld;
- worldX = x;
- worldY = y;
- worldSignX = Math.Sign(scaleX);
- worldSignY = Math.Sign(scaleY);
+ worldX = x + skeleton.x;
+ worldY = y + skeleton.y;
+// worldSignX = Math.Sign(scaleX);
+// worldSignY = Math.Sign(scaleY);
return;
}
float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
worldX = pa * x + pb * y + parent.worldX;
worldY = pc * x + pd * y + parent.worldY;
- worldSignX = parent.worldSignX * Math.Sign(scaleX);
- worldSignY = parent.worldSignY * Math.Sign(scaleY);
+// worldSignX = parent.worldSignX * Math.Sign(scaleX);
+// worldSignY = parent.worldSignY * Math.Sign(scaleY);
- if (data.inheritRotation && data.inheritScale) {
- a = pa * la + pb * lc;
- b = pa * lb + pb * ld;
- c = pc * la + pd * lc;
- d = pc * lb + pd * ld;
- } else {
- if (data.inheritRotation) { // No scale inheritance.
- pa = 1;
- pb = 0;
- pc = 0;
- pd = 1;
- do {
- float cos = MathUtils.CosDeg(parent.appliedRotation), sin = MathUtils.SinDeg(parent.appliedRotation);
- float temp = pa * cos + pb * sin;
- pb = pb * cos - pa * sin;
- pa = temp;
- temp = pc * cos + pd * sin;
- pd = pd * cos - pc * sin;
- pc = temp;
-
- if (!parent.data.inheritRotation) break;
- parent = parent.parent;
- } while (parent != null);
+ switch (data.transformMode) {
+ case TransformMode.Normal: {
+ float rotationY = rotation + 90 + shearY;
+ float la = MathUtils.CosDeg(rotation + shearX) * scaleX;
+ float lb = MathUtils.CosDeg(rotationY) * scaleY;
+ float lc = MathUtils.SinDeg(rotation + shearX) * scaleX;
+ float ld = MathUtils.SinDeg(rotationY) * scaleY;
a = pa * la + pb * lc;
b = pa * lb + pb * ld;
c = pc * la + pd * lc;
d = pc * lb + pd * ld;
- } else if (data.inheritScale) { // No rotation inheritance.
- pa = 1;
- pb = 0;
- pc = 0;
- pd = 1;
- do {
- float cos = MathUtils.CosDeg(parent.appliedRotation), sin = MathUtils.SinDeg(parent.appliedRotation);
- float psx = parent.scaleX, psy = parent.scaleY;
- float za = cos * psx, zb = sin * psy, zc = sin * psx, zd = cos * psy;
- float temp = pa * za + pb * zc;
- pb = pb * zd - pa * zb;
- pa = temp;
- temp = pc * za + pd * zc;
- pd = pd * zd - pc * zb;
- pc = temp;
-
- if (psx >= 0) sin = -sin;
- temp = pa * cos + pb * sin;
- pb = pb * cos - pa * sin;
- pa = temp;
- temp = pc * cos + pd * sin;
- pd = pd * cos - pc * sin;
- pc = temp;
-
- if (!parent.data.inheritScale) break;
- parent = parent.parent;
- } while (parent != null);
- a = pa * la + pb * lc;
- b = pa * lb + pb * ld;
+ return;
+ }
+ case TransformMode.OnlyTranslation: {
+ float rotationY = rotation + 90 + shearY;
+ a = MathUtils.CosDeg(rotation + shearX) * scaleX;
+ b = MathUtils.CosDeg(rotationY) * scaleY;
+ c = MathUtils.SinDeg(rotation + shearX) * scaleX;
+ d = MathUtils.SinDeg(rotationY) * scaleY;
+ break;
+ }
+ case TransformMode.NoRotationOrReflection: {
+ float s = pa * pa + pc * pc, prx;
+ if (s > 0.0001f) {
+ s = Math.Abs(pa * pd - pb * pc) / s;
+ pb = pc * s;
+ pd = pa * s;
+ prx = MathUtils.Atan2(pc, pa) * MathUtils.RadDeg;
+ } else {
+ pa = 0;
+ pc = 0;
+ prx = 90 - MathUtils.Atan2(pd, pb) * MathUtils.RadDeg;
+ }
+ float rx = rotation + shearX - prx;
+ float ry = rotation + shearY - prx + 90;
+ float la = MathUtils.CosDeg(rx) * scaleX;
+ float lb = MathUtils.CosDeg(ry) * scaleY;
+ float lc = MathUtils.SinDeg(rx) * scaleX;
+ float ld = MathUtils.SinDeg(ry) * scaleY;
+ a = pa * la - pb * lc;
+ b = pa * lb - pb * ld;
c = pc * la + pd * lc;
d = pc * lb + pd * ld;
- } else {
- a = la;
- b = lb;
- c = lc;
- d = ld;
+ break;
}
- if (skeleton.flipX) {
- a = -a;
- b = -b;
- }
- if (skeleton.flipY != yDown) {
- c = -c;
- d = -d;
+ case TransformMode.NoScale:
+ case TransformMode.NoScaleOrReflection: {
+ float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation);
+ float za = pa * cos + pb * sin;
+ float zc = pc * cos + pd * sin;
+ float s = (float)Math.Sqrt(za * za + zc * zc);
+ if (s > 0.00001f) s = 1 / s;
+ za *= s;
+ zc *= s;
+ s = (float)Math.Sqrt(za * za + zc * zc);
+ float r = MathUtils.PI / 2 + MathUtils.Atan2(zc, za);
+ float zb = MathUtils.Cos(r) * s;
+ float zd = MathUtils.Sin(r) * s;
+ float la = MathUtils.CosDeg(shearX) * scaleX;
+ float lb = MathUtils.CosDeg(90 + shearY) * scaleY;
+ float lc = MathUtils.SinDeg(shearX) * scaleX;
+ float ld = MathUtils.SinDeg(90 + shearY) * scaleY;
+ a = za * la + zb * lc;
+ b = za * lb + zb * ld;
+ c = zc * la + zd * lc;
+ d = zc * lb + zd * ld;
+ if (data.transformMode != TransformMode.NoScaleOrReflection ? pa * pd - pb * pc < 0 : skeleton.flipX != skeleton.flipY) {
+ b = -b;
+ d = -d;
+ }
+ return;
}
}
+
+ if (skeleton.flipX) {
+ a = -a;
+ b = -b;
+ }
+ if (skeleton.flipY) {
+ c = -c;
+ d = -d;
+ }
}
public void SetToSetupPose () {
@@ -221,18 +243,18 @@ namespace Spine {
public float WorldToLocalRotationX {
get {
Bone parent = this.parent;
- if (parent == null) return rotation;
+ if (parent == null) return arotation;
float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, a = this.a, c = this.c;
- return MathUtils.Atan2(pa * c - pc * a, pd * a - pb * c) * MathUtils.radDeg;
+ return MathUtils.Atan2(pa * c - pc * a, pd * a - pb * c) * MathUtils.RadDeg;
}
}
public float WorldToLocalRotationY {
get {
Bone parent = this.parent;
- if (parent == null) return rotation;
+ if (parent == null) return arotation;
float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, b = this.b, d = this.d;
- return MathUtils.Atan2(pa * d - pc * b, pd * b - pb * d) * MathUtils.radDeg;
+ return MathUtils.Atan2(pa * d - pc * b, pd * b - pb * d) * MathUtils.RadDeg;
}
}
@@ -243,33 +265,33 @@ namespace Spine {
this.b = cos * b - sin * d;
this.c = sin * a + cos * c;
this.d = sin * b + cos * d;
+ appliedValid = false;
}
///
- /// Computes the local transform from the world transform. This can be useful to perform processing on the local transform
- /// after the world transform has been modified directly (eg, by a constraint).
+ /// Computes the individual applied transform values from the world transform. This can be useful to perform processing using
+ /// the applied transform after the world transform has been modified directly (eg, by a constraint)..
///
- /// Some redundant information is lost by the world transform, such as -1,-1 scale versus 180 rotation. The computed local
- /// transform values may differ from the original values but are functionally the same.
+ /// Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation.
///
- public void UpdateLocalTransform () {
+ internal void UpdateAppliedTransform () {
+ appliedValid = true;
Bone parent = this.parent;
if (parent == null) {
- x = worldX;
- y = worldY;
- rotation = MathUtils.Atan2(c, a) * MathUtils.radDeg;
- scaleX = (float)Math.Sqrt(a * a + c * c);
- scaleY = (float)Math.Sqrt(b * b + d * d);
- float det = a * d - b * c;
- shearX = 0;
- shearY = MathUtils.Atan2(a * b + c * d, det) * MathUtils.radDeg;
+ ax = worldX;
+ ay = worldY;
+ arotation = MathUtils.Atan2(c, a) * MathUtils.RadDeg;
+ ascaleX = (float)Math.Sqrt(a * a + c * c);
+ ascaleY = (float)Math.Sqrt(b * b + d * d);
+ ashearX = 0;
+ ashearY = MathUtils.Atan2(a * b + c * d, a * d - b * c) * MathUtils.RadDeg;
return;
}
float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
float pid = 1 / (pa * pd - pb * pc);
float dx = worldX - parent.worldX, dy = worldY - parent.worldY;
- x = (dx * pd * pid - dy * pb * pid);
- y = (dy * pa * pid - dx * pc * pid);
+ ax = (dx * pd * pid - dy * pb * pid);
+ ay = (dy * pa * pid - dx * pc * pid);
float ia = pid * pd;
float id = pid * pa;
float ib = pid * pb;
@@ -278,23 +300,22 @@ namespace Spine {
float rb = ia * b - ib * d;
float rc = id * c - ic * a;
float rd = id * d - ic * b;
- shearX = 0;
- scaleX = (float)Math.Sqrt(ra * ra + rc * rc);
- if (scaleX > 0.0001f) {
+ ashearX = 0;
+ ascaleX = (float)Math.Sqrt(ra * ra + rc * rc);
+ if (ascaleX > 0.0001f) {
float det = ra * rd - rb * rc;
- scaleY = det / scaleX;
- shearY = MathUtils.Atan2(ra * rb + rc * rd, det) * MathUtils.radDeg;
- rotation = MathUtils.Atan2(rc, ra) * MathUtils.radDeg;
+ ascaleY = det / ascaleX;
+ ashearY = MathUtils.Atan2(ra * rb + rc * rd, det) * MathUtils.RadDeg;
+ arotation = MathUtils.Atan2(rc, ra) * MathUtils.RadDeg;
} else {
- scaleX = 0;
- scaleY = (float)Math.Sqrt(rb * rb + rd * rd);
- shearY = 0;
- rotation = 90 - MathUtils.Atan2(rd, rb) * MathUtils.radDeg;
+ ascaleX = 0;
+ ascaleY = (float)Math.Sqrt(rb * rb + rd * rd);
+ ashearY = 0;
+ arotation = 90 - MathUtils.Atan2(rd, rb) * MathUtils.RadDeg;
}
- appliedRotation = rotation;
}
- public void WorldToLocal (float worldX, float worldY, out float localX, out float localY) {
+ 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 x = worldX - this.worldX, y = worldY - this.worldY;
diff --git a/spine-csharp/src/BoneData.cs b/spine-csharp/src/BoneData.cs
index 1e2b8a6c6..3933374fd 100644
--- a/spine-csharp/src/BoneData.cs
+++ b/spine-csharp/src/BoneData.cs
@@ -37,7 +37,8 @@ namespace Spine {
internal BoneData parent;
internal float length;
internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY;
- internal bool inheritRotation = true, inheritScale = true;
+ internal TransformMode transformMode = TransformMode.Normal;
+ //internal bool inheritRotation = true, inheritScale = true;
/// May be null.
public int Index { get { return index; } }
@@ -51,8 +52,10 @@ namespace Spine {
public float ScaleY { get { return scaleY; } set { scaleY = value; } }
public float ShearX { get { return shearX; } set { shearX = value; } }
public float ShearY { get { return shearY; } set { shearY = value; } }
- public bool InheritRotation { get { return inheritRotation; } set { inheritRotation = value; } }
- public bool InheritScale { get { return inheritScale; } set { inheritScale = value; } }
+ public TransformMode TransformMode { get { return transformMode; } set { transformMode = value; } }
+// public bool InheritRotation { get { return inheritRotation; } set { inheritRotation = value; } }
+// public bool InheritScale { get { return inheritScale; } set { inheritScale = value; } }
+
/// May be null.
public BoneData (int index, String name, BoneData parent) {
@@ -67,4 +70,14 @@ namespace Spine {
return name;
}
}
+
+ [Flags]
+ public enum TransformMode {
+ //0000 0FSR
+ Normal = 0, // 0000
+ OnlyTranslation = 7, // 0111
+ NoRotationOrReflection = 1, // 0001
+ NoScale = 2, // 0010
+ NoScaleOrReflection = 6, // 0110
+ }
}
diff --git a/spine-csharp/src/Event.cs b/spine-csharp/src/Event.cs
index 71c6da50b..9d1df7497 100644
--- a/spine-csharp/src/Event.cs
+++ b/spine-csharp/src/Event.cs
@@ -32,20 +32,26 @@ using System;
namespace Spine {
public class Event {
- public EventData Data { get; private set; }
- public int Int { get; set; }
- public float Float { get; set; }
- public String String { get; set; }
- public float Time { get; private set; }
+ internal readonly EventData data;
+ internal readonly float time;
+ internal int intValue;
+ internal float floatValue;
+ internal string stringValue;
+
+ public EventData Data { get { return data; } }
+ public float Time { get { return time; } }
+ public int Int { get { return intValue; } set { intValue = value; } }
+ public float Float { get { return floatValue; } set { floatValue = value; } }
+ public String String { get { return stringValue; } set { stringValue = value; } }
public Event (float time, EventData data) {
if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
- Time = time;
- Data = data;
+ this.time = time;
+ this.data = data;
}
- override public String ToString () {
- return Data.Name;
+ override public string ToString () {
+ return this.data.Name;
}
}
}
diff --git a/spine-csharp/src/ExposedList.cs b/spine-csharp/src/ExposedList.cs
index a24db4a93..421c6a2c3 100644
--- a/spine-csharp/src/ExposedList.cs
+++ b/spine-csharp/src/ExposedList.cs
@@ -95,6 +95,15 @@ namespace Spine {
return this;
}
+ public void EnsureCapacity (int min) {
+ if (Items.Length < min) {
+ int newCapacity = Items.Length == 0 ? DefaultCapacity : Items.Length * 2;
+ //if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength;
+ if (newCapacity < min) newCapacity = min;
+ Capacity = newCapacity;
+ }
+ }
+
private void CheckRange (int idx, int count) {
if (idx < 0)
throw new ArgumentOutOfRangeException("index");
@@ -182,6 +191,8 @@ namespace Spine {
Array.Copy(Items, index, array, arrayIndex, count);
}
+
+
public bool Exists (Predicate match) {
CheckMatch(match);
return GetIndex(0, Count, match) != -1;
diff --git a/spine-csharp/src/IConstraint.cs b/spine-csharp/src/IConstraint.cs
new file mode 100644
index 000000000..25f375049
--- /dev/null
+++ b/spine-csharp/src/IConstraint.cs
@@ -0,0 +1,5 @@
+namespace Spine {
+ public interface IConstraint : IUpdatable {
+ int Order { get; }
+ }
+}
\ No newline at end of file
diff --git a/spine-csharp/src/IkConstraint.cs b/spine-csharp/src/IkConstraint.cs
index 2c3b4d833..a9822bfb6 100644
--- a/spine-csharp/src/IkConstraint.cs
+++ b/spine-csharp/src/IkConstraint.cs
@@ -31,16 +31,15 @@
using System;
namespace Spine {
- public class IkConstraint : IUpdatable {
+ public class IkConstraint : IConstraint {
internal IkConstraintData data;
internal ExposedList bones = new ExposedList();
internal Bone target;
internal float mix;
internal int bendDirection;
- internal int level;
-
public IkConstraintData Data { get { return data; } }
+ public int Order { get { return data.order; } }
public ExposedList Bones { get { return bones; } }
public Bone Target { get { return target; } set { target = value; } }
public int BendDirection { get { return bendDirection; } set { bendDirection = value; } }
@@ -83,17 +82,18 @@ namespace Spine {
/// Adjusts the bone rotation so the tip is as close to the target position as possible. The target is specified
/// in the world coordinate system.
static public void Apply (Bone bone, float targetX, float targetY, float alpha) {
- Bone pp = bone.parent;
- float id = 1 / (pp.a * pp.d - pp.b * pp.c);
- float x = targetX - pp.worldX, y = targetY - pp.worldY;
- float tx = (x * pp.d - y * pp.b) * id - bone.x, ty = (y * pp.a - x * pp.c) * id - bone.y;
- float rotationIK = MathUtils.Atan2(ty, tx) * MathUtils.radDeg - bone.shearX - bone.rotation;
- if (bone.scaleX < 0) rotationIK += 180;
+ if (!bone.appliedValid) bone.UpdateAppliedTransform();
+ Bone p = bone.parent;
+ float id = 1 / (p.a * p.d - p.b * p.c);
+ float x = targetX - p.worldX, y = targetY - p.worldY;
+ float tx = (x * p.d - y * p.b) * id - bone.ax, ty = (y * p.a - x * p.c) * id - bone.ay;
+ float rotationIK = MathUtils.Atan2(ty, tx) * MathUtils.RadDeg - bone.ashearX - bone.arotation;
+ if (bone.ascaleX < 0) rotationIK += 180;
if (rotationIK > 180)
rotationIK -= 360;
else if (rotationIK < -180) rotationIK += 360;
- bone.UpdateWorldTransform(bone.x, bone.y, bone.rotation + rotationIK * alpha, bone.scaleX, bone.scaleY,
- bone.shearX, bone.shearY);
+ bone.UpdateWorldTransform(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, bone.ascaleX, bone.ascaleY, bone.ashearX,
+ bone.ashearY);
}
/// Adjusts the parent and child bone rotations so the tip of the child is as close to the target position as
@@ -104,7 +104,10 @@ namespace Spine {
child.UpdateWorldTransform ();
return;
}
- float px = parent.x, py = parent.y, psx = parent.scaleX, psy = parent.scaleY, csx = child.scaleX;
+ //float px = parent.x, py = parent.y, psx = parent.scaleX, psy = parent.scaleY, csx = child.scaleX;
+ if (!parent.appliedValid) parent.UpdateAppliedTransform();
+ if (!child.appliedValid) child.UpdateAppliedTransform();
+ float px = parent.ax, py = parent.ay, psx = parent.ascaleX, psy = parent.ascaleY, csx = child.ascaleX;
int os1, os2, s2;
if (psx < 0) {
psx = -psx;
@@ -123,14 +126,14 @@ namespace Spine {
os2 = 180;
} else
os2 = 0;
- float cx = child.x, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d;
+ float cx = child.ax, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d;
bool u = Math.Abs(psx - psy) <= 0.0001f;
if (!u) {
cy = 0;
cwx = a * cx + parent.worldX;
cwy = c * cx + parent.worldY;
} else {
- cy = child.y;
+ cy = child.ay;
cwx = a * cx + b * cy + parent.worldX;
cwy = c * cx + d * cy + parent.worldY;
}
@@ -217,18 +220,18 @@ namespace Spine {
}
outer:
float os = MathUtils.Atan2(cy, cx) * s2;
- float rotation = parent.rotation;
- a1 = (a1 - os) * MathUtils.radDeg + os1 - rotation;
+ float rotation = parent.arotation;
+ a1 = (a1 - os) * MathUtils.RadDeg + os1 - rotation;
if (a1 > 180)
a1 -= 360;
else if (a1 < -180) a1 += 360;
- parent.UpdateWorldTransform(px, py, rotation + a1 * alpha, parent.scaleX, parent.scaleY, 0, 0);
- rotation = child.rotation;
- a2 = ((a2 + os) * MathUtils.radDeg - child.shearX) * s2 + os2 - rotation;
+ parent.UpdateWorldTransform(px, py, rotation + a1 * alpha, parent.scaleX, parent.ascaleY, 0, 0);
+ rotation = child.arotation;
+ a2 = ((a2 + os) * MathUtils.RadDeg - child.ashearX) * s2 + os2 - rotation;
if (a2 > 180)
a2 -= 360;
else if (a2 < -180) a2 += 360;
- child.UpdateWorldTransform(cx, cy, rotation + a2 * alpha, child.scaleX, child.scaleY, child.shearX, child.shearY);
+ child.UpdateWorldTransform(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY);
}
}
}
diff --git a/spine-csharp/src/IkConstraintData.cs b/spine-csharp/src/IkConstraintData.cs
index fc7591290..52ae64664 100644
--- a/spine-csharp/src/IkConstraintData.cs
+++ b/spine-csharp/src/IkConstraintData.cs
@@ -34,12 +34,14 @@ using System.Collections.Generic;
namespace Spine {
public class IkConstraintData {
internal String name;
+ internal int order;
internal List bones = new List();
internal BoneData target;
internal int bendDirection = 1;
internal float mix = 1;
public String Name { get { return name; } }
+ public int Order { get { return order; } set { order = value; } }
public List Bones { get { return bones; } }
public BoneData Target { get { return target; } set { target = value; } }
public int BendDirection { get { return bendDirection; } set { bendDirection = value; } }
diff --git a/spine-csharp/src/Json.cs b/spine-csharp/src/Json.cs
index 3534339ea..8dd10604b 100644
--- a/spine-csharp/src/Json.cs
+++ b/spine-csharp/src/Json.cs
@@ -54,7 +54,7 @@ namespace Spine {
*
* Changes made:
*
- * - Optimized parser speed (deserialize roughly near 3x faster than original)
+ * - Optimized parser speed (deserialize roughly near 3x faster than original)
* - Added support to handle lexer/parser error messages with line numbers
* - Added more fine grained control over type conversions during the parsing
* - Refactory API (Separate Lexer code from Parser code and the Encoder from Decoder)
@@ -133,19 +133,19 @@ namespace SharpJson
{
int idx = 0;
StringBuilder builder = null;
-
+
SkipWhiteSpaces();
-
+
// "
char c = json[index++];
-
+
bool failed = false;
bool complete = false;
-
+
while (!complete && !failed) {
if (index == json.Length)
break;
-
+
c = json[index++];
if (c == '"') {
complete = true;
@@ -153,9 +153,9 @@ namespace SharpJson
} else if (c == '\\') {
if (index == json.Length)
break;
-
+
c = json[index++];
-
+
switch (c) {
case '"':
stringBuffer[idx++] = '"';
@@ -185,10 +185,10 @@ namespace SharpJson
int remainingLength = json.Length - index;
if (remainingLength >= 4) {
var hex = new string(json, index, 4);
-
+
// XXX: handle UTF
stringBuffer[idx++] = (char) Convert.ToInt32(hex, 16);
-
+
// skip 4 chars
index += 4;
} else {
@@ -199,38 +199,38 @@ namespace SharpJson
} else {
stringBuffer[idx++] = c;
}
-
+
if (idx >= stringBuffer.Length) {
if (builder == null)
builder = new StringBuilder();
-
+
builder.Append(stringBuffer, 0, idx);
idx = 0;
}
}
-
+
if (!complete) {
success = false;
return null;
}
-
+
if (builder != null)
return builder.ToString ();
else
return new string (stringBuffer, 0, idx);
}
-
+
string GetNumberString()
{
SkipWhiteSpaces();
int lastIndex = GetLastIndexOfNumber(index);
int charLength = (lastIndex - index) + 1;
-
+
var result = new string (json, index, charLength);
-
+
index = lastIndex + 1;
-
+
return result;
}
@@ -238,10 +238,10 @@ namespace SharpJson
{
float number;
var str = GetNumberString ();
-
+
if (!float.TryParse (str, NumberStyles.Float, CultureInfo.InvariantCulture, out number))
return 0;
-
+
return number;
}
@@ -249,24 +249,25 @@ namespace SharpJson
{
double number;
var str = GetNumberString ();
-
+
if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number))
return 0;
-
+
return number;
}
-
+
int GetLastIndexOfNumber(int index)
{
int lastIndex;
-
+
for (lastIndex = index; lastIndex < json.Length; lastIndex++) {
char ch = json[lastIndex];
-
- if ((ch < '0' || ch > '9') && ch != '+' && ch != '-' && ch != '.' && ch != 'e' && ch != 'E')
+
+ if ((ch < '0' || ch > '9') && ch != '+' && ch != '-'
+ && ch != '.' && ch != 'e' && ch != 'E')
break;
}
-
+
return lastIndex - 1;
}
@@ -301,9 +302,9 @@ namespace SharpJson
{
if (index == json.Length)
return Token.None;
-
+
char c = json[index++];
-
+
switch (c) {
case '{':
return Token.CurlyOpen;
@@ -326,41 +327,38 @@ namespace SharpJson
}
index--;
-
+
int remainingLength = json.Length - index;
-
+
// false
if (remainingLength >= 5) {
if (json[index] == 'f' &&
- json[index + 1] == 'a' &&
- json[index + 2] == 'l' &&
- json[index + 3] == 's' &&
- json[index + 4] == 'e'
- ) {
+ json[index + 1] == 'a' &&
+ json[index + 2] == 'l' &&
+ json[index + 3] == 's' &&
+ json[index + 4] == 'e') {
index += 5;
return Token.False;
}
}
-
+
// true
if (remainingLength >= 4) {
if (json[index] == 't' &&
- json[index + 1] == 'r' &&
- json[index + 2] == 'u' &&
- json[index + 3] == 'e'
- ) {
+ json[index + 1] == 'r' &&
+ json[index + 2] == 'u' &&
+ json[index + 3] == 'e') {
index += 4;
return Token.True;
}
}
-
+
// null
if (remainingLength >= 4) {
if (json[index] == 'n' &&
- json[index + 1] == 'u' &&
- json[index + 2] == 'l' &&
- json[index + 3] == 'l'
- ) {
+ json[index + 1] == 'u' &&
+ json[index + 2] == 'l' &&
+ json[index + 3] == 'l') {
index += 4;
return Token.Null;
}
@@ -440,25 +438,25 @@ namespace SharpJson
TriggerError("Invalid token; expected ':'");
return null;
}
-
+
// value
object value = ParseValue();
if (errorMessage != null)
return null;
-
+
table[name] = value;
break;
}
}
-
+
//return null; // Unreachable code
}
IList
+ public event UpdateBonesDelegate UpdateComplete { add { _UpdateComplete += value; } remove { _UpdateComplete -= value; } }
+ #endregion
+ #region Serialized state and Beginner API
[SerializeField]
[SpineAnimation]
- private String _animationName;
- public String AnimationName {
+ private string _animationName;
+ public string AnimationName {
get {
if (!valid) {
Debug.LogWarning("You tried access AnimationName but the SkeletonAnimation was not valid. Try checking your Skeleton Data for errors.");
@@ -107,15 +104,14 @@ namespace Spine.Unity {
}
}
- /// Whether or not an animation should loop. This only applies to the initial animation specified in the inspector, or any subsequent Animations played through .AnimationName. Animations set through state.SetAnimation are unaffected.
- [Tooltip("Whether or not an animation should loop. This only applies to the initial animation specified in the inspector, or any subsequent Animations played through .AnimationName. Animations set through state.SetAnimation are unaffected.")]
+ /// Whether or not should loop. This only applies to the initial animation specified in the inspector, or any subsequent Animations played through .AnimationName. Animations set through state.SetAnimation are unaffected.
public bool loop;
///
/// The rate at which animations progress over time. 1 means 100%. 0.5 means 50%.
/// AnimationState and TrackEntry also have their own timeScale. These are combined multiplicatively.
- [Tooltip("The rate at which animations progress over time. 1 means 100%. 0.5 means 50%.")]
public float timeScale = 1;
+ #endregion
#region Runtime Instantiation
/// Adds and prepares a SkeletonAnimation component to a GameObject at runtime.
@@ -131,6 +127,11 @@ namespace Spine.Unity {
}
#endregion
+ protected override void ClearState () {
+ base.ClearState();
+ state.ClearTracks();
+ }
+
public override void Initialize (bool overwrite) {
if (valid && !overwrite)
return;
@@ -150,7 +151,7 @@ namespace Spine.Unity {
// Assume SkeletonAnimation is valid for skeletonData and skeleton. Checked above.
var animationObject = skeletonDataAsset.GetSkeletonData(false).FindAnimation(_animationName);
if (animationObject != null)
- animationObject.Apply(skeleton, 0f, 0f, false, null);
+ animationObject.Apply(skeleton, 0f, 0f, false, null, 1f, true, false);
}
Update(0);
}
diff --git a/spine-unity/Assets/spine-unity/SkeletonAnimator.cs b/spine-unity/Assets/spine-unity/SkeletonAnimator.cs
index b8fbc717b..f652b8413 100644
--- a/spine-unity/Assets/spine-unity/SkeletonAnimator.cs
+++ b/spine-unity/Assets/spine-unity/SkeletonAnimator.cs
@@ -30,8 +30,6 @@
// Contributed by: Mitch Thompson
-//#define USE_SPINE_EVENTS // Uncomment this define to use C# events to handle Spine events. (Does not disable Unity AnimationClip Events)
-
using UnityEngine;
using System.Collections.Generic;
@@ -42,251 +40,146 @@ namespace Spine.Unity {
public enum MixMode { AlwaysMix, MixNext, SpineStyle }
public MixMode[] layerMixModes = new MixMode[0];
- ///
- /// Occurs after the animations are applied and before world space values are resolved.
- /// Use this callback when you want to set bone local values.
- ///
- public event UpdateBonesDelegate UpdateLocal {
- add { _UpdateLocal += value; }
- remove { _UpdateLocal -= value; }
- }
-
- ///
- /// Occurs after the Skeleton's bone world space values are resolved (including all constraints).
- /// Using this callback will cause the world space values to be solved an extra time.
- /// Use this callback if want to use bone world space values, and also set bone local values.
- ///
- public event UpdateBonesDelegate UpdateWorld {
- add { _UpdateWorld += value; }
- remove { _UpdateWorld -= value; }
- }
-
- ///
- /// Occurs after the Skeleton's bone world space values are resolved (including all constraints).
- /// Use this callback if you want to use bone world space values, but don't intend to modify bone local values.
- /// This callback can also be used when setting world position and the bone matrix.
- ///
- public event UpdateBonesDelegate UpdateComplete {
- add { _UpdateComplete += value; }
- remove { _UpdateComplete -= value; }
- }
-
+ #region Bone Callbacks (ISkeletonAnimation)
protected event UpdateBonesDelegate _UpdateLocal;
protected event UpdateBonesDelegate _UpdateWorld;
protected event UpdateBonesDelegate _UpdateComplete;
+ ///
+ /// Occurs after the animations are applied and before world space values are resolved.
+ /// Use this callback when you want to set bone local values.
+ public event UpdateBonesDelegate UpdateLocal { add { _UpdateLocal += value; } remove { _UpdateLocal -= value; } }
+
+ ///
+ /// Occurs after the Skeleton's bone world space values are resolved (including all constraints).
+ /// Using this callback will cause the world space values to be solved an extra time.
+ /// Use this callback if want to use bone world space values, and also set bone local values.
+ public event UpdateBonesDelegate UpdateWorld { add { _UpdateWorld += value; } remove { _UpdateWorld -= value; } }
+
+ ///
+ /// Occurs after the Skeleton's bone world space values are resolved (including all constraints).
+ /// Use this callback if you want to use bone world space values, but don't intend to modify bone local values.
+ /// This callback can also be used when setting world position and the bone matrix.
+ public event UpdateBonesDelegate UpdateComplete { add { _UpdateComplete += value; } remove { _UpdateComplete -= value; } }
+ #endregion
+
readonly Dictionary animationTable = new Dictionary();
readonly Dictionary clipNameHashCodeTable = new Dictionary();
Animator animator;
- float lastTime;
-
- #if USE_SPINE_EVENTS
- public delegate void SkeletonAnimatorEventDelegate (Spine.Event firedEvent, float weight);
- public event SkeletonAnimatorEventDelegate AnimationEvent;
- public readonly ExposedList events = new ExposedList();
- #else
- public readonly ExposedList events = null;
- #endif
public override void Initialize (bool overwrite) {
- if (valid && !overwrite)
- return;
-
+ if (valid && !overwrite) return;
base.Initialize(overwrite);
-
- if (!valid)
- return;
+ if (!valid) return;
animationTable.Clear();
clipNameHashCodeTable.Clear();
-
- var data = skeletonDataAsset.GetSkeletonData(true);
-
- foreach (var a in data.Animations) {
- animationTable.Add(a.Name.GetHashCode(), a);
- }
-
animator = GetComponent();
-
- lastTime = Time.time;
+ var data = skeletonDataAsset.GetSkeletonData(true);
+ foreach (var a in data.Animations)
+ animationTable.Add(a.Name.GetHashCode(), a);
}
void Update () {
- if (!valid)
- return;
+ if (!valid) return;
- if (layerMixModes.Length != animator.layerCount) {
+ if (layerMixModes.Length < animator.layerCount)
System.Array.Resize(ref layerMixModes, animator.layerCount);
- }
- float deltaTime = Time.time - lastTime;
+
+ //skeleton.Update(Time.deltaTime); // Doesn't actually do anything, currently. (Spine 3.5).
- skeleton.Update(Time.deltaTime);
-
- //apply
- int layerCount = animator.layerCount;
-
- for (int i = 0; i < layerCount; i++) {
-
- float layerWeight = animator.GetLayerWeight(i);
- if (i == 0)
- layerWeight = 1;
-
- var stateInfo = animator.GetCurrentAnimatorStateInfo(i);
- var nextStateInfo = animator.GetNextAnimatorStateInfo(i);
+ // Apply
+ for (int layer = 0, n = animator.layerCount; layer < n; layer++) {
+ float layerWeight = (layer == 0) ? 1 : animator.GetLayerWeight(layer);
+ AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(layer);
+ AnimatorStateInfo nextStateInfo = animator.GetNextAnimatorStateInfo(layer);
#if UNITY_5
- var clipInfo = animator.GetCurrentAnimatorClipInfo(i);
- var nextClipInfo = animator.GetNextAnimatorClipInfo(i);
+ bool hasNext = nextStateInfo.fullPathHash != 0;
+ AnimatorClipInfo[] clipInfo = animator.GetCurrentAnimatorClipInfo(layer);
+ AnimatorClipInfo[] nextClipInfo = animator.GetNextAnimatorClipInfo(layer);
#else
+ bool hasNext = nextStateInfo.nameHash != 0;
var clipInfo = animator.GetCurrentAnimationClipState(i);
var nextClipInfo = animator.GetNextAnimationClipState(i);
#endif
- MixMode mode = layerMixModes[i];
+ MixMode mode = layerMixModes[layer];
if (mode == MixMode.AlwaysMix) {
- //always use Mix instead of Applying the first non-zero weighted clip
+ // Always use Mix instead of Applying the first non-zero weighted clip.
for (int c = 0; c < clipInfo.Length; c++) {
- var info = clipInfo[c];
- float weight = info.weight * layerWeight;
- if (weight == 0)
- continue;
-
- float time = stateInfo.normalizedTime * info.clip.length;
- animationTable[GetAnimationClipNameHashCode(info.clip)].Mix(skeleton, Mathf.Max(0, time - deltaTime), time, stateInfo.loop, events, weight);
- #if USE_SPINE_EVENTS
- FireEvents(events, weight, this.AnimationEvent);
- #endif
+ var info = clipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue;
+ animationTable[NameHashCode(info.clip)].Apply(skeleton, 0, AnimationTime(stateInfo.normalizedTime, info.clip.length), stateInfo.loop, null, weight, false, false);
}
- #if UNITY_5
- if (nextStateInfo.fullPathHash != 0) {
- #else
- if (nextStateInfo.nameHash != 0) {
- #endif
+ if (hasNext) {
for (int c = 0; c < nextClipInfo.Length; c++) {
- var info = nextClipInfo[c];
- float weight = info.weight * layerWeight;
- if (weight == 0)
- continue;
-
- float time = nextStateInfo.normalizedTime * info.clip.length;
- animationTable[GetAnimationClipNameHashCode(info.clip)].Mix(skeleton, Mathf.Max(0, time - deltaTime), time, nextStateInfo.loop, events, weight);
- #if USE_SPINE_EVENTS
- FireEvents(events, weight, this.AnimationEvent);
- #endif
+ var info = nextClipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue;
+ animationTable[NameHashCode(info.clip)].Apply(skeleton, 0, nextStateInfo.normalizedTime * info.clip.length, nextStateInfo.loop, null, weight, false, false);
}
}
- } else if (mode >= MixMode.MixNext) {
- //apply first non-zero weighted clip
+ } else { // case MixNext || SpineStyle
+ // Apply first non-zero weighted clip
int c = 0;
-
for (; c < clipInfo.Length; c++) {
- var info = clipInfo[c];
- float weight = info.weight * layerWeight;
- if (weight == 0)
- continue;
-
- float time = stateInfo.normalizedTime * info.clip.length;
- animationTable[GetAnimationClipNameHashCode(info.clip)].Apply(skeleton, Mathf.Max(0, time - deltaTime), time, stateInfo.loop, events);
- #if USE_SPINE_EVENTS
- FireEvents(events, weight, this.AnimationEvent);
- #endif
+ var info = clipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue;
+ animationTable[NameHashCode(info.clip)].Apply(skeleton, 0, AnimationTime(stateInfo.normalizedTime, info.clip.length), stateInfo.loop, null, 1f, false, false);
break;
}
-
- //mix the rest
+ // Mix the rest
for (; c < clipInfo.Length; c++) {
- var info = clipInfo[c];
- float weight = info.weight * layerWeight;
- if (weight == 0)
- continue;
-
- float time = stateInfo.normalizedTime * info.clip.length;
- animationTable[GetAnimationClipNameHashCode(info.clip)].Mix(skeleton, Mathf.Max(0, time - deltaTime), time, stateInfo.loop, events, weight);
- #if USE_SPINE_EVENTS
- FireEvents(events, weight, this.AnimationEvent);
- #endif
+ var info = clipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue;
+ animationTable[NameHashCode(info.clip)].Apply(skeleton, 0, AnimationTime(stateInfo.normalizedTime, info.clip.length), stateInfo.loop, null, weight, false, false);
}
c = 0;
- #if UNITY_5
- if (nextStateInfo.fullPathHash != 0) {
- #else
- if (nextStateInfo.nameHash != 0) {
- #endif
- //apply next clip directly instead of mixing (ie: no crossfade, ignores mecanim transition weights)
+ if (hasNext) {
+ // Apply next clip directly instead of mixing (ie: no crossfade, ignores mecanim transition weights)
if (mode == MixMode.SpineStyle) {
for (; c < nextClipInfo.Length; c++) {
- var info = nextClipInfo[c];
- float weight = info.weight * layerWeight;
- if (weight == 0)
- continue;
-
- float time = nextStateInfo.normalizedTime * info.clip.length;
- animationTable[GetAnimationClipNameHashCode(info.clip)].Apply(skeleton, Mathf.Max(0, time - deltaTime), time, nextStateInfo.loop, events);
- #if USE_SPINE_EVENTS
- FireEvents(events, weight, this.AnimationEvent);
- #endif
+ var info = nextClipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue;
+ animationTable[NameHashCode(info.clip)].Apply(skeleton, 0, nextStateInfo.normalizedTime * info.clip.length, nextStateInfo.loop, null, 1f, false, false);
break;
}
}
-
- //mix the rest
+ // Mix the rest
for (; c < nextClipInfo.Length; c++) {
- var info = nextClipInfo[c];
- float weight = info.weight * layerWeight;
- if (weight == 0)
- continue;
-
- float time = nextStateInfo.normalizedTime * info.clip.length;
- animationTable[GetAnimationClipNameHashCode(info.clip)].Mix(skeleton, Mathf.Max(0, time - deltaTime), time, nextStateInfo.loop, events, weight);
- #if USE_SPINE_EVENTS
- FireEvents(events, weight, this.AnimationEvent);
- #endif
+ var info = nextClipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue;
+ animationTable[NameHashCode(info.clip)].Apply(skeleton, 0, nextStateInfo.normalizedTime * info.clip.length, nextStateInfo.loop, null, weight, false, false);
}
}
}
}
- if (_UpdateLocal != null)
- _UpdateLocal(this);
+ // UpdateWorldTransform and Bone Callbacks
+ {
+ if (_UpdateLocal != null)
+ _UpdateLocal(this);
- skeleton.UpdateWorldTransform();
-
- if (_UpdateWorld != null) {
- _UpdateWorld(this);
skeleton.UpdateWorldTransform();
- }
- if (_UpdateComplete != null) {
- _UpdateComplete(this);
- }
+ if (_UpdateWorld != null) {
+ _UpdateWorld(this);
+ skeleton.UpdateWorldTransform();
+ }
- lastTime = Time.time;
+ if (_UpdateComplete != null)
+ _UpdateComplete(this);
+ }
}
- private int GetAnimationClipNameHashCode (AnimationClip clip) {
+ static float AnimationTime (float normalizedTime, float clipLength) {
+ float time = normalizedTime * clipLength;
+ const float EndSnapEpsilon = 1f/30f; // Workaround for end-duration keys not being applied.
+ return (clipLength - time < EndSnapEpsilon) ? clipLength : time; // return a time snapped to clipLength;
+ }
+
+ int NameHashCode (AnimationClip clip) {
int clipNameHashCode;
if (!clipNameHashCodeTable.TryGetValue(clip, out clipNameHashCode)) {
clipNameHashCode = clip.name.GetHashCode();
clipNameHashCodeTable.Add(clip, clipNameHashCode);
}
-
return clipNameHashCode;
}
-
- #if USE_SPINE_EVENTS
- static void FireEvents (ExposedList eventList, float weight, SkeletonAnimatorEventDelegate callback) {
- int eventsCount = eventList.Count;
- if (eventsCount > 0) {
- var eventListItems = eventList.Items;
- for (int i = 0; i < eventsCount; i++) {
- if (callback != null)
- callback(eventListItems[i], weight);
- }
-
- eventList.Clear(false);
- }
- }
- #endif
}
}
diff --git a/spine-unity/Assets/spine-unity/SkeletonExtensions.cs b/spine-unity/Assets/spine-unity/SkeletonExtensions.cs
index 99f3943ba..d9ae59846 100644
--- a/spine-unity/Assets/spine-unity/SkeletonExtensions.cs
+++ b/spine-unity/Assets/spine-unity/SkeletonExtensions.cs
@@ -36,9 +36,8 @@ using Spine;
namespace Spine.Unity {
public static class SkeletonExtensions {
- const float ByteToFloat = 1f / 255f;
-
#region Colors
+ const float ByteToFloat = 1f / 255f;
public static Color GetColor (this Skeleton s) { return new Color(s.r, s.g, s.b, s.a); }
public static Color GetColor (this RegionAttachment a) { return new Color(a.r, a.g, a.b, a.a); }
public static Color GetColor (this MeshAttachment a) { return new Color(a.r, a.g, a.b, a.a); }
@@ -111,6 +110,10 @@ namespace Spine.Unity {
bone.Y = position.y;
}
+ public static Vector2 GetLocalPosition (this Bone bone) {
+ return new Vector2(bone.x, bone.y);
+ }
+
public static Vector2 GetSkeletonSpacePosition (this Bone bone) {
return new Vector2(bone.worldX, bone.worldY);
}
@@ -128,24 +131,65 @@ namespace Spine.Unity {
}
#endregion
+ #region Attachments
+ public static Material GetMaterial (this Attachment a) {
+ var regionAttachment = a as RegionAttachment;
+ if (regionAttachment != null)
+ return (Material)((AtlasRegion)regionAttachment.RendererObject).page.rendererObject;
+
+ var meshAttachment = a as MeshAttachment;
+ if (meshAttachment != null)
+ return (Material)((AtlasRegion)meshAttachment.RendererObject).page.rendererObject;
+
+ return null;
+ }
+
+ /// Calculates world vertices and fills a Vector2 buffer.
+ /// The VertexAttachment
+ /// Slot where
+ /// Correctly-sized buffer. Use attachment's .WorldVerticesLength to get the correct size. If null, a new Vector2[] of the correct size will be allocated.
+ public static Vector2[] GetWorldVertices (this VertexAttachment a, Slot slot, Vector2[] buffer) {
+ int worldVertsLength = a.worldVerticesLength;
+ int bufferTargetSize = worldVertsLength >> 1;
+ buffer = buffer ?? new Vector2[bufferTargetSize];
+ if (buffer.Length < bufferTargetSize) throw new System.ArgumentException(string.Format("Vector2 buffer too small. {0} requires an array of size {1}. Use the attachment's .WorldVerticesLength to get the correct size.", a.Name, worldVertsLength), "buffer");
+
+ var floats = new float[worldVertsLength];
+ a.ComputeWorldVertices(slot, floats);
+
+ for (int i = 0, n = worldVertsLength >> 1; i < n; i++) {
+ int j = i * 2;
+ buffer[i] = new Vector2(floats[j], floats[j + 1]);
+ }
+
+ return buffer;
+ }
+ #endregion
}
}
namespace Spine {
public static class SkeletonExtensions {
+ public static bool IsWeighted (this VertexAttachment va) {
+ return va.bones != null && va.bones.Length > 0;
+ }
+
+ #region Transform Modes
+ public static bool InheritsRotation (this TransformMode mode) {
+ const int RotationBit = 0;
+ return ((int)mode & (1U << RotationBit)) == 0;
+ }
+
+ public static bool InheritsScale (this TransformMode mode) {
+ const int ScaleBit = 1;
+ return ((int)mode & (1U << ScaleBit)) == 0;
+ }
+ #endregion
+
#region Posing
- ///
- /// Shortcut for posing a skeleton at a specific time. Time is in seconds. (frameNumber / 30f) will give you seconds.
- /// If you need to do this often, you should get the Animation object yourself using skeleton.data.FindAnimation. and call Apply on that.
- /// The skeleton to pose.
- /// The name of the animation to use.
- /// The time of the pose within the animation.
- /// Wraps the time around if it is longer than the duration of the animation.
- public static void PoseWithAnimation (this Skeleton skeleton, string animationName, float time, bool loop) {
- // Fail loud when skeleton.data is null.
- Spine.Animation animation = skeleton.data.FindAnimation(animationName);
- if (animation == null) return;
- animation.Apply(skeleton, 0, time, loop, null);
+ [System.Obsolete("Old Animation.Apply method signature. Please use the 8 parameter signature. See summary to learn about the extra arguments.")]
+ public static void Apply (this Spine.Animation animation, Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events) {
+ animation.Apply(skeleton, lastTime, time, loop, events, 1f, false, false);
}
/// Resets the DrawOrder to the Setup Pose's draw order
@@ -157,7 +201,6 @@ namespace Spine {
drawOrder.Clear(false);
drawOrder.GrowIfNeeded(n);
System.Array.Copy(slotsItems, drawOrder.Items, n);
- drawOrder.Count = n;
}
/// Resets the color of a slot to Setup Pose value.
@@ -177,69 +220,32 @@ namespace Spine {
/// Resets the attachment of slot at a given slotIndex to setup pose. This is faster than Slot.SetAttachmentToSetupPose.
public static void SetSlotAttachmentToSetupPose (this Skeleton skeleton, int slotIndex) {
var slot = skeleton.slots.Items[slotIndex];
- // Based on Slot.SetToSetupPose
- if (slot.data.attachmentName == null)
+ var attachmentName = slot.data.attachmentName;
+ if (string.IsNullOrEmpty(attachmentName)) {
slot.Attachment = null;
- else {
- slot.attachment = null;
- slot.Attachment = skeleton.GetAttachment(slotIndex, slot.data.attachmentName);
+ } else {
+ var attachment = skeleton.GetAttachment(slotIndex, attachmentName);
+ slot.Attachment = attachment;
}
}
+ ///
+ /// Shortcut for posing a skeleton at a specific time. Time is in seconds. (frameNumber / 30f) will give you seconds.
+ /// If you need to do this often, you should get the Animation object yourself using skeleton.data.FindAnimation. and call Apply on that.
+ /// The skeleton to pose.
+ /// The name of the animation to use.
+ /// The time of the pose within the animation.
+ /// Wraps the time around if it is longer than the duration of the animation.
+ public static void PoseWithAnimation (this Skeleton skeleton, string animationName, float time, bool loop) {
+ // Fail loud when skeleton.data is null.
+ Spine.Animation animation = skeleton.data.FindAnimation(animationName);
+ if (animation == null) return;
+ animation.Apply(skeleton, 0, time, loop, null, 1f, false, false);
+ }
+
/// Resets Skeleton parts to Setup Pose according to a Spine.Animation's keyed items.
public static void SetKeyedItemsToSetupPose (this Animation animation, Skeleton skeleton) {
- var timelinesItems = animation.timelines.Items;
- for (int i = 0, n = timelinesItems.Length; i < n; i++)
- timelinesItems[i].SetToSetupPose(skeleton);
- }
-
- public static void SetToSetupPose (this Timeline timeline, Skeleton skeleton) {
- if (timeline != null) {
- // sorted according to assumed likelihood here
-
- // Bone
- if (timeline is RotateTimeline) {
- var bone = skeleton.bones.Items[((RotateTimeline)timeline).boneIndex];
- bone.rotation = bone.data.rotation;
- } else if (timeline is TranslateTimeline) {
- var bone = skeleton.bones.Items[((TranslateTimeline)timeline).boneIndex];
- bone.x = bone.data.x;
- bone.y = bone.data.y;
- } else if (timeline is ScaleTimeline) {
- var bone = skeleton.bones.Items[((ScaleTimeline)timeline).boneIndex];
- bone.scaleX = bone.data.scaleX;
- bone.scaleY = bone.data.scaleY;
-
-
- // Attachment
- } else if (timeline is DeformTimeline) {
- var slot = skeleton.slots.Items[((DeformTimeline)timeline).slotIndex];
- slot.attachmentVertices.Clear(false);
-
- // Slot
- } else if (timeline is AttachmentTimeline) {
- skeleton.SetSlotAttachmentToSetupPose(((AttachmentTimeline)timeline).slotIndex);
-
- } else if (timeline is ColorTimeline) {
- skeleton.slots.Items[((ColorTimeline)timeline).slotIndex].SetColorToSetupPose();
-
-
- // Constraint
- } else if (timeline is IkConstraintTimeline) {
- var ikTimeline = (IkConstraintTimeline)timeline;
- var ik = skeleton.ikConstraints.Items[ikTimeline.ikConstraintIndex];
- var data = ik.data;
- ik.bendDirection = data.bendDirection;
- ik.mix = data.mix;
-
- // Skeleton
- } else if (timeline is DrawOrderTimeline) {
- skeleton.SetDrawOrderToSetupPose();
-
- }
-
- }
-
+ animation.Apply(skeleton, 0, 0, false, null, 0, true, true);
}
#endregion
}
diff --git a/spine-unity/Assets/spine-unity/SkeletonRenderer.cs b/spine-unity/Assets/spine-unity/SkeletonRenderer.cs
index 71caf827a..ca911bbfe 100644
--- a/spine-unity/Assets/spine-unity/SkeletonRenderer.cs
+++ b/spine-unity/Assets/spine-unity/SkeletonRenderer.cs
@@ -34,7 +34,6 @@
#define SPINE_OPTIONAL_SOLVETANGENTS
//#define SPINE_OPTIONAL_FRONTFACING
-//#define SPINE_OPTIONAL_SUBMESHRENDERER // Deprecated
using System;
using System.Collections.Generic;
@@ -51,8 +50,8 @@ namespace Spine.Unity {
public SkeletonRendererDelegate OnRebuild;
public SkeletonDataAsset skeletonDataAsset;
- public SkeletonDataAsset SkeletonDataAsset { get { return skeletonDataAsset; } }
- public String initialSkinName;
+ public SkeletonDataAsset SkeletonDataAsset { get { return skeletonDataAsset; } } // ISkeletonComponent
+ public string initialSkinName;
#region Advanced
// Submesh Separation
@@ -65,6 +64,7 @@ namespace Spine.Unity {
public float zSpacing;
public bool renderMeshes = true, immutableTriangles;
public bool pmaVertexColors = true;
+ public bool clearStateOnDisable = false;
#if SPINE_OPTIONAL_NORMALS
public bool calculateNormals;
@@ -100,10 +100,6 @@ namespace Spine.Unity {
}
#endif
- #if SPINE_OPTIONAL_SUBMESHRENDERER
- private Spine.Unity.Modules.SkeletonUtilitySubmeshRenderer[] submeshRenderers;
- #endif
-
#if SPINE_OPTIONAL_MATERIALOVERRIDE
[System.NonSerialized] readonly Dictionary customMaterialOverride = new Dictionary();
public Dictionary CustomMaterialOverride { get { return customMaterialOverride; } }
@@ -164,6 +160,17 @@ namespace Spine.Unity {
Initialize(false);
}
+ void OnDisable () {
+ if (clearStateOnDisable)
+ ClearState();
+ }
+
+ protected virtual void ClearState () {
+ meshFilter.sharedMesh = null;
+ currentInstructions.Clear();
+ skeleton.SetToSetupPose();
+ }
+
public virtual void Initialize (bool overwrite) {
if (valid && !overwrite)
return;
@@ -212,10 +219,6 @@ namespace Spine.Unity {
for (int i = 0; i < separatorSlotNames.Length; i++)
separatorSlots.Add(skeleton.FindSlot(separatorSlotNames[i]));
- #if SPINE_OPTIONAL_SUBMESHRENDERER
- submeshRenderers = GetComponentsInChildren();
- #endif
-
LateUpdate();
if (OnRebuild != null)
@@ -231,9 +234,6 @@ namespace Spine.Unity {
#if SPINE_OPTIONAL_RENDEROVERRIDE
&& this.generateMeshOverride == null
#endif
- #if SPINE_OPTIONAL_SUBMESHRENDERER
- && submeshRenderers.Length > 0
- #endif
)
return;
@@ -528,18 +528,6 @@ namespace Spine.Unity {
meshFilter.sharedMesh = currentMesh;
currentSmartMesh.instructionUsed.Set(workingInstruction);
-
- #if SPINE_OPTIONAL_SUBMESHRENDERER
- if (submeshRenderers.Length > 0) {
- for (int i = 0; i < submeshRenderers.Length; i++) {
- var submeshRenderer = submeshRenderers[i];
- if (submeshRenderer.submeshIndex < sharedMaterials.Length)
- submeshRenderer.SetMesh(meshRenderer, currentMesh, sharedMaterials[submeshRenderer.submeshIndex]);
- else
- submeshRenderer.GetComponent().enabled = false;
- }
- }
- #endif
}
static bool CheckIfMustUpdateMeshStructure (SmartMesh.Instruction a, SmartMesh.Instruction b) {
@@ -705,22 +693,6 @@ namespace Spine.Unity {
}
#endif
- #if UNITY_EDITOR
- void OnDrawGizmos () {
- // Make scene view selection easier by drawing a clear gizmo over the skeleton.
- meshFilter = GetComponent();
- if (meshFilter == null) return;
-
- Mesh mesh = meshFilter.sharedMesh;
- if (mesh == null) return;
-
- Bounds meshBounds = mesh.bounds;
- Gizmos.color = Color.clear;
- Gizmos.matrix = transform.localToWorldMatrix;
- Gizmos.DrawCube(meshBounds.center, meshBounds.size);
- }
- #endif
-
///This is a Mesh that also stores the instructions SkeletonRenderer generated for it.
public class SmartMesh {
public Mesh mesh = Spine.Unity.SpineMesh.NewMesh();
diff --git a/spine-unity/Assets/spine-unity/SkeletonUtility/Editor/SkeletonUtilityBoneInspector.cs b/spine-unity/Assets/spine-unity/SkeletonUtility/Editor/SkeletonUtilityBoneInspector.cs
index 0ced7a96c..c3a9c9c21 100644
--- a/spine-unity/Assets/spine-unity/SkeletonUtility/Editor/SkeletonUtilityBoneInspector.cs
+++ b/spine-unity/Assets/spine-unity/SkeletonUtility/Editor/SkeletonUtilityBoneInspector.cs
@@ -60,21 +60,16 @@ namespace Spine.Unity.Editor {
scale = this.serializedObject.FindProperty("scale");
overrideAlpha = this.serializedObject.FindProperty("overrideAlpha");
parentReference = this.serializedObject.FindProperty("parentReference");
-
EvaluateFlags();
- if (utilityBone.valid == false && skeletonUtility != null && skeletonUtility.skeletonRenderer != null)
+ if (!utilityBone.valid && skeletonUtility != null && skeletonUtility.skeletonRenderer != null)
skeletonUtility.skeletonRenderer.Initialize(false);
canCreateHingeChain = CanCreateHingeChain();
-
boundingBoxTable.Clear();
- if (multiObject)
- return;
-
- if (utilityBone.bone == null)
- return;
+ if (multiObject) return;
+ if (utilityBone.bone == null) return;
var skeleton = utilityBone.bone.Skeleton;
int slotCount = skeleton.Slots.Count;
@@ -95,9 +90,8 @@ namespace Spine.Unity.Editor {
boundingBoxes.Add(boundingBoxAttachment);
}
- if (boundingBoxes.Count > 0) {
+ if (boundingBoxes.Count > 0)
boundingBoxTable.Add(slot, boundingBoxes);
- }
}
}
@@ -124,8 +118,7 @@ namespace Spine.Unity.Editor {
}
}
- if (boneCount > 1)
- multiObject = true;
+ multiObject |= (boneCount > 1);
}
}
@@ -167,45 +160,54 @@ namespace Spine.Unity.Editor {
EditorGUILayout.Space();
using (new GUILayout.HorizontalScope()) {
+ EditorGUILayout.Space();
using (new EditorGUI.DisabledGroupScope(multiObject || !utilityBone.valid || utilityBone.bone == null || utilityBone.bone.Children.Count == 0)) {
- if (GUILayout.Button(new GUIContent("Add Child", SpineEditorUtilities.Icons.bone), GUILayout.Width(150), GUILayout.Height(24)))
+ if (GUILayout.Button(new GUIContent("Add Child", SpineEditorUtilities.Icons.bone), GUILayout.MinWidth(120), GUILayout.Height(24)))
BoneSelectorContextMenu("", utilityBone.bone.Children, "", SpawnChildBoneSelected);
}
using (new EditorGUI.DisabledGroupScope(multiObject || !utilityBone.valid || utilityBone.bone == null || containsOverrides)) {
- if (GUILayout.Button(new GUIContent("Add Override", SpineEditorUtilities.Icons.poseBones), GUILayout.Width(150), GUILayout.Height(24)))
+ if (GUILayout.Button(new GUIContent("Add Override", SpineEditorUtilities.Icons.poseBones), GUILayout.MinWidth(120), GUILayout.Height(24)))
SpawnOverride();
}
+ EditorGUILayout.Space();
+ }
+ EditorGUILayout.Space();
+ using (new GUILayout.HorizontalScope()) {
+ EditorGUILayout.Space();
using (new EditorGUI.DisabledGroupScope(multiObject || !utilityBone.valid || !canCreateHingeChain)) {
if (GUILayout.Button(new GUIContent("Create Hinge Chain", SpineEditorUtilities.Icons.hingeChain), GUILayout.Width(150), GUILayout.Height(24)))
CreateHingeChain();
}
+ EditorGUILayout.Space();
}
using (new EditorGUI.DisabledGroupScope(multiObject || boundingBoxTable.Count == 0)) {
EditorGUILayout.LabelField(new GUIContent("Bounding Boxes", SpineEditorUtilities.Icons.boundingBox), EditorStyles.boldLabel);
foreach(var entry in boundingBoxTable){
+ Slot slot = entry.Key;
+ var boundingBoxes = entry.Value;
+
EditorGUI.indentLevel++;
- EditorGUILayout.LabelField(entry.Key.Data.Name);
+ EditorGUILayout.LabelField(slot.Data.Name);
EditorGUI.indentLevel++;
{
- foreach (var box in entry.Value) {
+ foreach (var box in boundingBoxes) {
using (new GUILayout.HorizontalScope()) {
GUILayout.Space(30);
if (GUILayout.Button(box.Name, GUILayout.Width(200))) {
var child = utilityBone.transform.FindChild("[BoundingBox]" + box.Name);
if (child != null) {
var originalCollider = child.GetComponent();
- var updatedCollider = SkeletonUtility.AddBoundingBoxAsComponent(box, child.gameObject, originalCollider.isTrigger);
+ var updatedCollider = SkeletonUtility.AddBoundingBoxAsComponent(box, slot, child.gameObject, originalCollider.isTrigger);
originalCollider.points = updatedCollider.points;
if (EditorApplication.isPlaying)
Destroy(updatedCollider);
else
DestroyImmediate(updatedCollider);
} else {
- utilityBone.AddBoundingBox(currentSkinName, entry.Key.Data.Name, box.Name);
+ utilityBone.AddBoundingBox(currentSkinName, slot.Data.Name, box.Name);
}
-
}
}
@@ -220,7 +222,7 @@ namespace Spine.Unity.Editor {
}
static void BoneSelectorContextMenu (string current, ExposedList bones, string topValue, GenericMenu.MenuFunction2 callback) {
- GenericMenu menu = new GenericMenu();
+ var menu = new GenericMenu();
if (topValue != "")
menu.AddItem(new GUIContent(topValue), current == topValue, callback, null);
@@ -237,17 +239,16 @@ namespace Spine.Unity.Editor {
boneName.stringValue = "";
serializedObject.ApplyModifiedProperties();
} else {
- Bone bone = (Bone)obj;
+ var bone = (Bone)obj;
boneName.stringValue = bone.Data.Name;
serializedObject.ApplyModifiedProperties();
-
utilityBone.Reset();
}
}
void SpawnChildBoneSelected (object obj) {
if (obj == null) {
- //add recursively
+ // Add recursively
foreach (var bone in utilityBone.bone.Children) {
GameObject go = skeletonUtility.SpawnBoneRecursively(bone, utilityBone.transform, utilityBone.mode, utilityBone.position, utilityBone.rotation, utilityBone.scale);
SkeletonUtilityBone[] newUtilityBones = go.GetComponentsInChildren();
@@ -255,7 +256,7 @@ namespace Spine.Unity.Editor {
SkeletonUtilityInspector.AttachIcon(utilBone);
}
} else {
- Bone bone = (Bone)obj;
+ var bone = (Bone)obj;
GameObject go = skeletonUtility.SpawnBone(bone, utilityBone.transform, utilityBone.mode, utilityBone.position, utilityBone.rotation, utilityBone.scale);
SkeletonUtilityInspector.AttachIcon(go.GetComponent());
Selection.activeGameObject = go;
@@ -281,10 +282,7 @@ namespace Spine.Unity.Editor {
Rigidbody[] rigidbodies = utilityBone.GetComponentsInChildren();
- if (rigidbodies.Length > 0)
- return false;
-
- return true;
+ return rigidbodies.Length <= 0;
}
void CreateHingeChain () {
diff --git a/spine-unity/Assets/spine-unity/SkeletonUtility/Editor/SkeletonUtilityInspector.cs b/spine-unity/Assets/spine-unity/SkeletonUtility/Editor/SkeletonUtilityInspector.cs
index 3b32920a8..7d8b5bfb4 100644
--- a/spine-unity/Assets/spine-unity/SkeletonUtility/Editor/SkeletonUtilityInspector.cs
+++ b/spine-unity/Assets/spine-unity/SkeletonUtility/Editor/SkeletonUtilityInspector.cs
@@ -32,74 +32,30 @@
using UnityEngine;
using UnityEditor;
-
-#if UNITY_4_3
-//nothing
-#else
using UnityEditor.AnimatedValues;
-#endif
using System.Collections.Generic;
using Spine;
-
using System.Reflection;
-
namespace Spine.Unity.Editor {
+ using Icons = SpineEditorUtilities.Icons;
+
[CustomEditor(typeof(SkeletonUtility))]
public class SkeletonUtilityInspector : UnityEditor.Editor {
- public static void AttachIcon (SkeletonUtilityBone utilityBone) {
- Skeleton skeleton = utilityBone.skeletonUtility.skeletonRenderer.skeleton;
- Texture2D icon;
- if (utilityBone.bone.Data.Length == 0)
- icon = SpineEditorUtilities.Icons.nullBone;
- else
- icon = SpineEditorUtilities.Icons.boneNib;
-
- foreach (IkConstraint c in skeleton.IkConstraints) {
- if (c.Target == utilityBone.bone) {
- icon = SpineEditorUtilities.Icons.constraintNib;
- break;
- }
- }
-
- typeof(EditorGUIUtility).InvokeMember("SetIconForObject", BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.NonPublic, null, null, new object[2] {
- utilityBone.gameObject,
- icon
- });
- }
-
- static void AttachIconsToChildren (Transform root) {
- if (root != null) {
- var utilityBones = root.GetComponentsInChildren();
- foreach (var utilBone in utilityBones) {
- AttachIcon(utilBone);
- }
- }
- }
-
- static SkeletonUtilityInspector () {
- #if UNITY_4_3
- showSlots = false;
- #else
- showSlots = new AnimBool(false);
- #endif
- }
-
SkeletonUtility skeletonUtility;
Skeleton skeleton;
SkeletonRenderer skeletonRenderer;
Transform transform;
bool isPrefab;
+
Dictionary> attachmentTable = new Dictionary>();
-
- //GUI stuff
- #if UNITY_4_3
- static bool showSlots;
- #else
- static AnimBool showSlots;
- #endif
+ GUIContent SpawnHierarchyButtonLabel = new GUIContent("Spawn Hierarchy", Icons.skeleton);
+ GUIContent SlotsRootLabel = new GUIContent("Slots", Icons.slotRoot);
+ static AnimBool showSlots = new AnimBool(false);
+ static bool showPaths = true;
+ static bool debugSkeleton = false;
void OnEnable () {
skeletonUtility = (SkeletonUtility)target;
@@ -110,7 +66,6 @@ namespace Spine.Unity.Editor {
if (skeleton == null) {
skeletonRenderer.Initialize(false);
skeletonRenderer.LateUpdate();
-
skeleton = skeletonRenderer.skeleton;
}
@@ -125,149 +80,135 @@ namespace Spine.Unity.Editor {
OnEnable();
return;
}
-
+
var m = transform.localToWorldMatrix;
foreach (Bone b in skeleton.Bones) {
Vector3 pos = new Vector3(b.WorldX, b.WorldY, 0);
Quaternion rot = Quaternion.Euler(0, 0, b.WorldRotationX - 90f);
Vector3 scale = Vector3.one * b.Data.Length * b.WorldScaleX;
- const float mx = 2.5f;
+ const float mx = 2f;
scale.x = Mathf.Clamp(scale.x, -mx, mx);
SpineEditorUtilities.DrawBone(m * Matrix4x4.TRS(pos, rot, scale));
}
- foreach (Slot s in skeleton.DrawOrder) {
- var p = s.Attachment as PathAttachment;
- if (p != null) SpineEditorUtilities.DrawPath(s, p, transform);
+ if (showPaths) {
+ foreach (Slot s in skeleton.DrawOrder) {
+ var p = s.Attachment as PathAttachment;
+ if (p != null) SpineEditorUtilities.DrawPath(s, p, transform);
+ }
+ }
+ }
+
+ public override void OnInspectorGUI () {
+ bool requireRepaint = false;
+
+ if (isPrefab) {
+ GUILayout.Label(new GUIContent("Cannot edit Prefabs", Icons.warning));
+ return;
+ }
+
+ if (!skeletonRenderer.valid) {
+ GUILayout.Label(new GUIContent("Spine Component invalid. Check Skeleton Data Asset.", Icons.warning));
+ return;
+ }
+
+ skeletonUtility.boneRoot = (Transform)EditorGUILayout.ObjectField("Bone Root", skeletonUtility.boneRoot, typeof(Transform), true);
+
+ using (new EditorGUI.DisabledGroupScope(skeletonUtility.boneRoot != null)) {
+ if (SpineInspectorUtility.LargeCenteredButton(SpawnHierarchyButtonLabel))
+ SpawnHierarchyContextMenu();
+ }
+
+ using (new SpineInspectorUtility.BoxScope()) {
+ debugSkeleton = EditorGUILayout.Foldout(debugSkeleton, "Debug Skeleton");
+
+ if (debugSkeleton) {
+ EditorGUI.BeginChangeCheck();
+ showPaths = EditorGUILayout.Toggle("Show Paths", showPaths);
+ requireRepaint |= EditorGUI.EndChangeCheck();
+
+ EditorGUI.BeginChangeCheck();
+ skeleton.FlipX = EditorGUILayout.ToggleLeft("skeleton.FlipX", skeleton.FlipX);
+ skeleton.FlipY = EditorGUILayout.ToggleLeft("skeleton.FlipY", skeleton.FlipY);
+ requireRepaint |= EditorGUI.EndChangeCheck();
+
+ foreach (var t in skeleton.IkConstraints)
+ EditorGUILayout.LabelField(t.Data.Name + " " + t.Mix + " " + t.Target.Data.Name);
+
+ showSlots.target = EditorGUILayout.Foldout(showSlots.target, SlotsRootLabel);
+ if (showSlots.faded > 0) {
+ using (new EditorGUILayout.FadeGroupScope(showSlots.faded)) {
+ int baseIndent = EditorGUI.indentLevel;
+ foreach (KeyValuePair> pair in attachmentTable) {
+ Slot slot = pair.Key;
+
+ using (new EditorGUILayout.HorizontalScope()) {
+ EditorGUI.indentLevel = baseIndent + 1;
+ EditorGUILayout.LabelField(new GUIContent(slot.Data.Name, Icons.slot), GUILayout.ExpandWidth(false));
+ EditorGUI.BeginChangeCheck();
+ Color c = EditorGUILayout.ColorField(new Color(slot.R, slot.G, slot.B, slot.A), GUILayout.Width(60));
+ if (EditorGUI.EndChangeCheck()) {
+ slot.SetColor(c);
+ requireRepaint = true;
+ }
+ }
+
+ foreach (var attachment in pair.Value) {
+ GUI.contentColor = slot.Attachment == attachment ? Color.white : Color.grey;
+ EditorGUI.indentLevel = baseIndent + 2;
+ var icon = (attachment is MeshAttachment) ? Icons.mesh : Icons.image;
+ bool isAttached = (attachment == slot.Attachment);
+ bool swap = EditorGUILayout.ToggleLeft(new GUIContent(attachment.Name, icon), attachment == slot.Attachment);
+ if (isAttached != swap) {
+ slot.Attachment = isAttached ? null : attachment;
+ requireRepaint = true;
+ }
+ GUI.contentColor = Color.white;
+ }
+ }
+ }
+ }
+
+
+ }
+
+ if (showSlots.isAnimating)
+ Repaint();
+ }
+
+ if (requireRepaint) {
+ skeletonRenderer.LateUpdate();
+ SceneView.RepaintAll();
}
}
void UpdateAttachments () {
attachmentTable = new Dictionary>();
Skin skin = skeleton.Skin ?? skeletonRenderer.skeletonDataAsset.GetSkeletonData(true).DefaultSkin;
- for (int i = skeleton.Slots.Count-1; i >= 0; i--) {
- List attachments = new List();
+ for (int i = skeleton.Slots.Count - 1; i >= 0; i--) {
+ var attachments = new List();
skin.FindAttachmentsForSlot(i, attachments);
-
attachmentTable.Add(skeleton.Slots.Items[i], attachments);
}
}
- void SpawnHierarchyButton (string label, string tooltip, SkeletonUtilityBone.Mode mode, bool pos, bool rot, bool sca, params GUILayoutOption[] options) {
- GUIContent content = new GUIContent(label, tooltip);
- if (GUILayout.Button(content, options)) {
- if (skeletonUtility.skeletonRenderer == null)
- skeletonUtility.skeletonRenderer = skeletonUtility.GetComponent();
-
- if (skeletonUtility.boneRoot != null) {
- return;
- }
-
- skeletonUtility.SpawnHierarchy(mode, pos, rot, sca);
-
- SkeletonUtilityBone[] boneComps = skeletonUtility.GetComponentsInChildren();
- foreach (SkeletonUtilityBone b in boneComps)
- AttachIcon(b);
- }
- }
-
- public override void OnInspectorGUI () {
- if (isPrefab) {
- GUILayout.Label(new GUIContent("Cannot edit Prefabs", SpineEditorUtilities.Icons.warning));
- return;
- }
-
- if (!skeletonRenderer.valid) {
- GUILayout.Label(new GUIContent("Spine Component invalid. Check Skeleton Data Asset.", SpineEditorUtilities.Icons.warning));
- return;
- }
-
- skeletonUtility.boneRoot = (Transform)EditorGUILayout.ObjectField("Bone Root", skeletonUtility.boneRoot, typeof(Transform), true);
-
- using (new GUILayout.HorizontalScope()) {
- using (new EditorGUI.DisabledGroupScope(skeletonUtility.boneRoot != null)) {
- if (GUILayout.Button(new GUIContent("Spawn Hierarchy", SpineEditorUtilities.Icons.skeleton), GUILayout.Width(150), GUILayout.Height(24)))
- SpawnHierarchyContextMenu();
- }
-
- // if (GUILayout.Button(new GUIContent("Spawn Submeshes", SpineEditorUtilities.Icons.subMeshRenderer), GUILayout.Width(150), GUILayout.Height(24)))
- // skeletonUtility.SpawnSubRenderers(true);
- }
-
- EditorGUI.BeginChangeCheck();
- skeleton.FlipX = EditorGUILayout.ToggleLeft("Flip X", skeleton.FlipX);
- skeleton.FlipY = EditorGUILayout.ToggleLeft("Flip Y", skeleton.FlipY);
- if (EditorGUI.EndChangeCheck()) {
- skeletonRenderer.LateUpdate();
- SceneView.RepaintAll();
- }
-
- #if UNITY_4_3
- showSlots = EditorGUILayout.Foldout(showSlots, "Slots");
- #else
- showSlots.target = EditorGUILayout.Foldout(showSlots.target, "Slots");
- if (EditorGUILayout.BeginFadeGroup(showSlots.faded)) {
- #endif
- foreach (KeyValuePair> pair in attachmentTable) {
-
- Slot slot = pair.Key;
-
- EditorGUILayout.BeginHorizontal();
- EditorGUI.indentLevel = 1;
- EditorGUILayout.LabelField(new GUIContent(slot.Data.Name, SpineEditorUtilities.Icons.slot), GUILayout.ExpandWidth(false));
-
- EditorGUI.BeginChangeCheck();
- Color c = EditorGUILayout.ColorField(new Color(slot.R, slot.G, slot.B, slot.A), GUILayout.Width(60));
-
- if (EditorGUI.EndChangeCheck()) {
- slot.SetColor(c);
- skeletonRenderer.LateUpdate();
- }
-
- EditorGUILayout.EndHorizontal();
-
-
-
- foreach (Attachment attachment in pair.Value) {
-
- if (slot.Attachment == attachment) {
- GUI.contentColor = Color.white;
- } else {
- GUI.contentColor = Color.grey;
- }
-
- EditorGUI.indentLevel = 2;
- bool isAttached = attachment == slot.Attachment;
-
- Texture2D icon = null;
-
- if (attachment is MeshAttachment)
- icon = SpineEditorUtilities.Icons.mesh;
- else
- icon = SpineEditorUtilities.Icons.image;
-
- bool swap = EditorGUILayout.ToggleLeft(new GUIContent(attachment.Name, icon), attachment == slot.Attachment);
-
- if (!isAttached && swap) {
- slot.Attachment = attachment;
- skeletonRenderer.LateUpdate();
- } else if (isAttached && !swap) {
- slot.Attachment = null;
- skeletonRenderer.LateUpdate();
- }
-
- GUI.contentColor = Color.white;
- }
- }
- #if UNITY_4_3
-
- #else
- }
- EditorGUILayout.EndFadeGroup();
- if (showSlots.isAnimating)
- Repaint();
- #endif
- }
+// void SpawnHierarchyButton (string label, string tooltip, SkeletonUtilityBone.Mode mode, bool pos, bool rot, bool sca, params GUILayoutOption[] options) {
+// GUIContent content = new GUIContent(label, tooltip);
+// if (GUILayout.Button(content, options)) {
+// if (skeletonUtility.skeletonRenderer == null)
+// skeletonUtility.skeletonRenderer = skeletonUtility.GetComponent();
+//
+// if (skeletonUtility.boneRoot != null) {
+// return;
+// }
+//
+// skeletonUtility.SpawnHierarchy(mode, pos, rot, sca);
+//
+// SkeletonUtilityBone[] boneComps = skeletonUtility.GetComponentsInChildren();
+// foreach (SkeletonUtilityBone b in boneComps)
+// AttachIcon(b);
+// }
+// }
void SpawnHierarchyContextMenu () {
GenericMenu menu = new GenericMenu();
@@ -281,6 +222,30 @@ namespace Spine.Unity.Editor {
menu.ShowAsContext();
}
+ public static void AttachIcon (SkeletonUtilityBone utilityBone) {
+ Skeleton skeleton = utilityBone.skeletonUtility.skeletonRenderer.skeleton;
+ Texture2D icon = utilityBone.bone.Data.Length == 0 ? Icons.nullBone : Icons.boneNib;
+
+ foreach (IkConstraint c in skeleton.IkConstraints)
+ if (c.Target == utilityBone.bone) {
+ icon = Icons.constraintNib;
+ break;
+ }
+
+ typeof(EditorGUIUtility).InvokeMember("SetIconForObject", BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.NonPublic, null, null, new object[2] {
+ utilityBone.gameObject,
+ icon
+ });
+ }
+
+ static void AttachIconsToChildren (Transform root) {
+ if (root != null) {
+ var utilityBones = root.GetComponentsInChildren();
+ foreach (var utilBone in utilityBones)
+ AttachIcon(utilBone);
+ }
+ }
+
void SpawnFollowHierarchy () {
Selection.activeGameObject = skeletonUtility.SpawnHierarchy(SkeletonUtilityBone.Mode.Follow, true, true, true);
AttachIconsToChildren(skeletonUtility.boneRoot);
diff --git a/spine-unity/Assets/spine-unity/SkeletonUtility/SkeletonUtility.cs b/spine-unity/Assets/spine-unity/SkeletonUtility/SkeletonUtility.cs
index b7898a189..ae0d24c88 100644
--- a/spine-unity/Assets/spine-unity/SkeletonUtility/SkeletonUtility.cs
+++ b/spine-unity/Assets/spine-unity/SkeletonUtility/SkeletonUtility.cs
@@ -39,85 +39,39 @@ namespace Spine.Unity {
[RequireComponent(typeof(ISkeletonAnimation))]
[ExecuteInEditMode]
public class SkeletonUtility : MonoBehaviour {
-
- public static T GetInParent (Transform origin) where T : Component {
- #if UNITY_4_3
- Transform parent = origin.parent;
- while(parent.GetComponent() == null){
- parent = parent.parent;
- if(parent == null)
- return default(T);
- }
-
- return parent.GetComponent();
- #else
- return origin.GetComponentInParent();
- #endif
- }
-
+
+ #region BoundingBoxAttachment
public static PolygonCollider2D AddBoundingBox (Skeleton skeleton, string skinName, string slotName, string attachmentName, Transform parent, bool isTrigger = true) {
- // List attachments = new List();
- Skin skin;
-
- if (skinName == "")
- skinName = skeleton.Data.DefaultSkin.Name;
-
- skin = skeleton.Data.FindSkin(skinName);
-
+ skinName = string.IsNullOrEmpty(skinName) ? skinName : skeleton.Data.DefaultSkin.Name;
+ Skin skin = skeleton.Data.FindSkin(skinName);
if (skin == null) {
Debug.LogError("Skin " + skinName + " not found!");
return null;
}
var attachment = skin.GetAttachment(skeleton.FindSlotIndex(slotName), attachmentName);
- if (attachment is BoundingBoxAttachment) {
- GameObject go = new GameObject("[BoundingBox]" + attachmentName);
- go.transform.parent = parent;
- go.transform.localPosition = Vector3.zero;
- go.transform.localRotation = Quaternion.identity;
- go.transform.localScale = Vector3.one;
- var collider = go.AddComponent();
- collider.isTrigger = isTrigger;
- var boundingBox = (BoundingBoxAttachment)attachment;
- float[] floats = boundingBox.Vertices;
- int floatCount = floats.Length;
- int vertCount = floatCount / 2;
-
- Vector2[] verts = new Vector2[vertCount];
- int v = 0;
- for (int i = 0; i < floatCount; i += 2, v++) {
- verts[v].x = floats[i];
- verts[v].y = floats[i + 1];
- }
-
- collider.SetPath(0, verts);
-
- return collider;
-
+ var box = attachment as BoundingBoxAttachment;
+ if (box != null) {
+ var go = new GameObject("[BoundingBox]" + attachmentName);
+ var got = go.transform;
+ got.parent = parent;
+ got.localPosition = Vector3.zero;
+ got.localRotation = Quaternion.identity;
+ got.localScale = Vector3.one;
+ var slot = skeleton.FindSlot(slotName);
+ return AddBoundingBoxAsComponent(box, slot, go, isTrigger);
}
return null;
}
- public static PolygonCollider2D AddBoundingBoxAsComponent (BoundingBoxAttachment boundingBox, GameObject gameObject, bool isTrigger = true) {
- if (boundingBox == null)
- return null;
-
+ public static PolygonCollider2D AddBoundingBoxAsComponent (BoundingBoxAttachment box, Slot slot, GameObject gameObject, bool isTrigger = true) {
+ if (box == null) return null;
+ if (box.IsWeighted()) Debug.LogWarning("UnityEngine.PolygonCollider2D does not support weighted or animated points. Collider will not be animated. Please remove weights and animations from the bounding box in Spine editor.");
+ var verts = box.GetWorldVertices(slot, null);
var collider = gameObject.AddComponent();
collider.isTrigger = isTrigger;
- float[] floats = boundingBox.Vertices;
- int floatCount = floats.Length;
- int vertCount = floatCount / 2;
-
- Vector2[] verts = new Vector2[vertCount];
- int v = 0;
- for (int i = 0; i < floatCount; i += 2, v++) {
- verts[v].x = floats[i];
- verts[v].y = floats[i + 1];
- }
-
collider.SetPath(0, verts);
-
return collider;
}
@@ -126,22 +80,20 @@ namespace Spine.Unity {
int floatCount = floats.Length;
Bounds bounds = new Bounds();
-
bounds.center = new Vector3(floats[0], floats[1], 0);
- for (int i = 2; i < floatCount; i += 2) {
+ for (int i = 2; i < floatCount; i += 2)
bounds.Encapsulate(new Vector3(floats[i], floats[i + 1], 0));
- }
+
Vector3 size = bounds.size;
size.z = depth;
bounds.size = size;
return bounds;
}
+ #endregion
public delegate void SkeletonUtilityDelegate ();
-
public event SkeletonUtilityDelegate OnReset;
-
public Transform boneRoot;
void Update () {
@@ -165,7 +117,6 @@ namespace Spine.Unity {
public List utilityBones = new List();
[System.NonSerialized]
public List utilityConstraints = new List();
- // Dictionary utilityBoneTable;
protected bool hasTransformBones;
protected bool hasUtilityConstraints;
@@ -190,13 +141,12 @@ namespace Spine.Unity {
skeletonAnimation.UpdateLocal += UpdateLocal;
}
-
CollectBones();
}
void Start () {
//recollect because order of operations failure when switching between game mode and edit mode...
- // CollectBones();
+ CollectBones();
}
void OnDisable () {
@@ -244,41 +194,38 @@ namespace Spine.Unity {
}
public void CollectBones () {
- if (skeletonRenderer.skeleton == null)
- return;
+ var skeleton = skeletonRenderer.skeleton;
+ if (skeleton == null) return;
if (boneRoot != null) {
- List constraintTargetNames = new List();
-
- ExposedList ikConstraints = skeletonRenderer.skeleton.IkConstraints;
+ var constraintTargets = new List();
+ var ikConstraints = skeleton.IkConstraints;
for (int i = 0, n = ikConstraints.Count; i < n; i++)
- constraintTargetNames.Add(ikConstraints.Items[i].Target.Data.Name);
+ constraintTargets.Add(ikConstraints.Items[i].target);
+
+ var transformConstraints = skeleton.TransformConstraints;
+ for (int i = 0, n = transformConstraints.Count; i < n; i++)
+ constraintTargets.Add(transformConstraints.Items[i].target);
var utilityBones = this.utilityBones;
for (int i = 0, n = utilityBones.Count; i < n; i++) {
var b = utilityBones[i];
if (b.bone == null) return;
- if (b.mode == SkeletonUtilityBone.Mode.Override)
- hasTransformBones = true;
-
- if (constraintTargetNames.Contains(b.bone.Data.Name))
- hasUtilityConstraints = true;
+ hasTransformBones |= (b.mode == SkeletonUtilityBone.Mode.Override);
+ hasUtilityConstraints |= constraintTargets.Contains(b.bone);
}
- if (utilityConstraints.Count > 0)
- hasUtilityConstraints = true;
+ hasUtilityConstraints |= utilityConstraints.Count > 0;
if (skeletonAnimation != null) {
skeletonAnimation.UpdateWorld -= UpdateWorld;
skeletonAnimation.UpdateComplete -= UpdateComplete;
- if (hasTransformBones || hasUtilityConstraints) {
+ if (hasTransformBones || hasUtilityConstraints)
skeletonAnimation.UpdateWorld += UpdateWorld;
- }
-
- if (hasUtilityConstraints) {
+
+ if (hasUtilityConstraints)
skeletonAnimation.UpdateComplete += UpdateComplete;
- }
}
needToReprocessBones = false;
@@ -286,7 +233,6 @@ namespace Spine.Unity {
utilityBones.Clear();
utilityConstraints.Clear();
}
-
}
void UpdateLocal (ISkeletonAnimation anim) {
@@ -339,21 +285,15 @@ namespace Spine.Unity {
Skeleton skeleton = this.skeletonRenderer.skeleton;
GameObject go = SpawnBone(skeleton.RootBone, boneRoot, mode, pos, rot, sca);
-
CollectBones();
-
return go;
}
public GameObject SpawnHierarchy (SkeletonUtilityBone.Mode mode, bool pos, bool rot, bool sca) {
GetBoneRoot();
-
Skeleton skeleton = this.skeletonRenderer.skeleton;
-
GameObject go = SpawnBoneRecursively(skeleton.RootBone, boneRoot, mode, pos, rot, sca);
-
CollectBones();
-
return go;
}
diff --git a/spine-unity/Assets/spine-unity/SkeletonUtility/SkeletonUtilityBone.cs b/spine-unity/Assets/spine-unity/SkeletonUtility/SkeletonUtilityBone.cs
index e376f8777..1b695779a 100644
--- a/spine-unity/Assets/spine-unity/SkeletonUtility/SkeletonUtilityBone.cs
+++ b/spine-unity/Assets/spine-unity/SkeletonUtility/SkeletonUtilityBone.cs
@@ -41,49 +41,30 @@ namespace Spine.Unity {
[ExecuteInEditMode]
[AddComponentMenu("Spine/SkeletonUtilityBone")]
public class SkeletonUtilityBone : MonoBehaviour {
-
public enum Mode {
Follow,
Override
}
- [System.NonSerialized]
- public bool valid;
- [System.NonSerialized]
- public SkeletonUtility skeletonUtility;
- [System.NonSerialized]
- public Bone bone;
+ #region Inspector
+ /// If a bone isn't set, boneName is used to find the bone.
+ public string boneName;
+ public Transform parentReference;
public Mode mode;
- public bool zPosition = true;
- public bool position;
- public bool rotation;
- public bool scale;
- // MITCH : remove flipX
- // Kept these fields public to retain serialization. Probably remove eventually?
- public bool flip;
- public bool flipX;
+ public bool position, rotation, scale, zPosition = true;
[Range(0f, 1f)]
public float overrideAlpha = 1;
+ #endregion
- /// If a bone isn't set, boneName is used to find the bone.
- public String boneName;
- public Transform parentReference;
-
- [System.NonSerialized]
- public bool transformLerpComplete;
-
- protected Transform cachedTransform;
- protected Transform skeletonTransform;
-
- // MITCH : nonuniform scale
- // private bool nonUniformScaleWarning;
- // public bool NonUniformScaleWarning {
- // get { return nonUniformScaleWarning; }
- // }
-
- private bool disableInheritScaleWarning;
- public bool DisableInheritScaleWarning {
- get { return disableInheritScaleWarning; }
+ [System.NonSerialized] public SkeletonUtility skeletonUtility;
+ [System.NonSerialized] public Bone bone;
+ [System.NonSerialized] public bool transformLerpComplete;
+ [System.NonSerialized] public bool valid;
+ Transform cachedTransform;
+ Transform skeletonTransform;
+ bool incompatibleTransformMode;
+ public bool IncompatibleTransformMode {
+ get { return incompatibleTransformMode; }
}
public void Reset () {
@@ -99,7 +80,7 @@ namespace Spine.Unity {
}
void OnEnable () {
- skeletonUtility = SkeletonUtility.GetInParent(transform);
+ skeletonUtility = transform.GetComponentInParent();
if (skeletonUtility == null)
return;
@@ -125,14 +106,11 @@ namespace Spine.Unity {
return;
}
- Spine.Skeleton skeleton = skeletonUtility.skeletonRenderer.skeleton;
+ var skeleton = skeletonUtility.skeletonRenderer.skeleton;
if (bone == null) {
- if (boneName == null || boneName.Length == 0)
- return;
-
+ if (string.IsNullOrEmpty(boneName)) return;
bone = skeleton.FindBone(boneName);
-
if (bone == null) {
Debug.LogError("Bone not found: " + boneName, this);
return;
@@ -140,29 +118,13 @@ namespace Spine.Unity {
}
float skeletonFlipRotation = (skeleton.flipX ^ skeleton.flipY) ? -1f : 1f;
-
- // MITCH : remove flipX
- // float flipCompensation = 0;
- // if (flip && (flipX || (flipX != bone.flipX)) && bone.parent != null) {
- // flipCompensation = bone.parent.WorldRotation * -2;
- // }
-
if (mode == Mode.Follow) {
- // MITCH : remove flipX
- // if (flip)
- // flipX = bone.flipX;
-
if (position)
cachedTransform.localPosition = new Vector3(bone.x, bone.y, 0);
-
+
if (rotation) {
- if (bone.Data.InheritRotation) {
- // MITCH : remove flipX
- //if (bone.FlipX) {
- // cachedTransform.localRotation = Quaternion.Euler(0, 180, bone.rotationIK - flipCompensation);
- //} else {
+ if (!bone.data.transformMode.InheritsRotation()) {
cachedTransform.localRotation = Quaternion.Euler(0, 0, bone.AppliedRotation);
- //}
} else {
Vector3 euler = skeletonTransform.rotation.eulerAngles;
cachedTransform.rotation = Quaternion.Euler(euler.x, euler.y, euler.z + (bone.WorldRotationX * skeletonFlipRotation));
@@ -170,53 +132,32 @@ namespace Spine.Unity {
}
if (scale) {
- cachedTransform.localScale = new Vector3(bone.scaleX, bone.scaleY, bone.WorldSignX);
- // MITCH : nonuniform scale
- //nonUniformScaleWarning = (bone.scaleX != bone.scaleY);
- disableInheritScaleWarning = !bone.data.inheritScale;
+ cachedTransform.localScale = new Vector3(bone.scaleX, bone.scaleY, 1f);//, bone.WorldSignX);
+ incompatibleTransformMode = BoneTransformModeIncompatible(bone);
}
-
} else if (mode == Mode.Override) {
if (transformLerpComplete)
return;
if (parentReference == null) {
if (position) {
- bone.x = Mathf.Lerp(bone.x, cachedTransform.localPosition.x, overrideAlpha);
- bone.y = Mathf.Lerp(bone.y, cachedTransform.localPosition.y, overrideAlpha);
+ Vector3 clp = cachedTransform.localPosition;
+ bone.x = Mathf.Lerp(bone.x, clp.x, overrideAlpha);
+ bone.y = Mathf.Lerp(bone.y, clp.y, overrideAlpha);
}
if (rotation) {
float angle = Mathf.LerpAngle(bone.Rotation, cachedTransform.localRotation.eulerAngles.z, overrideAlpha);
-
- // MITCH : remove flipX
- // float angle = Mathf.LerpAngle(bone.Rotation, cachedTransform.localRotation.eulerAngles.z, overrideAlpha) + flipCompensation;
- // if (flip) {
- //
- // if ((!flipX && bone.flipX)) {
- // angle -= flipCompensation;
- // }
- //
- // //TODO fix this...
- // if (angle >= 360)
- // angle -= 360;
- // else if (angle <= -360)
- // angle += 360;
- // }
bone.Rotation = angle;
bone.AppliedRotation = angle;
}
if (scale) {
- bone.scaleX = Mathf.Lerp(bone.scaleX, cachedTransform.localScale.x, overrideAlpha);
- bone.scaleY = Mathf.Lerp(bone.scaleY, cachedTransform.localScale.y, overrideAlpha);
- // MITCH : nonuniform scale
- //nonUniformScaleWarning = (bone.scaleX != bone.scaleY);
+ Vector3 cls = cachedTransform.localScale;
+ bone.scaleX = Mathf.Lerp(bone.scaleX, cls.x, overrideAlpha);
+ bone.scaleY = Mathf.Lerp(bone.scaleY, cls.y, overrideAlpha);
}
- // MITCH : remove flipX
- //if (flip)
- // bone.flipX = flipX;
} else {
if (transformLerpComplete)
return;
@@ -229,81 +170,35 @@ namespace Spine.Unity {
// MITCH
if (rotation) {
- float angle = Mathf.LerpAngle(bone.Rotation, Quaternion.LookRotation(flipX ? Vector3.forward * -1 : Vector3.forward, parentReference.InverseTransformDirection(cachedTransform.up)).eulerAngles.z, overrideAlpha);
-
- // MITCH : remove flipX
- // float angle = Mathf.LerpAngle(bone.Rotation, Quaternion.LookRotation(flipX ? Vector3.forward * -1 : Vector3.forward, parentReference.InverseTransformDirection(cachedTransform.up)).eulerAngles.z, overrideAlpha) + flipCompensation;
- // if (flip) {
- //
- // if ((!flipX && bone.flipX)) {
- // angle -= flipCompensation;
- // }
- //
- // //TODO fix this...
- // if (angle >= 360)
- // angle -= 360;
- // else if (angle <= -360)
- // angle += 360;
- // }
+ float angle = Mathf.LerpAngle(bone.Rotation, Quaternion.LookRotation(Vector3.forward, parentReference.InverseTransformDirection(cachedTransform.up)).eulerAngles.z, overrideAlpha);
bone.Rotation = angle;
bone.AppliedRotation = angle;
}
if (scale) {
- bone.scaleX = Mathf.Lerp(bone.scaleX, cachedTransform.localScale.x, overrideAlpha);
- bone.scaleY = Mathf.Lerp(bone.scaleY, cachedTransform.localScale.y, overrideAlpha);
- // MITCH : nonuniform scale
- //nonUniformScaleWarning = (bone.scaleX != bone.scaleY);
+ Vector3 cls = cachedTransform.localScale;
+ bone.scaleX = Mathf.Lerp(bone.scaleX, cls.x, overrideAlpha);
+ bone.scaleY = Mathf.Lerp(bone.scaleY, cls.y, overrideAlpha);
}
- disableInheritScaleWarning = !bone.data.inheritScale;
-
- // MITCH : remove flipX
- //if (flip)
- // bone.flipX = flipX;
+ incompatibleTransformMode = BoneTransformModeIncompatible(bone);
}
transformLerpComplete = true;
}
}
- // MITCH : remove flipX
- // public void FlipX (bool state) {
- // if (state != flipX) {
- // flipX = state;
- // if (flipX && Mathf.Abs(transform.localRotation.eulerAngles.y) > 90) {
- // skeletonUtility.skeletonAnimation.LateUpdate();
- // return;
- // } else if (!flipX && Mathf.Abs(transform.localRotation.eulerAngles.y) < 90) {
- // skeletonUtility.skeletonAnimation.LateUpdate();
- // return;
- // }
- // }
- //
- //
- // bone.FlipX = state;
- // transform.RotateAround(transform.position, skeletonUtility.transform.up, 180);
- // Vector3 euler = transform.localRotation.eulerAngles;
- // euler.x = 0;
- //
- // euler.y = bone.FlipX ? 180 : 0;
- // euler.y = 0;
- // transform.localRotation = Quaternion.Euler(euler);
- // }
+ public static bool BoneTransformModeIncompatible (Bone bone) {
+ return !bone.data.transformMode.InheritsScale();
+ }
public void AddBoundingBox (string skinName, string slotName, string attachmentName) {
SkeletonUtility.AddBoundingBox(bone.skeleton, skinName, slotName, attachmentName, transform);
}
-
#if UNITY_EDITOR
void OnDrawGizmos () {
- // MITCH : nonuniform scale
- // if (NonUniformScaleWarning) {
- // Gizmos.DrawIcon(transform.position + new Vector3(0, 0.128f, 0), "icon-warning");
- // }
-
- if (DisableInheritScaleWarning)
+ if (IncompatibleTransformMode)
Gizmos.DrawIcon(transform.position + new Vector3(0, 0.128f, 0), "icon-warning");
}
#endif
diff --git a/spine-unity/Assets/spine-unity/SkeletonUtility/SkeletonUtilityConstraint.cs b/spine-unity/Assets/spine-unity/SkeletonUtility/SkeletonUtilityConstraint.cs
index a89401c6f..3e44be7ce 100644
--- a/spine-unity/Assets/spine-unity/SkeletonUtility/SkeletonUtilityConstraint.cs
+++ b/spine-unity/Assets/spine-unity/SkeletonUtility/SkeletonUtilityConstraint.cs
@@ -40,7 +40,7 @@ namespace Spine.Unity {
protected virtual void OnEnable () {
utilBone = GetComponent();
- skeletonUtility = SkeletonUtility.GetInParent(transform);
+ skeletonUtility = transform.GetComponentInParent();
skeletonUtility.RegisterConstraint(this);
}
diff --git a/spine-unity/README.md b/spine-unity/README.md
index c6f4d76db..011c88772 100644
--- a/spine-unity/README.md
+++ b/spine-unity/README.md
@@ -14,10 +14,12 @@ The Spine Runtimes are developed with the intent to be used with data exported f
## Spine version
-spine-unity works with data exported from Spine 3.4.02.
+spine-unity works with data exported from Spine 3.5.x.
spine-unity supports all Spine features.
+Unity's physics components do not support dynamically assigned vertices so they cannot be used to mirror bone-weighted and deformed BoundingBoxAttachments. However, BoundingBoxAttachment vertices at runtime will still deform correctly and can be used to perform manual hit detection.
+
## Documentation
A Spine skeleton GameObject (a GameObject with a SkeletonAnimation component on it) can be used throughout Unity like any other GameObject. It renders through `MeshRenderer`.
diff --git a/spine-xna/LICENSE b/spine-xna/LICENSE
index daceab94a..815ec1ca1 100644
--- a/spine-xna/LICENSE
+++ b/spine-xna/LICENSE
@@ -1,16 +1,17 @@
-Spine Runtimes Software License v2.5
+Spine Runtimes Software License
+Version 2.3
-Copyright (c) 2013-2016, Esoteric Software
+Copyright (c) 2013-2015, Esoteric Software
All rights reserved.
-You are granted a perpetual, non-exclusive, non-sublicensable, and
-non-transferable license to use, install, execute, and perform the Spine
-Runtimes software and derivative works solely for personal or internal
-use. Without the written permission of Esoteric Software (see Section 2 of
-the Spine Software License Agreement), you may not (a) modify, translate,
-adapt, or develop new applications using the Spine Runtimes or otherwise
-create derivative works or improvements of the Spine Runtimes or (b) remove,
-delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+You are granted a perpetual, non-exclusive, non-sublicensable and
+non-transferable license to use, install, execute and perform the Spine
+Runtimes Software (the "Software") and derivative works solely for personal
+or internal use. Without the written permission of Esoteric Software (see
+Section 2 of the Spine Software License Agreement), you may not (a) modify,
+translate, adapt or otherwise create derivative works, improvements of the
+Software or develop new applications using the Software or (b) remove,
+delete, alter or obscure any trademarks or any copyright, trademark, patent
or other intellectual property or proprietary rights notices on or in the
Software, including any copy thereof. Redistributions in binary or source
form must include this license and terms.
@@ -20,8 +21,8 @@ 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 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+OR BUSINESS INTERRUPTION) 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 THIS SOFTWARE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/spine-xna/README.md b/spine-xna/README.md
index 711772d7a..7dd2eee29 100644
--- a/spine-xna/README.md
+++ b/spine-xna/README.md
@@ -10,7 +10,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f
## Spine version
-spine-xna works with data exported from Spine 3.4.02.
+spine-xna works with data exported from Spine 3.5.x.
spine-xna supports all Spine features.
diff --git a/spine-xna/example/src/ExampleGame.cs b/spine-xna/example/src/ExampleGame.cs
index a738a6f89..2b2c40e1b 100644
--- a/spine-xna/example/src/ExampleGame.cs
+++ b/spine-xna/example/src/ExampleGame.cs
@@ -179,27 +179,27 @@ namespace Spine {
base.Draw(gameTime);
}
- public void Start (AnimationState state, int trackIndex) {
+ public void Start (TrackEntry entry) {
#if !WINDOWS_STOREAPP
- Console.WriteLine(trackIndex + " " + state.GetCurrent(trackIndex) + ": start");
+ Console.WriteLine(entry + ": start");
#endif
}
- public void End (AnimationState state, int trackIndex) {
+ public void End (TrackEntry entry) {
#if !WINDOWS_STOREAPP
- Console.WriteLine(trackIndex + " " + state.GetCurrent(trackIndex) + ": end");
+ Console.WriteLine(entry + ": end");
#endif
}
- public void Complete (AnimationState state, int trackIndex, int loopCount) {
+ public void Complete (TrackEntry entry) {
#if !WINDOWS_STOREAPP
- Console.WriteLine(trackIndex + " " + state.GetCurrent(trackIndex) + ": complete " + loopCount);
+ Console.WriteLine(entry + ": complete ");
#endif
}
- public void Event (AnimationState state, int trackIndex, Event e) {
+ public void Event (TrackEntry entry, Event e) {
#if !WINDOWS_STOREAPP
- Console.WriteLine(trackIndex + " " + state.GetCurrent(trackIndex) + ": event " + e);
+ Console.WriteLine(entry + ": event " + e);
#endif
}
}
diff --git a/spine-xna/example/src/ExampleProgram.cs b/spine-xna/example/src/ExampleProgram.cs
index 4ee5042cf..5f8237283 100644
--- a/spine-xna/example/src/ExampleProgram.cs
+++ b/spine-xna/example/src/ExampleProgram.cs
@@ -1,5 +1,4 @@
-
using System;
namespace Spine {
diff --git a/spine-xna/src/MeshBatcher.cs b/spine-xna/src/MeshBatcher.cs
index 803357806..1ff451cf2 100644
--- a/spine-xna/src/MeshBatcher.cs
+++ b/spine-xna/src/MeshBatcher.cs
@@ -1,31 +1,32 @@
/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, Esoteric Software
+ * Spine Runtimes Software License
+ * Version 2.3
+ *
+ * Copyright (c) 2013-2015, Esoteric Software
* All rights reserved.
- *
- * You are granted a perpetual, non-exclusive, non-sublicensable, and
- * non-transferable license to use, install, execute, and perform the Spine
- * Runtimes software and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ *
+ * You are granted a perpetual, non-exclusive, non-sublicensable and
+ * non-transferable license to use, install, execute and perform the Spine
+ * Runtimes Software (the "Software") and derivative works solely for personal
+ * or internal use. Without the written permission of Esoteric Software (see
+ * Section 2 of the Spine Software License Agreement), you may not (a) modify,
+ * translate, adapt or otherwise create derivative works, improvements of the
+ * Software or develop new applications using the Software or (b) remove,
+ * delete, alter or obscure any trademarks or any copyright, trademark, patent
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
- *
+ *
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) 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 THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
@@ -37,7 +38,7 @@ namespace Spine {
// #region License
// /*
// Microsoft Public License (Ms-PL)
- // MonoGame - Copyright ? 2009 The MonoGame Team
+ // MonoGame - Copyright © 2009 The MonoGame Team
//
// All rights reserved.
//
diff --git a/spine-xna/src/RegionBatcher.cs b/spine-xna/src/RegionBatcher.cs
index 33abfd764..e1304e6e3 100644
--- a/spine-xna/src/RegionBatcher.cs
+++ b/spine-xna/src/RegionBatcher.cs
@@ -1,31 +1,32 @@
/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, Esoteric Software
+ * Spine Runtimes Software License
+ * Version 2.3
+ *
+ * Copyright (c) 2013-2015, Esoteric Software
* All rights reserved.
- *
- * You are granted a perpetual, non-exclusive, non-sublicensable, and
- * non-transferable license to use, install, execute, and perform the Spine
- * Runtimes software and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ *
+ * You are granted a perpetual, non-exclusive, non-sublicensable and
+ * non-transferable license to use, install, execute and perform the Spine
+ * Runtimes Software (the "Software") and derivative works solely for personal
+ * or internal use. Without the written permission of Esoteric Software (see
+ * Section 2 of the Spine Software License Agreement), you may not (a) modify,
+ * translate, adapt or otherwise create derivative works, improvements of the
+ * Software or develop new applications using the Software or (b) remove,
+ * delete, alter or obscure any trademarks or any copyright, trademark, patent
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
- *
+ *
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) 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 THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
@@ -37,7 +38,7 @@ namespace Spine {
// #region License
// /*
// Microsoft Public License (Ms-PL)
- // MonoGame - Copyright ? 2009 The MonoGame Team
+ // MonoGame - Copyright © 2009 The MonoGame Team
//
// All rights reserved.
//
diff --git a/spine-xna/src/SkeletonMeshRenderer.cs b/spine-xna/src/SkeletonMeshRenderer.cs
index bbb23be6d..cd566e245 100644
--- a/spine-xna/src/SkeletonMeshRenderer.cs
+++ b/spine-xna/src/SkeletonMeshRenderer.cs
@@ -1,31 +1,32 @@
/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, Esoteric Software
+ * Spine Runtimes Software License
+ * Version 2.3
+ *
+ * Copyright (c) 2013-2015, Esoteric Software
* All rights reserved.
- *
- * You are granted a perpetual, non-exclusive, non-sublicensable, and
- * non-transferable license to use, install, execute, and perform the Spine
- * Runtimes software and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ *
+ * You are granted a perpetual, non-exclusive, non-sublicensable and
+ * non-transferable license to use, install, execute and perform the Spine
+ * Runtimes Software (the "Software") and derivative works solely for personal
+ * or internal use. Without the written permission of Esoteric Software (see
+ * Section 2 of the Spine Software License Agreement), you may not (a) modify,
+ * translate, adapt or otherwise create derivative works, improvements of the
+ * Software or develop new applications using the Software or (b) remove,
+ * delete, alter or obscure any trademarks or any copyright, trademark, patent
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
- *
+ *
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) 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 THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
diff --git a/spine-xna/src/SkeletonRegionRenderer.cs b/spine-xna/src/SkeletonRegionRenderer.cs
index d2f730825..73a703983 100644
--- a/spine-xna/src/SkeletonRegionRenderer.cs
+++ b/spine-xna/src/SkeletonRegionRenderer.cs
@@ -1,31 +1,32 @@
/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, Esoteric Software
+ * Spine Runtimes Software License
+ * Version 2.3
+ *
+ * Copyright (c) 2013-2015, Esoteric Software
* All rights reserved.
- *
- * You are granted a perpetual, non-exclusive, non-sublicensable, and
- * non-transferable license to use, install, execute, and perform the Spine
- * Runtimes software and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ *
+ * You are granted a perpetual, non-exclusive, non-sublicensable and
+ * non-transferable license to use, install, execute and perform the Spine
+ * Runtimes Software (the "Software") and derivative works solely for personal
+ * or internal use. Without the written permission of Esoteric Software (see
+ * Section 2 of the Spine Software License Agreement), you may not (a) modify,
+ * translate, adapt or otherwise create derivative works, improvements of the
+ * Software or develop new applications using the Software or (b) remove,
+ * delete, alter or obscure any trademarks or any copyright, trademark, patent
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
- *
+ *
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) 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 THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
diff --git a/spine-xna/src/Util.cs b/spine-xna/src/Util.cs
index 7baeb286e..1df35a44d 100644
--- a/spine-xna/src/Util.cs
+++ b/spine-xna/src/Util.cs
@@ -1,33 +1,34 @@
/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, Esoteric Software
+ * Spine Runtimes Software License
+ * Version 2.3
+ *
+ * Copyright (c) 2013-2015, Esoteric Software
* All rights reserved.
- *
- * You are granted a perpetual, non-exclusive, non-sublicensable, and
- * non-transferable license to use, install, execute, and perform the Spine
- * Runtimes software and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ *
+ * You are granted a perpetual, non-exclusive, non-sublicensable and
+ * non-transferable license to use, install, execute and perform the Spine
+ * Runtimes Software (the "Software") and derivative works solely for personal
+ * or internal use. Without the written permission of Esoteric Software (see
+ * Section 2 of the Spine Software License Agreement), you may not (a) modify,
+ * translate, adapt or otherwise create derivative works, improvements of the
+ * Software or develop new applications using the Software or (b) remove,
+ * delete, alter or obscure any trademarks or any copyright, trademark, patent
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
- *
+ *
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) 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 THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
using System;
using System.IO;
using Microsoft.Xna.Framework;
@@ -78,4 +79,4 @@ namespace Spine {
return Texture2D.FromStream(device, input);
}
}
-}
+}
diff --git a/spine-xna/src/XnaTextureLoader.cs b/spine-xna/src/XnaTextureLoader.cs
index f7fcea85a..58634807d 100644
--- a/spine-xna/src/XnaTextureLoader.cs
+++ b/spine-xna/src/XnaTextureLoader.cs
@@ -1,31 +1,32 @@
/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, Esoteric Software
+ * Spine Runtimes Software License
+ * Version 2.3
+ *
+ * Copyright (c) 2013-2015, Esoteric Software
* All rights reserved.
- *
- * You are granted a perpetual, non-exclusive, non-sublicensable, and
- * non-transferable license to use, install, execute, and perform the Spine
- * Runtimes software and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ *
+ * You are granted a perpetual, non-exclusive, non-sublicensable and
+ * non-transferable license to use, install, execute and perform the Spine
+ * Runtimes Software (the "Software") and derivative works solely for personal
+ * or internal use. Without the written permission of Esoteric Software (see
+ * Section 2 of the Spine Software License Agreement), you may not (a) modify,
+ * translate, adapt or otherwise create derivative works, improvements of the
+ * Software or develop new applications using the Software or (b) remove,
+ * delete, alter or obscure any trademarks or any copyright, trademark, patent
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
- *
+ *
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) 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 THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;