[csharp] Animation and AnimationState 3.7

This commit is contained in:
pharan 2018-01-19 05:34:20 +08:00
parent 83778a76c1
commit b4c8deb283
2 changed files with 409 additions and 223 deletions

View File

@ -50,8 +50,8 @@ namespace Spine {
} }
/// <summary>Applies all the animation's timelines to the specified skeleton.</summary> /// <summary>Applies all the animation's timelines to the specified skeleton.</summary>
/// <seealso cref="Timeline.Apply(Skeleton, float, float, ExposedList, float, MixPose, MixDirection)"/> /// <seealso cref="Timeline.Apply(Skeleton, float, float, ExposedList, float, MixBlend, MixDirection)"/>
public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList<Event> events, float alpha, MixPose pose, MixDirection direction) { public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList<Event> events, float alpha, MixBlend pose, MixDirection direction) {
if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
if (loop && duration != 0) { if (loop && duration != 0) {
@ -113,28 +113,48 @@ namespace Spine {
/// value. Between 0 and 1 applies a value between the current or setup pose and the timeline value. By adjusting /// value. Between 0 and 1 applies a value between the current or setup pose and the timeline value. By adjusting
/// alpha over time, an animation can be mixed in or out. <code>alpha</code> can also be useful to /// alpha over time, an animation can be mixed in or out. <code>alpha</code> can also be useful to
/// apply animations on top of each other (layered).</param> /// apply animations on top of each other (layered).</param>
/// <param name="pose">Controls how mixing is applied when alpha is than 1.</param> /// <param name="blend">Controls how mixing is applied when alpha is than 1.</param>
/// <param name="direction">Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions such as DrawOrderTimeline and AttachmentTimeline.</param> /// <param name="direction">Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions such as DrawOrderTimeline and AttachmentTimeline.</param>
void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> events, float alpha, MixPose pose, MixDirection direction); void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> events, float alpha, MixBlend blend, MixDirection direction);
int PropertyId { get; } int PropertyId { get; }
} }
/// <summary> /// <summary>
/// Controls how a timeline is mixed with the setup or current pose.</summary> /// Controls how a timeline is mixed with the setup or current pose.</summary>
/// <seealso cref="Timeline.Apply(Skeleton, float, float, ExposedList, float, MixPose, MixDirection)"/> /// <seealso cref="Timeline.Apply(Skeleton, float, float, ExposedList, float, MixBlend, MixDirection)"/>
public enum MixPose { public enum MixBlend {
/// <summary> The timeline value is mixed with the setup pose (the current pose is not used).</summary>
/// <summary> Transitions from the setup value to the timeline value (the current value is not used). Before the first key, the setup value is set..</summary>
Setup, Setup,
/// <summary> The timeline value is mixed with the current pose. The setup pose is used as the timeline value before the first key,
/// except for timelines which perform instant transitions, such as DrawOrderTimeline or AttachmentTimeline.</summary> /// <summary>
Current, /// <para>Transitions from the current value to the timeline value. Before the first key, transitions from the current value to
/// <summary> The timeline value is mixed with the current pose. No change is made before the first key (the current pose is kept until the first key).</summary> /// the setup value. Timelines which perform instant transitions, such as <see cref="DrawOrderTimeline"/> or <see cref="AttachmentTimeline"/>, use the setup value before the first key.</para>
CurrentLayered /// <para>
/// <code>First</code> is intended for the first animations applied, not for animations layered on top of those.</para>
/// </summary>
First,
/// <summary>
/// <para>
/// Transitions from the current value to the timeline value. No change is made before the first key (the current value is kept until the first key).</para>
/// <para>
/// <code>Replace</code> is intended for animations layered on top of others, not for the first animations applied.</para>
/// </summary>
Replace,
/// <summary>
/// <para>
/// Transitions from the current value to the current value plus the timeline value. No change is made before the first key (the current value is kept until the first key).</para>
/// <para>
/// <code>Add</code> is intended for animations layered on top of others, not for the first animations applied.</para>
/// </summary>
Add
} }
/// <summary> /// <summary>
/// Indicates whether a timeline's <code>alpha</code> is mixing out over time toward 0 (the setup or current pose) or mixing in toward 1 (the timeline's pose).</summary> /// Indicates whether a timeline's <code>alpha</code> is mixing out over time toward 0 (the setup or current pose value) or mixing in toward 1 (the timeline's value).</summary>
/// <seealso cref="Timeline.Apply(Skeleton, float, float, ExposedList, float, MixPose, MixDirection)"/> /// <seealso cref="Timeline.Apply(Skeleton, float, float, ExposedList, float, MixBlend, MixDirection)"/>
public enum MixDirection { public enum MixDirection {
In, In,
Out Out
@ -162,7 +182,7 @@ namespace Spine {
curves = new float[(frameCount - 1) * BEZIER_SIZE]; curves = new float[(frameCount - 1) * BEZIER_SIZE];
} }
abstract public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixPose pose, MixDirection direction); abstract public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend pose, MixDirection direction);
abstract public int PropertyId { get; } abstract public int PropertyId { get; }
@ -258,31 +278,38 @@ namespace Spine {
frames[frameIndex + ROTATION] = degrees; frames[frameIndex + ROTATION] = degrees;
} }
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixPose pose, MixDirection direction) { override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
Bone bone = skeleton.bones.Items[boneIndex]; Bone bone = skeleton.bones.Items[boneIndex];
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { if (time < frames[0]) {
switch (pose) { switch (blend) {
case MixPose.Setup: case MixBlend.Setup:
bone.rotation = bone.data.rotation; bone.rotation = bone.data.rotation;
return; return;
case MixPose.Current: case MixBlend.First:
float rr = bone.data.rotation - bone.rotation; float r = bone.data.rotation - bone.rotation;
rr -= (16384 - (int)(16384.499999999996 - rr / 360)) * 360; bone.rotation += (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * alpha;
bone.rotation += rr * alpha; return;
return;
} }
return; return;
} }
if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
if (pose == MixPose.Setup) { float r = frames[frames.Length + PREV_ROTATION];
bone.rotation = bone.data.rotation + frames[frames.Length + PREV_ROTATION] * alpha; switch (blend) {
} else { case MixBlend.Setup:
float rr = bone.data.rotation + frames[frames.Length + PREV_ROTATION] - bone.rotation; bone.rotation = bone.data.rotation + r * alpha;
rr -= (16384 - (int)(16384.499999999996 - rr / 360)) * 360; // Wrap within -180 and 180. break;
bone.rotation += rr * alpha; case MixBlend.First:
case MixBlend.Replace:
r += bone.data.rotation - bone.rotation;
r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360;
goto case MixBlend.Add; // Fall through.
case MixBlend.Add:
bone.rotation += r * alpha;
break;
} }
return; return;
} }
@ -292,18 +319,23 @@ namespace Spine {
float prevRotation = frames[frame + PREV_ROTATION]; float prevRotation = frames[frame + PREV_ROTATION];
float frameTime = frames[frame]; float frameTime = frames[frame];
float percent = GetCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); float percent = GetCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
{
float r = frames[frame + ROTATION] - prevRotation; float r = frames[frame + ROTATION] - prevRotation;
r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; r = prevRotation + (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * percent;
r = prevRotation + r * percent; switch (blend) {
if (pose == MixPose.Setup) { case MixBlend.Setup:
r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; bone.rotation = bone.data.rotation + (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * alpha;
bone.rotation = bone.data.rotation + r * alpha; break;
} else { case MixBlend.First:
r = bone.data.rotation + r - bone.rotation; case MixBlend.Replace:
r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; r += bone.data.rotation - bone.rotation;
bone.rotation += r * alpha; goto case MixBlend.Add; // Fall through.
case MixBlend.Add:
bone.rotation += (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * alpha;
break;
}
} }
} }
} }
@ -335,17 +367,17 @@ namespace Spine {
frames[frameIndex + Y] = y; frames[frameIndex + Y] = y;
} }
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixPose pose, MixDirection direction) { override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
Bone bone = skeleton.bones.Items[boneIndex]; Bone bone = skeleton.bones.Items[boneIndex];
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { if (time < frames[0]) {
switch (pose) { switch (blend) {
case MixPose.Setup: case MixBlend.Setup:
bone.x = bone.data.x; bone.x = bone.data.x;
bone.y = bone.data.y; bone.y = bone.data.y;
return; return;
case MixPose.Current: case MixBlend.First:
bone.x += (bone.data.x - bone.x) * alpha; bone.x += (bone.data.x - bone.x) * alpha;
bone.y += (bone.data.y - bone.y) * alpha; bone.y += (bone.data.y - bone.y) * alpha;
return; return;
@ -369,12 +401,20 @@ namespace Spine {
x += (frames[frame + X] - x) * percent; x += (frames[frame + X] - x) * percent;
y += (frames[frame + Y] - y) * percent; y += (frames[frame + Y] - y) * percent;
} }
if (pose == MixPose.Setup) { switch (blend) {
bone.x = bone.data.x + x * alpha; case MixBlend.Setup:
bone.y = bone.data.y + y * alpha; bone.x = bone.data.x + x * alpha;
} else { bone.y = bone.data.y + y * alpha;
bone.x += (bone.data.x + x - bone.x) * alpha; break;
bone.y += (bone.data.y + y - bone.y) * alpha; case MixBlend.First:
case MixBlend.Replace:
bone.x += (bone.data.x + x - bone.x) * alpha;
bone.y += (bone.data.y + y - bone.y) * alpha;
break;
case MixBlend.Add:
bone.x += x * alpha;
bone.y += y * alpha;
break;
} }
} }
} }
@ -388,17 +428,17 @@ namespace Spine {
: base(frameCount) { : base(frameCount) {
} }
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixPose pose, MixDirection direction) { override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
Bone bone = skeleton.bones.Items[boneIndex]; Bone bone = skeleton.bones.Items[boneIndex];
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { if (time < frames[0]) {
switch (pose) { switch (blend) {
case MixPose.Setup: case MixBlend.Setup:
bone.scaleX = bone.data.scaleX; bone.scaleX = bone.data.scaleX;
bone.scaleY = bone.data.scaleY; bone.scaleY = bone.data.scaleY;
return; return;
case MixPose.Current: case MixBlend.First:
bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha; bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha;
bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha; bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha;
return; return;
@ -423,27 +463,61 @@ namespace Spine {
y = (y + (frames[frame + Y] - y) * percent) * bone.data.scaleY; y = (y + (frames[frame + Y] - y) * percent) * bone.data.scaleY;
} }
if (alpha == 1) { if (alpha == 1) {
bone.scaleX = x; if (blend == MixBlend.Add) {
bone.scaleY = y; bone.scaleX += x - bone.data.scaleX;
bone.scaleY += y - bone.data.scaleY;
} else {
bone.scaleX = x;
bone.scaleY = y;
}
} else { } else {
float bx, by;
if (pose == MixPose.Setup) {
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. // Mixing out uses sign of setup or current pose, else use sign of key.
float bx, by;
if (direction == MixDirection.Out) { if (direction == MixDirection.Out) {
x = (x >= 0 ? x : -x) * (bx >= 0 ? 1 : -1); switch (blend) {
y = (y >= 0 ? y : -y) * (by >= 0 ? 1 : -1); case MixBlend.Setup:
bx = bone.data.scaleX;
by = bone.data.scaleY;
bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha;
bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha;
break;
case MixBlend.First:
case MixBlend.Replace:
bx = bone.scaleX;
by = bone.scaleY;
bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha;
bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha;
break;
case MixBlend.Add:
bx = bone.scaleX;
by = bone.scaleY;
bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bone.data.scaleX) * alpha;
bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - bone.data.scaleY) * alpha;
break;
}
} else { } else {
bx = (bx >= 0 ? bx : -bx) * (x >= 0 ? 1 : -1); switch (blend) {
by = (by >= 0 ? by : -by) * (y >= 0 ? 1 : -1); case MixBlend.Setup:
bx = Math.Abs(bone.data.scaleX) * Math.Sign(x);
by = Math.Abs(bone.data.scaleY) * Math.Sign(y);
bone.scaleX = bx + (x - bx) * alpha;
bone.scaleY = by + (y - by) * alpha;
break;
case MixBlend.First:
case MixBlend.Replace:
bx = Math.Abs(bone.scaleX) * Math.Sign(x);
by = Math.Abs(bone.scaleY) * Math.Sign(y);
bone.scaleX = bx + (x - bx) * alpha;
bone.scaleY = by + (y - by) * alpha;
break;
case MixBlend.Add:
bx = Math.Sign(x);
by = Math.Sign(y);
bone.scaleX = Math.Abs(bone.scaleX) * bx + (x - Math.Abs(bone.data.scaleX) * bx) * alpha;
bone.scaleY = Math.Abs(bone.scaleY) * by + (y - Math.Abs(bone.data.scaleY) * by) * alpha;
break;
}
} }
bone.scaleX = bx + (x - bx) * alpha;
bone.scaleY = by + (y - by) * alpha;
} }
} }
} }
@ -457,16 +531,16 @@ namespace Spine {
: base(frameCount) { : base(frameCount) {
} }
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixPose pose, MixDirection direction) { override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
Bone bone = skeleton.bones.Items[boneIndex]; Bone bone = skeleton.bones.Items[boneIndex];
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { if (time < frames[0]) {
switch (pose) { switch (blend) {
case MixPose.Setup: case MixBlend.Setup:
bone.shearX = bone.data.shearX; bone.shearX = bone.data.shearX;
bone.shearY = bone.data.shearY; bone.shearY = bone.data.shearY;
return; return;
case MixPose.Current: case MixBlend.First:
bone.shearX += (bone.data.shearX - bone.shearX) * alpha; bone.shearX += (bone.data.shearX - bone.shearX) * alpha;
bone.shearY += (bone.data.shearY - bone.shearY) * alpha; bone.shearY += (bone.data.shearY - bone.shearY) * alpha;
return; return;
@ -490,12 +564,20 @@ namespace Spine {
x = x + (frames[frame + X] - x) * percent; x = x + (frames[frame + X] - x) * percent;
y = y + (frames[frame + Y] - y) * percent; y = y + (frames[frame + Y] - y) * percent;
} }
if (pose == MixPose.Setup) { switch (blend) {
bone.shearX = bone.data.shearX + x * alpha; case MixBlend.Setup:
bone.shearY = bone.data.shearY + y * alpha; bone.shearX = bone.data.shearX + x * alpha;
} else { bone.shearY = bone.data.shearY + y * alpha;
bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha; break;
bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha; case MixBlend.First:
case MixBlend.Replace:
bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha;
bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha;
break;
case MixBlend.Add:
bone.shearX += x * alpha;
bone.shearY += y * alpha;
break;
} }
} }
} }
@ -530,19 +612,19 @@ namespace Spine {
frames[frameIndex + A] = a; frames[frameIndex + A] = a;
} }
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixPose pose, MixDirection direction) { override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
Slot slot = skeleton.slots.Items[slotIndex]; Slot slot = skeleton.slots.Items[slotIndex];
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { if (time < frames[0]) {
var slotData = slot.data; var slotData = slot.data;
switch (pose) { switch (blend) {
case MixPose.Setup: case MixBlend.Setup:
slot.r = slotData.r; slot.r = slotData.r;
slot.g = slotData.g; slot.g = slotData.g;
slot.b = slotData.b; slot.b = slotData.b;
slot.a = slotData.a; slot.a = slotData.a;
return; return;
case MixPose.Current: case MixBlend.First:
slot.r += (slot.r - slotData.r) * alpha; slot.r += (slot.r - slotData.r) * alpha;
slot.g += (slot.g - slotData.g) * alpha; slot.g += (slot.g - slotData.g) * alpha;
slot.b += (slot.b - slotData.b) * alpha; slot.b += (slot.b - slotData.b) * alpha;
@ -582,7 +664,7 @@ namespace Spine {
slot.a = a; slot.a = a;
} else { } else {
float br, bg, bb, ba; float br, bg, bb, ba;
if (pose == MixPose.Setup) { if (blend == MixBlend.Setup) {
br = slot.data.r; br = slot.data.r;
bg = slot.data.g; bg = slot.data.g;
bb = slot.data.b; bb = slot.data.b;
@ -641,13 +723,13 @@ namespace Spine {
frames[frameIndex + B2] = b2; frames[frameIndex + B2] = b2;
} }
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixPose pose, MixDirection direction) { override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
Slot slot = skeleton.slots.Items[slotIndex]; Slot slot = skeleton.slots.Items[slotIndex];
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame. if (time < frames[0]) { // Time is before first frame.
var slotData = slot.data; var slotData = slot.data;
switch (pose) { switch (blend) {
case MixPose.Setup: case MixBlend.Setup:
// slot.color.set(slot.data.color); // slot.color.set(slot.data.color);
// slot.darkColor.set(slot.data.darkColor); // slot.darkColor.set(slot.data.darkColor);
slot.r = slotData.r; slot.r = slotData.r;
@ -658,7 +740,7 @@ namespace Spine {
slot.g2 = slotData.g2; slot.g2 = slotData.g2;
slot.b2 = slotData.b2; slot.b2 = slotData.b2;
return; return;
case MixPose.Current: case MixBlend.First:
slot.r += (slot.r - slotData.r) * alpha; slot.r += (slot.r - slotData.r) * alpha;
slot.g += (slot.g - slotData.g) * alpha; slot.g += (slot.g - slotData.g) * alpha;
slot.b += (slot.b - slotData.b) * alpha; slot.b += (slot.b - slotData.b) * alpha;
@ -713,7 +795,7 @@ namespace Spine {
slot.b2 = b2; slot.b2 = b2;
} else { } else {
float br, bg, bb, ba, br2, bg2, bb2; float br, bg, bb, ba, br2, bg2, bb2;
if (pose == MixPose.Setup) { if (blend == MixBlend.Setup) {
br = slot.data.r; br = slot.data.r;
bg = slot.data.g; bg = slot.data.g;
bb = slot.data.b; bb = slot.data.b;
@ -767,10 +849,10 @@ namespace Spine {
attachmentNames[frameIndex] = attachmentName; attachmentNames[frameIndex] = attachmentName;
} }
public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixPose pose, MixDirection direction) { public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
string attachmentName; string attachmentName;
Slot slot = skeleton.slots.Items[slotIndex]; Slot slot = skeleton.slots.Items[slotIndex];
if (direction == MixDirection.Out && pose == MixPose.Setup) { if (direction == MixDirection.Out && blend == MixBlend.Setup) {
attachmentName = slot.data.attachmentName; attachmentName = slot.data.attachmentName;
slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName);
return; return;
@ -778,7 +860,7 @@ namespace Spine {
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame. if (time < frames[0]) { // Time is before first frame.
if (pose == MixPose.Setup) { if (blend == MixBlend.Setup || blend == MixBlend.First) {
attachmentName = slot.data.attachmentName; attachmentName = slot.data.attachmentName;
slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName);
} }
@ -823,13 +905,13 @@ namespace Spine {
frameVertices[frameIndex] = vertices; frameVertices[frameIndex] = vertices;
} }
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixPose pose, MixDirection direction) { override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
Slot slot = skeleton.slots.Items[slotIndex]; Slot slot = skeleton.slots.Items[slotIndex];
VertexAttachment vertexAttachment = slot.attachment as VertexAttachment; VertexAttachment vertexAttachment = slot.attachment as VertexAttachment;
if (vertexAttachment == null || !vertexAttachment.ApplyDeform(attachment)) return; if (vertexAttachment == null || !vertexAttachment.ApplyDeform(attachment)) return;
var verticesArray = slot.attachmentVertices; var verticesArray = slot.attachmentVertices;
if (verticesArray.Count == 0) alpha = 1; if (verticesArray.Count == 0) blend = MixBlend.Setup;
float[][] frameVertices = this.frameVertices; float[][] frameVertices = this.frameVertices;
int vertexCount = frameVertices[0].Length; int vertexCount = frameVertices[0].Length;
@ -838,11 +920,11 @@ namespace Spine {
if (time < frames[0]) { if (time < frames[0]) {
switch (pose) { switch (blend) {
case MixPose.Setup: case MixBlend.Setup:
verticesArray.Clear(); verticesArray.Clear();
return; return;
case MixPose.Current: case MixBlend.Replace:
if (alpha == 1) { if (alpha == 1) {
verticesArray.Clear(); verticesArray.Clear();
return; return;
@ -877,27 +959,60 @@ namespace Spine {
vertices = verticesArray.Items; vertices = verticesArray.Items;
if (time >= frames[frames.Length - 1]) { // Time is after last frame. if (time >= frames[frames.Length - 1]) { // Time is after last frame.
float[] lastVertices = frameVertices[frames.Length - 1]; float[] lastVertices = frameVertices[frames.Length - 1];
if (alpha == 1) { if (alpha == 1) {
// Vertex positions or deform offsets, no alpha. if (blend == MixBlend.Add) {
Array.Copy(lastVertices, 0, vertices, 0, vertexCount); if (vertexAttachment.bones == null) {
} else if (pose == MixPose.Setup) { // Unweighted vertex positions, no alpha.
if (vertexAttachment.bones == null) { float[] setupVertices = vertexAttachment.vertices;
// Unweighted vertex positions, with alpha. for (int i = 0; i < vertexCount; i++)
float[] setupVertices = vertexAttachment.vertices; vertices[i] += lastVertices[i] - setupVertices[i];
for (int i = 0; i < vertexCount; i++) { } else {
float setup = setupVertices[i]; // Weighted deform offsets, no alpha.
vertices[i] = setup + (lastVertices[i] - setup) * alpha; for (int i = 0; i < vertexCount; i++)
vertices[i] += lastVertices[i];
} }
} else { } else {
// Weighted deform offsets, with alpha. // Vertex positions or deform offsets, no alpha.
for (int i = 0; i < vertexCount; i++) Array.Copy(lastVertices, 0, vertices, 0, vertexCount);
vertices[i] = lastVertices[i] * alpha;
} }
} else { } else {
// Vertex positions or deform offsets, with alpha. switch (blend) {
for (int i = 0; i < vertexCount; i++) case MixBlend.Setup: {
vertices[i] += (lastVertices[i] - vertices[i]) * alpha; 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;
}
break;
}
case MixBlend.First:
case MixBlend.Replace:
// Vertex positions or deform offsets, with alpha.
for (int i = 0; i < vertexCount; i++)
vertices[i] += (lastVertices[i] - vertices[i]) * alpha;
break;
case MixBlend.Add:
if (vertexAttachment.bones == null) {
// Unweighted vertex positions, no alpha.
float[] setupVertices = vertexAttachment.vertices;
for (int i = 0; i < vertexCount; i++)
vertices[i] += (lastVertices[i] - setupVertices[i]) * alpha;
} else {
// Weighted deform offsets, alpha.
for (int i = 0; i < vertexCount; i++)
vertices[i] += lastVertices[i] * alpha;
}
break;
}
} }
return; return;
} }
@ -910,31 +1025,73 @@ namespace Spine {
float percent = GetCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime)); float percent = GetCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime));
if (alpha == 1) { if (alpha == 1) {
// Vertex positions or deform offsets, no alpha. if (blend == MixBlend.Add) {
for (int i = 0; i < vertexCount; i++) { if (vertexAttachment.bones == null) {
float prev = prevVertices[i]; // Unweighted vertex positions, no alpha.
vertices[i] = prev + (nextVertices[i] - prev) * percent; float[] setupVertices = vertexAttachment.vertices;
} for (int i = 0; i < vertexCount; i++) {
} else if (pose == MixPose.Setup) { float prev = prevVertices[i];
if (vertexAttachment.bones == null) { vertices[i] += prev + (nextVertices[i] - prev) * percent - setupVertices[i];
// Unweighted vertex positions, with alpha. }
var setupVertices = vertexAttachment.vertices; } else {
for (int i = 0; i < vertexCount; i++) { // Weighted deform offsets, no alpha.
float prev = prevVertices[i], setup = setupVertices[i]; for (int i = 0; i < vertexCount; i++) {
vertices[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha; float prev = prevVertices[i];
vertices[i] += prev + (nextVertices[i] - prev) * percent;
}
} }
} else { } else {
// Weighted deform offsets, with alpha. // Vertex positions or deform offsets, no alpha.
for (int i = 0; i < vertexCount; i++) { for (int i = 0; i < vertexCount; i++) {
float prev = prevVertices[i]; float prev = prevVertices[i];
vertices[i] = (prev + (nextVertices[i] - prev) * percent) * alpha; vertices[i] = prev + (nextVertices[i] - prev) * percent;
} }
} }
} else { } else {
// Vertex positions or deform offsets, with alpha. switch (blend) {
for (int i = 0; i < vertexCount; i++) { case MixBlend.Setup: {
float prev = prevVertices[i]; if (vertexAttachment.bones == null) {
vertices[i] += (prev + (nextVertices[i] - prev) * percent - vertices[i]) * alpha; // Unweighted vertex positions, with alpha.
float[] 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;
}
}
break;
}
case MixBlend.First:
case MixBlend.Replace: {
// 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;
}
break;
}
case MixBlend.Add: {
if (vertexAttachment.bones == null) {
// Unweighted vertex positions, with alpha.
float[] setupVertices = vertexAttachment.vertices;
for (int i = 0; i < vertexCount; i++) {
float prev = prevVertices[i];
vertices[i] += (prev + (nextVertices[i] - prev) * percent - setupVertices[i]) * 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;
}
}
break;
}
} }
} }
} }
@ -964,13 +1121,13 @@ namespace Spine {
} }
/// <summary>Fires events for frames &gt; lastTime and &lt;= time.</summary> /// <summary>Fires events for frames &gt; lastTime and &lt;= time.</summary>
public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixPose pose, MixDirection direction) { public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
if (firedEvents == null) return; if (firedEvents == null) return;
float[] frames = this.frames; float[] frames = this.frames;
int frameCount = frames.Length; int frameCount = frames.Length;
if (lastTime > time) { // Fire events after last time for looped animations. if (lastTime > time) { // Fire events after last time for looped animations.
Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha, pose, direction); Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha, blend, direction);
lastTime = -1f; lastTime = -1f;
} else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. } else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame.
return; return;
@ -1016,17 +1173,17 @@ namespace Spine {
drawOrders[frameIndex] = drawOrder; drawOrders[frameIndex] = drawOrder;
} }
public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixPose pose, MixDirection direction) { public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
ExposedList<Slot> drawOrder = skeleton.drawOrder; ExposedList<Slot> drawOrder = skeleton.drawOrder;
ExposedList<Slot> slots = skeleton.slots; ExposedList<Slot> slots = skeleton.slots;
if (direction == MixDirection.Out && pose == MixPose.Setup) { if (direction == MixDirection.Out && blend == MixBlend.Setup) {
Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count); Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count);
return; return;
} }
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { if (time < frames[0]) {
if (pose == MixPose.Setup) Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count); if (blend == MixBlend.Setup || blend == MixBlend.First) Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count);
return; return;
} }
@ -1078,16 +1235,16 @@ namespace Spine {
frames[frameIndex + BEND_DIRECTION] = bendDirection; frames[frameIndex + BEND_DIRECTION] = bendDirection;
} }
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixPose pose, MixDirection direction) { override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex]; IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex];
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { if (time < frames[0]) {
switch (pose) { switch (blend) {
case MixPose.Setup: case MixBlend.Setup:
constraint.mix = constraint.data.mix; constraint.mix = constraint.data.mix;
constraint.bendDirection = constraint.data.bendDirection; constraint.bendDirection = constraint.data.bendDirection;
return; return;
case MixPose.Current: case MixBlend.First:
constraint.mix += (constraint.data.mix - constraint.mix) * alpha; constraint.mix += (constraint.data.mix - constraint.mix) * alpha;
constraint.bendDirection = constraint.data.bendDirection; constraint.bendDirection = constraint.data.bendDirection;
return; return;
@ -1096,7 +1253,7 @@ namespace Spine {
} }
if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
if (pose == MixPose.Setup) { if (blend == MixBlend.Setup) {
constraint.mix = constraint.data.mix + (frames[frames.Length + PREV_MIX] - constraint.data.mix) * alpha; constraint.mix = constraint.data.mix + (frames[frames.Length + PREV_MIX] - constraint.data.mix) * alpha;
constraint.bendDirection = direction == MixDirection.Out ? constraint.data.bendDirection constraint.bendDirection = direction == MixDirection.Out ? constraint.data.bendDirection
: (int)frames[frames.Length + PREV_BEND_DIRECTION]; : (int)frames[frames.Length + PREV_BEND_DIRECTION];
@ -1113,7 +1270,7 @@ namespace Spine {
float frameTime = frames[frame]; float frameTime = frames[frame];
float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
if (pose == MixPose.Setup) { if (blend == MixBlend.Setup) {
constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha; constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha;
constraint.bendDirection = direction == MixDirection.Out ? constraint.data.bendDirection : (int)frames[frame + PREV_BEND_DIRECTION]; constraint.bendDirection = direction == MixDirection.Out ? constraint.data.bendDirection : (int)frames[frame + PREV_BEND_DIRECTION];
} else { } else {
@ -1152,19 +1309,19 @@ namespace Spine {
frames[frameIndex + SHEAR] = shearMix; frames[frameIndex + SHEAR] = shearMix;
} }
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixPose pose, MixDirection direction) { override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex]; TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex];
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { if (time < frames[0]) {
var data = constraint.data; var data = constraint.data;
switch (pose) { switch (blend) {
case MixPose.Setup: case MixBlend.Setup:
constraint.rotateMix = data.rotateMix; constraint.rotateMix = data.rotateMix;
constraint.translateMix = data.translateMix; constraint.translateMix = data.translateMix;
constraint.scaleMix = data.scaleMix; constraint.scaleMix = data.scaleMix;
constraint.shearMix = data.shearMix; constraint.shearMix = data.shearMix;
return; return;
case MixPose.Current: case MixBlend.First:
constraint.rotateMix += (data.rotateMix - constraint.rotateMix) * alpha; constraint.rotateMix += (data.rotateMix - constraint.rotateMix) * alpha;
constraint.translateMix += (data.translateMix - constraint.translateMix) * alpha; constraint.translateMix += (data.translateMix - constraint.translateMix) * alpha;
constraint.scaleMix += (data.scaleMix - constraint.scaleMix) * alpha; constraint.scaleMix += (data.scaleMix - constraint.scaleMix) * alpha;
@ -1197,7 +1354,7 @@ namespace Spine {
scale += (frames[frame + SCALE] - scale) * percent; scale += (frames[frame + SCALE] - scale) * percent;
shear += (frames[frame + SHEAR] - shear) * percent; shear += (frames[frame + SHEAR] - shear) * percent;
} }
if (pose == MixPose.Setup) { if (blend == MixBlend.Setup) {
TransformConstraintData data = constraint.data; TransformConstraintData data = constraint.data;
constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha; constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha;
constraint.translateMix = data.translateMix + (translate - data.translateMix) * alpha; constraint.translateMix = data.translateMix + (translate - data.translateMix) * alpha;
@ -1239,15 +1396,15 @@ namespace Spine {
frames[frameIndex + VALUE] = value; frames[frameIndex + VALUE] = value;
} }
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixPose pose, MixDirection direction) { override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex];
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { if (time < frames[0]) {
switch (pose) { switch (blend) {
case MixPose.Setup: case MixBlend.Setup:
constraint.position = constraint.data.position; constraint.position = constraint.data.position;
return; return;
case MixPose.Current: case MixBlend.First:
constraint.position += (constraint.data.position - constraint.position) * alpha; constraint.position += (constraint.data.position - constraint.position) * alpha;
return; return;
} }
@ -1267,7 +1424,7 @@ namespace Spine {
position += (frames[frame + VALUE] - position) * percent; position += (frames[frame + VALUE] - position) * percent;
} }
if (pose == MixPose.Setup) if (blend == MixBlend.Setup)
constraint.position = constraint.data.position + (position - constraint.data.position) * alpha; constraint.position = constraint.data.position + (position - constraint.data.position) * alpha;
else else
constraint.position += (position - constraint.position) * alpha; constraint.position += (position - constraint.position) * alpha;
@ -1283,15 +1440,15 @@ namespace Spine {
: base(frameCount) { : base(frameCount) {
} }
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixPose pose, MixDirection direction) { override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex];
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { if (time < frames[0]) {
switch (pose) { switch (blend) {
case MixPose.Setup: case MixBlend.Setup:
constraint.spacing = constraint.data.spacing; constraint.spacing = constraint.data.spacing;
return; return;
case MixPose.Current: case MixBlend.First:
constraint.spacing += (constraint.data.spacing - constraint.spacing) * alpha; constraint.spacing += (constraint.data.spacing - constraint.spacing) * alpha;
return; return;
} }
@ -1312,7 +1469,7 @@ namespace Spine {
spacing += (frames[frame + VALUE] - spacing) * percent; spacing += (frames[frame + VALUE] - spacing) * percent;
} }
if (pose == MixPose.Setup) if (blend == MixBlend.Setup)
constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha; constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha;
else else
constraint.spacing += (spacing - constraint.spacing) * alpha; constraint.spacing += (spacing - constraint.spacing) * alpha;
@ -1347,16 +1504,16 @@ namespace Spine {
frames[frameIndex + TRANSLATE] = translateMix; frames[frameIndex + TRANSLATE] = translateMix;
} }
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixPose pose, MixDirection direction) { override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex];
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { if (time < frames[0]) {
switch (pose) { switch (blend) {
case MixPose.Setup: case MixBlend.Setup:
constraint.rotateMix = constraint.data.rotateMix; constraint.rotateMix = constraint.data.rotateMix;
constraint.translateMix = constraint.data.translateMix; constraint.translateMix = constraint.data.translateMix;
return; return;
case MixPose.Current: case MixBlend.First:
constraint.rotateMix += (constraint.data.rotateMix - constraint.rotateMix) * alpha; constraint.rotateMix += (constraint.data.rotateMix - constraint.rotateMix) * alpha;
constraint.translateMix += (constraint.data.translateMix - constraint.translateMix) * alpha; constraint.translateMix += (constraint.data.translateMix - constraint.translateMix) * alpha;
return; return;
@ -1381,7 +1538,7 @@ namespace Spine {
translate += (frames[frame + TRANSLATE] - translate) * percent; translate += (frames[frame + TRANSLATE] - translate) * percent;
} }
if (pose == MixPose.Setup) { if (blend == MixBlend.Setup) {
constraint.rotateMix = constraint.data.rotateMix + (rotate - constraint.data.rotateMix) * alpha; constraint.rotateMix = constraint.data.rotateMix + (rotate - constraint.data.rotateMix) * alpha;
constraint.translateMix = constraint.data.translateMix + (translate - constraint.data.translateMix) * alpha; constraint.translateMix = constraint.data.translateMix + (translate - constraint.data.translateMix) * alpha;
} else { } else {

View File

@ -54,13 +54,13 @@ namespace Spine {
public ExposedList<TrackEntry> Tracks { get { return tracks; } } public ExposedList<TrackEntry> Tracks { get { return tracks; } }
public float TimeScale { get { return timeScale; } set { timeScale = value; } } public float TimeScale { get { return timeScale; } set { timeScale = value; } }
public delegate void TrackEntryDelegate (TrackEntry trackEntry); public delegate void TrackEntryDelegate(TrackEntry trackEntry);
public event TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; public event TrackEntryDelegate Start, Interrupt, End, Dispose, Complete;
public delegate void TrackEntryEventDelegate (TrackEntry trackEntry, Event e); public delegate void TrackEntryEventDelegate(TrackEntry trackEntry, Event e);
public event TrackEntryEventDelegate Event; public event TrackEntryEventDelegate Event;
public AnimationState (AnimationStateData data) { public AnimationState(AnimationStateData data) {
if (data == null) throw new ArgumentNullException("data", "data cannot be null."); if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
this.data = data; this.data = data;
this.queue = new EventQueue( this.queue = new EventQueue(
@ -171,12 +171,13 @@ namespace Spine {
TrackEntry current = tracksItems[i]; TrackEntry current = tracksItems[i];
if (current == null || current.delay > 0) continue; if (current == null || current.delay > 0) continue;
applied = true; applied = true;
MixPose currentPose = i == 0 ? MixPose.Current : MixPose.CurrentLayered;
MixBlend blend = i == 0 ? MixBlend.First : current.mixBlend;
// Apply mixing from entries first. // Apply mixing from entries first.
float mix = current.alpha; float mix = current.alpha;
if (current.mixingFrom != null) if (current.mixingFrom != null)
mix *= ApplyMixingFrom(current, skeleton, currentPose); mix *= ApplyMixingFrom(current, skeleton, blend);
else if (current.trackTime >= current.trackEnd && current.next == null) // else if (current.trackTime >= current.trackEnd && current.next == null) //
mix = 0; // Set to setup pose the last time the entry will be applied. mix = 0; // Set to setup pose the last time the entry will be applied.
@ -185,9 +186,9 @@ namespace Spine {
int timelineCount = current.animation.timelines.Count; int timelineCount = current.animation.timelines.Count;
var timelines = current.animation.timelines; var timelines = current.animation.timelines;
var timelinesItems = timelines.Items; var timelinesItems = timelines.Items;
if (mix == 1) { if (mix == 1 || blend == MixBlend.Add) {
for (int ii = 0; ii < timelineCount; ii++) for (int ii = 0; ii < timelineCount; ii++)
timelinesItems[ii].Apply(skeleton, animationLast, animationTime, events, 1, MixPose.Setup, MixDirection.In); timelinesItems[ii].Apply(skeleton, animationLast, animationTime, events, mix, blend, MixDirection.In);
} else { } else {
var timelineData = current.timelineData.Items; var timelineData = current.timelineData.Items;
@ -197,12 +198,12 @@ namespace Spine {
for (int ii = 0; ii < timelineCount; ii++) { for (int ii = 0; ii < timelineCount; ii++) {
Timeline timeline = timelinesItems[ii]; Timeline timeline = timelinesItems[ii];
MixPose pose = timelineData[ii] >= AnimationState.First ? MixPose.Setup : currentPose; MixBlend timelineBlend = timelineData[ii] >= AnimationState.Subsequent ? blend : MixBlend.Setup;
var rotateTimeline = timeline as RotateTimeline; var rotateTimeline = timeline as RotateTimeline;
if (rotateTimeline != null) if (rotateTimeline != null)
ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, mix, pose, timelinesRotation, ii << 1, firstFrame); ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation, ii << 1, firstFrame);
else else
timeline.Apply(skeleton, animationLast, animationTime, events, mix, pose, MixDirection.In); timeline.Apply(skeleton, animationLast, animationTime, events, mix, timelineBlend, MixDirection.In);
} }
} }
QueueEvents(current, animationTime); QueueEvents(current, animationTime);
@ -215,17 +216,18 @@ namespace Spine {
return applied; return applied;
} }
private float ApplyMixingFrom (TrackEntry to, Skeleton skeleton, MixPose currentPose) { private float ApplyMixingFrom (TrackEntry to, Skeleton skeleton, MixBlend blend) {
TrackEntry from = to.mixingFrom; TrackEntry from = to.mixingFrom;
if (from.mixingFrom != null) ApplyMixingFrom(from, skeleton, currentPose); if (from.mixingFrom != null) ApplyMixingFrom(from, skeleton, blend);
float mix; float mix;
if (to.mixDuration == 0) { // Single frame mix to undo mixingFrom changes. if (to.mixDuration == 0) { // Single frame mix to undo mixingFrom changes.
mix = 1; mix = 1;
currentPose = MixPose.Setup; if (blend == MixBlend.First) blend = MixBlend.Setup; // Tracks > 0 are transparent and can't reset to setup pose.
} else { } else {
mix = to.mixTime / to.mixDuration; mix = to.mixTime / to.mixDuration;
if (mix > 1) mix = 1; if (mix > 1) mix = 1;
if (blend != MixBlend.First) blend = from.mixBlend; // Track 0 ignores frack mix blend.
} }
var eventBuffer = mix < from.eventThreshold ? this.events : null; var eventBuffer = mix < from.eventThreshold ? this.events : null;
@ -234,45 +236,55 @@ namespace Spine {
var timelines = from.animation.timelines; var timelines = from.animation.timelines;
int timelineCount = timelines.Count; int timelineCount = timelines.Count;
var timelinesItems = timelines.Items; var timelinesItems = timelines.Items;
var timelineData = from.timelineData.Items; float alphaDip = from.alpha * to.interruptAlpha, alphaMix = alphaDip * (1 - mix);
var timelineDipMix = from.timelineDipMix.Items;
bool firstFrame = from.timelinesRotation.Count == 0; if (blend == MixBlend.Add) {
if (firstFrame) from.timelinesRotation.Resize(timelines.Count << 1); // from.timelinesRotation.setSize for (int i = 0; i < timelineCount; i++)
var timelinesRotation = from.timelinesRotation.Items; (timelinesItems[i]).Apply(skeleton, animationLast, animationTime, eventBuffer, alphaMix, blend, MixDirection.Out);
} else {
var timelineData = from.timelineData.Items;
var timelineDipMix = from.timelineDipMix.Items;
MixPose pose; bool firstFrame = from.timelinesRotation.Count == 0;
float alphaDip = from.alpha * to.interruptAlpha, alphaMix = alphaDip * (1 - mix), alpha; if (firstFrame) from.timelinesRotation.Resize(timelines.Count << 1); // from.timelinesRotation.setSize
from.totalAlpha = 0; var timelinesRotation = from.timelinesRotation.Items;
for (int i = 0; i < timelineCount; i++) {
Timeline timeline = timelinesItems[i]; from.totalAlpha = 0;
switch (timelineData[i]) { for (int i = 0; i < timelineCount; i++) {
case Subsequent: Timeline timeline = timelinesItems[i];
if (!attachments && timeline is AttachmentTimeline) continue; MixBlend timelineBlend;
if (!drawOrder && timeline is DrawOrderTimeline) continue; float alpha;
pose = currentPose; switch (timelineData[i]) {
alpha = alphaMix; case AnimationState.Subsequent:
break; if (!attachments && timeline is AttachmentTimeline)
case First: continue;
pose = MixPose.Setup; if (!drawOrder && timeline is DrawOrderTimeline)
alpha = alphaMix; continue;
break; timelineBlend = blend;
case Dip: alpha = alphaMix;
pose = MixPose.Setup; break;
alpha = alphaDip; case AnimationState.First:
break; timelineBlend = MixBlend.Setup;
default: alpha = alphaMix;
pose = MixPose.Setup; break;
TrackEntry dipMix = timelineDipMix[i]; case AnimationState.Dip:
alpha = alphaDip * Math.Max(0, 1 - dipMix.mixTime / dipMix.mixDuration); timelineBlend = MixBlend.Setup;
break; alpha = alphaDip;
} break;
from.totalAlpha += alpha; default:
var rotateTimeline = timeline as RotateTimeline; timelineBlend = MixBlend.Setup;
if (rotateTimeline != null) { TrackEntry dipMix = timelineDipMix[i];
ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, alpha, pose, timelinesRotation, i << 1, firstFrame); alpha = alphaDip * Math.Max(0, 1 - dipMix.mixTime / dipMix.mixDuration);
} else { break;
timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, alpha, pose, MixDirection.Out); }
from.totalAlpha += alpha;
var rotateTimeline = timeline as RotateTimeline;
if (rotateTimeline != null) {
ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, alpha, timelineBlend, timelinesRotation, i << 1, firstFrame);
} else {
timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, alpha, timelineBlend, MixDirection.Out);
}
} }
} }
@ -284,7 +296,7 @@ namespace Spine {
return mix; return mix;
} }
static private void ApplyRotateTimeline (RotateTimeline rotateTimeline, Skeleton skeleton, float time, float alpha, MixPose pose, static private void ApplyRotateTimeline (RotateTimeline rotateTimeline, Skeleton skeleton, float time, float alpha, MixBlend pose,
float[] timelinesRotation, int i, bool firstFrame) { float[] timelinesRotation, int i, bool firstFrame) {
if (firstFrame) timelinesRotation[i] = 0; if (firstFrame) timelinesRotation[i] = 0;
@ -297,7 +309,7 @@ namespace Spine {
Bone bone = skeleton.bones.Items[rotateTimeline.boneIndex]; Bone bone = skeleton.bones.Items[rotateTimeline.boneIndex];
float[] frames = rotateTimeline.frames; float[] frames = rotateTimeline.frames;
if (time < frames[0]) { if (time < frames[0]) {
if (pose == MixPose.Setup) bone.rotation = bone.data.rotation; if (pose == MixBlend.Setup) bone.rotation = bone.data.rotation;
return; return;
} }
@ -319,7 +331,7 @@ namespace Spine {
} }
// Mix between rotations using the direction of the shortest route on the first frame while detecting crosses. // Mix between rotations using the direction of the shortest route on the first frame while detecting crosses.
float r1 = pose == MixPose.Setup ? bone.data.rotation : bone.rotation; float r1 = pose == MixBlend.Setup ? bone.data.rotation : bone.rotation;
float total, diff = r2 - r1; float total, diff = r2 - r1;
if (diff == 0) { if (diff == 0) {
total = timelinesRotation[i]; total = timelinesRotation[i];
@ -490,8 +502,9 @@ namespace Spine {
/// <summary>Adds an animation to be played delay seconds after the current or last queued animation /// <summary>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 <see cref="SetAnimation"/>.</summary> /// for a track. If the track is empty, it is equivalent to calling <see cref="SetAnimation"/>.</summary>
/// <param name="delay"> /// <param name="delay">
/// Seconds to begin this animation after the start of the previous animation. May be &lt;= 0 to use the animation /// delay Seconds to begin this animation after the start of the previous animation. If &lt;= 0, uses the duration of the
/// duration of the previous track minus any mix duration plus the negative delay. /// previous track entry minus any mix duration plus the specified<code>delay</code>.If the previous entry is
/// looping, its next loop completion is used instead of the duration.
/// </param> /// </param>
/// <returns>A track entry to allow further customization of animation playback. References to the track entry must not be kept /// <returns>A track entry to allow further customization of animation playback. References to the track entry must not be kept
/// after <see cref="AnimationState.Dispose"/></returns> /// after <see cref="AnimationState.Dispose"/></returns>
@ -627,7 +640,7 @@ namespace Spine {
var tracksItems = tracks.Items; var tracksItems = tracks.Items;
for (int i = 0, n = tracks.Count; i < n; i++) { for (int i = 0, n = tracks.Count; i < n; i++) {
var entry = tracksItems[i]; var entry = tracksItems[i];
if (entry != null) entry.SetTimelineData(null, mixingTo, propertyIDs); if (entry != null && (i == 0 || entry.mixBlend != MixBlend.Add)) entry.SetTimelineData(null, mixingTo, propertyIDs);
} }
} }
@ -667,6 +680,7 @@ namespace Spine {
internal float animationStart, animationEnd, animationLast, nextAnimationLast; internal float animationStart, animationEnd, animationLast, nextAnimationLast;
internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f; internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f;
internal float alpha, mixTime, mixDuration, interruptAlpha, totalAlpha; internal float alpha, mixTime, mixDuration, interruptAlpha, totalAlpha;
internal MixBlend mixBlend = MixBlend.Replace;
internal readonly ExposedList<int> timelineData = new ExposedList<int>(); internal readonly ExposedList<int> timelineData = new ExposedList<int>();
internal readonly ExposedList<TrackEntry> timelineDipMix = new ExposedList<TrackEntry>(); internal readonly ExposedList<TrackEntry> timelineDipMix = new ExposedList<TrackEntry>();
internal readonly ExposedList<float> timelinesRotation = new ExposedList<float>(); internal readonly ExposedList<float> timelinesRotation = new ExposedList<float>();
@ -870,6 +884,17 @@ namespace Spine {
/// </summary> /// </summary>
public float MixDuration { get { return mixDuration; } set { mixDuration = value; } } public float MixDuration { get { return mixDuration; } set { mixDuration = value; } }
/// <summary>
/// Controls how properties keyed in the animation are mixed with lower tracks. Defaults to {@link MixBlend#replace}, which
/// replaces the values from the lower tracks with the animation values. {@link MixBlend#add} adds the animation values to
/// the values from the lower tracks.
/// <para>
/// The<code> mixBlend</code> can be set for a new track entry only before
/// <code>AnimationState.Apply(Skeleton)</code> is first called.
/// </para>
/// </summary>
public MixBlend MixBlend { get { return mixBlend; } set { mixBlend = value; } }
/// <summary> /// <summary>
/// The track entry for the previous animation when mixing from the previous animation to this animation, or null if no /// The track entry for the previous animation when mixing from the previous animation to this animation, or null if no
/// mixing is currently occuring. When mixing from multiple animations, MixingFrom makes up a linked list.</summary> /// mixing is currently occuring. When mixing from multiple animations, MixingFrom makes up a linked list.</summary>
@ -885,13 +910,17 @@ namespace Spine {
internal void OnEvent (Event e) { if (Event != null) Event(this, e); } internal void OnEvent (Event e) { if (Event != null) Event(this, e); }
/// <summary> /// <summary>
/// <para>
/// Resets the rotation directions for mixing this entry's rotate timelines. This can be useful to avoid bones rotating the /// 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 <see cref="alpha"/> and starting animations on other tracks. /// long way around when using <see cref="alpha"/> and starting animations on other tracks.
/// /// </para>
/// Mixing involves finding a rotation between two others, which has two possible solutions: the short way or the long way around. /// <para>
/// The two rotations likely change over time, so which direction is the short or long way also changes. /// Mixing with <see cref="MixBlend.Replace"/> involves finding a rotation between two others, which has two possible solutions:
/// If the short way was always chosen, bones would flip to the other side when that direction became the long way. /// the short way or the long way around.The two rotations likely change over time, so which direction is the short or long
/// TrackEntry chooses the short way the first time it is applied and remembers that direction.</summary> /// 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.
/// </para>
/// </summary>
public void ResetRotationDirections () { public void ResetRotationDirections () {
timelinesRotation.Clear(); timelinesRotation.Clear();
} }