[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>
/// <seealso cref="Timeline.Apply(Skeleton, float, float, ExposedList, float, MixPose, MixDirection)"/>
public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList<Event> events, float alpha, MixPose pose, MixDirection direction) {
/// <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, MixBlend pose, MixDirection direction) {
if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
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
/// 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>
/// <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>
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; }
}
/// <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)"/>
public enum MixPose {
/// <summary> The timeline value is mixed with the setup pose (the current pose is not used).</summary>
/// <seealso cref="Timeline.Apply(Skeleton, float, float, ExposedList, float, MixBlend, MixDirection)"/>
public enum MixBlend {
/// <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,
/// <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>
Current,
/// <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>
CurrentLayered
/// <summary>
/// <para>Transitions from the current value to the timeline value. Before the first key, transitions from the current value to
/// 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>
/// <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>
/// 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>
/// <seealso cref="Timeline.Apply(Skeleton, float, float, ExposedList, float, MixPose, MixDirection)"/>
/// 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, MixBlend, MixDirection)"/>
public enum MixDirection {
In,
Out
@ -162,7 +182,7 @@ namespace Spine {
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; }
@ -258,31 +278,38 @@ namespace Spine {
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];
float[] frames = this.frames;
if (time < frames[0]) {
switch (pose) {
case MixPose.Setup:
switch (blend) {
case MixBlend.Setup:
bone.rotation = bone.data.rotation;
return;
case MixPose.Current:
float rr = bone.data.rotation - bone.rotation;
rr -= (16384 - (int)(16384.499999999996 - rr / 360)) * 360;
bone.rotation += rr * alpha;
return;
case MixBlend.First:
float r = bone.data.rotation - bone.rotation;
bone.rotation += (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * alpha;
return;
}
return;
}
if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
if (pose == MixPose.Setup) {
bone.rotation = bone.data.rotation + frames[frames.Length + PREV_ROTATION] * alpha;
} else {
float rr = bone.data.rotation + frames[frames.Length + PREV_ROTATION] - bone.rotation;
rr -= (16384 - (int)(16384.499999999996 - rr / 360)) * 360; // Wrap within -180 and 180.
bone.rotation += rr * alpha;
float r = frames[frames.Length + PREV_ROTATION];
switch (blend) {
case MixBlend.Setup:
bone.rotation = bone.data.rotation + r * alpha;
break;
case MixBlend.First:
case MixBlend.Replace:
r += bone.data.rotation - bone.rotation;
r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360;
goto case MixBlend.Add; // Fall through.
case MixBlend.Add:
bone.rotation += r * alpha;
break;
}
return;
}
@ -292,18 +319,23 @@ namespace Spine {
float prevRotation = frames[frame + PREV_ROTATION];
float frameTime = frames[frame];
float percent = GetCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
float r = frames[frame + ROTATION] - prevRotation;
r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360;
r = prevRotation + r * percent;
if (pose == MixPose.Setup) {
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;
{
float r = frames[frame + ROTATION] - prevRotation;
r = prevRotation + (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * percent;
switch (blend) {
case MixBlend.Setup:
bone.rotation = bone.data.rotation + (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * alpha;
break;
case MixBlend.First:
case MixBlend.Replace:
r += bone.data.rotation - bone.rotation;
goto case MixBlend.Add; // Fall through.
case MixBlend.Add:
bone.rotation += (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * alpha;
break;
}
}
}
}
@ -335,17 +367,17 @@ namespace Spine {
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];
float[] frames = this.frames;
if (time < frames[0]) {
switch (pose) {
case MixPose.Setup:
switch (blend) {
case MixBlend.Setup:
bone.x = bone.data.x;
bone.y = bone.data.y;
return;
case MixPose.Current:
case MixBlend.First:
bone.x += (bone.data.x - bone.x) * alpha;
bone.y += (bone.data.y - bone.y) * alpha;
return;
@ -369,12 +401,20 @@ namespace Spine {
x += (frames[frame + X] - x) * percent;
y += (frames[frame + Y] - y) * percent;
}
if (pose == MixPose.Setup) {
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;
switch (blend) {
case MixBlend.Setup:
bone.x = bone.data.x + x * alpha;
bone.y = bone.data.y + y * alpha;
break;
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) {
}
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];
float[] frames = this.frames;
if (time < frames[0]) {
switch (pose) {
case MixPose.Setup:
switch (blend) {
case MixBlend.Setup:
bone.scaleX = bone.data.scaleX;
bone.scaleY = bone.data.scaleY;
return;
case MixPose.Current:
case MixBlend.First:
bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha;
bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha;
return;
@ -423,27 +463,61 @@ namespace Spine {
y = (y + (frames[frame + Y] - y) * percent) * bone.data.scaleY;
}
if (alpha == 1) {
bone.scaleX = x;
bone.scaleY = y;
if (blend == MixBlend.Add) {
bone.scaleX += x - bone.data.scaleX;
bone.scaleY += y - bone.data.scaleY;
} else {
bone.scaleX = x;
bone.scaleY = y;
}
} 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.
float bx, by;
if (direction == MixDirection.Out) {
x = (x >= 0 ? x : -x) * (bx >= 0 ? 1 : -1);
y = (y >= 0 ? y : -y) * (by >= 0 ? 1 : -1);
switch (blend) {
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 {
bx = (bx >= 0 ? bx : -bx) * (x >= 0 ? 1 : -1);
by = (by >= 0 ? by : -by) * (y >= 0 ? 1 : -1);
switch (blend) {
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) {
}
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];
float[] frames = this.frames;
if (time < frames[0]) {
switch (pose) {
case MixPose.Setup:
switch (blend) {
case MixBlend.Setup:
bone.shearX = bone.data.shearX;
bone.shearY = bone.data.shearY;
return;
case MixPose.Current:
case MixBlend.First:
bone.shearX += (bone.data.shearX - bone.shearX) * alpha;
bone.shearY += (bone.data.shearY - bone.shearY) * alpha;
return;
@ -490,12 +564,20 @@ namespace Spine {
x = x + (frames[frame + X] - x) * percent;
y = y + (frames[frame + Y] - y) * percent;
}
if (pose == MixPose.Setup) {
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;
switch (blend) {
case MixBlend.Setup:
bone.shearX = bone.data.shearX + x * alpha;
bone.shearY = bone.data.shearY + y * alpha;
break;
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;
}
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];
float[] frames = this.frames;
if (time < frames[0]) {
var slotData = slot.data;
switch (pose) {
case MixPose.Setup:
switch (blend) {
case MixBlend.Setup:
slot.r = slotData.r;
slot.g = slotData.g;
slot.b = slotData.b;
slot.a = slotData.a;
return;
case MixPose.Current:
case MixBlend.First:
slot.r += (slot.r - slotData.r) * alpha;
slot.g += (slot.g - slotData.g) * alpha;
slot.b += (slot.b - slotData.b) * alpha;
@ -582,7 +664,7 @@ namespace Spine {
slot.a = a;
} else {
float br, bg, bb, ba;
if (pose == MixPose.Setup) {
if (blend == MixBlend.Setup) {
br = slot.data.r;
bg = slot.data.g;
bb = slot.data.b;
@ -641,13 +723,13 @@ namespace Spine {
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];
float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame.
var slotData = slot.data;
switch (pose) {
case MixPose.Setup:
switch (blend) {
case MixBlend.Setup:
// slot.color.set(slot.data.color);
// slot.darkColor.set(slot.data.darkColor);
slot.r = slotData.r;
@ -658,7 +740,7 @@ namespace Spine {
slot.g2 = slotData.g2;
slot.b2 = slotData.b2;
return;
case MixPose.Current:
case MixBlend.First:
slot.r += (slot.r - slotData.r) * alpha;
slot.g += (slot.g - slotData.g) * alpha;
slot.b += (slot.b - slotData.b) * alpha;
@ -713,7 +795,7 @@ namespace Spine {
slot.b2 = b2;
} else {
float br, bg, bb, ba, br2, bg2, bb2;
if (pose == MixPose.Setup) {
if (blend == MixBlend.Setup) {
br = slot.data.r;
bg = slot.data.g;
bb = slot.data.b;
@ -767,10 +849,10 @@ namespace Spine {
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;
Slot slot = skeleton.slots.Items[slotIndex];
if (direction == MixDirection.Out && pose == MixPose.Setup) {
if (direction == MixDirection.Out && blend == MixBlend.Setup) {
attachmentName = slot.data.attachmentName;
slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName);
return;
@ -778,7 +860,7 @@ namespace Spine {
float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame.
if (pose == MixPose.Setup) {
if (blend == MixBlend.Setup || blend == MixBlend.First) {
attachmentName = slot.data.attachmentName;
slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName);
}
@ -823,13 +905,13 @@ namespace Spine {
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];
VertexAttachment vertexAttachment = slot.attachment as VertexAttachment;
if (vertexAttachment == null || !vertexAttachment.ApplyDeform(attachment)) return;
var verticesArray = slot.attachmentVertices;
if (verticesArray.Count == 0) alpha = 1;
if (verticesArray.Count == 0) blend = MixBlend.Setup;
float[][] frameVertices = this.frameVertices;
int vertexCount = frameVertices[0].Length;
@ -838,11 +920,11 @@ namespace Spine {
if (time < frames[0]) {
switch (pose) {
case MixPose.Setup:
switch (blend) {
case MixBlend.Setup:
verticesArray.Clear();
return;
case MixPose.Current:
case MixBlend.Replace:
if (alpha == 1) {
verticesArray.Clear();
return;
@ -877,27 +959,60 @@ namespace Spine {
vertices = verticesArray.Items;
if (time >= frames[frames.Length - 1]) { // Time is after last frame.
float[] lastVertices = frameVertices[frames.Length - 1];
if (alpha == 1) {
// Vertex positions or deform offsets, no alpha.
Array.Copy(lastVertices, 0, vertices, 0, vertexCount);
} else if (pose == MixPose.Setup) {
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;
if (blend == 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];
} else {
// Weighted deform offsets, no alpha.
for (int i = 0; i < vertexCount; i++)
vertices[i] += lastVertices[i];
}
} else {
// Weighted deform offsets, with alpha.
for (int i = 0; i < vertexCount; i++)
vertices[i] = lastVertices[i] * alpha;
// Vertex positions or deform offsets, no alpha.
Array.Copy(lastVertices, 0, vertices, 0, vertexCount);
}
} else {
// Vertex positions or deform offsets, with alpha.
for (int i = 0; i < vertexCount; i++)
vertices[i] += (lastVertices[i] - vertices[i]) * alpha;
switch (blend) {
case MixBlend.Setup: {
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;
}
@ -910,31 +1025,73 @@ namespace Spine {
float percent = GetCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime));
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 (pose == MixPose.Setup) {
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;
if (blend == MixBlend.Add) {
if (vertexAttachment.bones == null) {
// Unweighted vertex positions, no 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];
}
} else {
// Weighted deform offsets, no alpha.
for (int i = 0; i < vertexCount; i++) {
float prev = prevVertices[i];
vertices[i] += prev + (nextVertices[i] - prev) * percent;
}
}
} else {
// Weighted deform offsets, with alpha.
// 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) * alpha;
vertices[i] = prev + (nextVertices[i] - prev) * percent;
}
}
} 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;
switch (blend) {
case MixBlend.Setup: {
if (vertexAttachment.bones == null) {
// 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>
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;
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, pose, direction);
Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha, blend, direction);
lastTime = -1f;
} else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame.
return;
@ -1016,17 +1173,17 @@ namespace Spine {
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> 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);
return;
}
float[] frames = this.frames;
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;
}
@ -1078,16 +1235,16 @@ namespace Spine {
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];
float[] frames = this.frames;
if (time < frames[0]) {
switch (pose) {
case MixPose.Setup:
switch (blend) {
case MixBlend.Setup:
constraint.mix = constraint.data.mix;
constraint.bendDirection = constraint.data.bendDirection;
return;
case MixPose.Current:
case MixBlend.First:
constraint.mix += (constraint.data.mix - constraint.mix) * alpha;
constraint.bendDirection = constraint.data.bendDirection;
return;
@ -1096,7 +1253,7 @@ namespace Spine {
}
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.bendDirection = direction == MixDirection.Out ? constraint.data.bendDirection
: (int)frames[frames.Length + PREV_BEND_DIRECTION];
@ -1113,7 +1270,7 @@ namespace Spine {
float frameTime = frames[frame];
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.bendDirection = direction == MixDirection.Out ? constraint.data.bendDirection : (int)frames[frame + PREV_BEND_DIRECTION];
} else {
@ -1152,19 +1309,19 @@ namespace Spine {
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];
float[] frames = this.frames;
if (time < frames[0]) {
var data = constraint.data;
switch (pose) {
case MixPose.Setup:
switch (blend) {
case MixBlend.Setup:
constraint.rotateMix = data.rotateMix;
constraint.translateMix = data.translateMix;
constraint.scaleMix = data.scaleMix;
constraint.shearMix = data.shearMix;
return;
case MixPose.Current:
case MixBlend.First:
constraint.rotateMix += (data.rotateMix - constraint.rotateMix) * alpha;
constraint.translateMix += (data.translateMix - constraint.translateMix) * alpha;
constraint.scaleMix += (data.scaleMix - constraint.scaleMix) * alpha;
@ -1197,7 +1354,7 @@ namespace Spine {
scale += (frames[frame + SCALE] - scale) * percent;
shear += (frames[frame + SHEAR] - shear) * percent;
}
if (pose == MixPose.Setup) {
if (blend == MixBlend.Setup) {
TransformConstraintData data = constraint.data;
constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha;
constraint.translateMix = data.translateMix + (translate - data.translateMix) * alpha;
@ -1239,15 +1396,15 @@ namespace Spine {
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];
float[] frames = this.frames;
if (time < frames[0]) {
switch (pose) {
case MixPose.Setup:
switch (blend) {
case MixBlend.Setup:
constraint.position = constraint.data.position;
return;
case MixPose.Current:
case MixBlend.First:
constraint.position += (constraint.data.position - constraint.position) * alpha;
return;
}
@ -1267,7 +1424,7 @@ namespace Spine {
position += (frames[frame + VALUE] - position) * percent;
}
if (pose == MixPose.Setup)
if (blend == MixBlend.Setup)
constraint.position = constraint.data.position + (position - constraint.data.position) * alpha;
else
constraint.position += (position - constraint.position) * alpha;
@ -1283,15 +1440,15 @@ namespace Spine {
: 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];
float[] frames = this.frames;
if (time < frames[0]) {
switch (pose) {
case MixPose.Setup:
switch (blend) {
case MixBlend.Setup:
constraint.spacing = constraint.data.spacing;
return;
case MixPose.Current:
case MixBlend.First:
constraint.spacing += (constraint.data.spacing - constraint.spacing) * alpha;
return;
}
@ -1312,7 +1469,7 @@ namespace Spine {
spacing += (frames[frame + VALUE] - spacing) * percent;
}
if (pose == MixPose.Setup)
if (blend == MixBlend.Setup)
constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha;
else
constraint.spacing += (spacing - constraint.spacing) * alpha;
@ -1347,16 +1504,16 @@ namespace Spine {
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];
float[] frames = this.frames;
if (time < frames[0]) {
switch (pose) {
case MixPose.Setup:
switch (blend) {
case MixBlend.Setup:
constraint.rotateMix = constraint.data.rotateMix;
constraint.translateMix = constraint.data.translateMix;
return;
case MixPose.Current:
case MixBlend.First:
constraint.rotateMix += (constraint.data.rotateMix - constraint.rotateMix) * alpha;
constraint.translateMix += (constraint.data.translateMix - constraint.translateMix) * alpha;
return;
@ -1381,7 +1538,7 @@ namespace Spine {
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.translateMix = constraint.data.translateMix + (translate - constraint.data.translateMix) * alpha;
} else {

View File

@ -54,13 +54,13 @@ namespace Spine {
public ExposedList<TrackEntry> Tracks { get { return tracks; } }
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 delegate void TrackEntryEventDelegate (TrackEntry trackEntry, Event e);
public delegate void TrackEntryEventDelegate(TrackEntry trackEntry, Event e);
public event TrackEntryEventDelegate Event;
public AnimationState (AnimationStateData data) {
public AnimationState(AnimationStateData data) {
if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
this.data = data;
this.queue = new EventQueue(
@ -171,12 +171,13 @@ namespace Spine {
TrackEntry current = tracksItems[i];
if (current == null || current.delay > 0) continue;
applied = true;
MixPose currentPose = i == 0 ? MixPose.Current : MixPose.CurrentLayered;
MixBlend blend = i == 0 ? MixBlend.First : current.mixBlend;
// Apply mixing from entries first.
float mix = current.alpha;
if (current.mixingFrom != null)
mix *= ApplyMixingFrom(current, skeleton, currentPose);
mix *= ApplyMixingFrom(current, skeleton, blend);
else if (current.trackTime >= current.trackEnd && current.next == null) //
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;
var timelines = current.animation.timelines;
var timelinesItems = timelines.Items;
if (mix == 1) {
if (mix == 1 || blend == MixBlend.Add) {
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 {
var timelineData = current.timelineData.Items;
@ -197,12 +198,12 @@ namespace Spine {
for (int ii = 0; ii < timelineCount; 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;
if (rotateTimeline != null)
ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, mix, pose, timelinesRotation, ii << 1, firstFrame);
ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation, ii << 1, firstFrame);
else
timeline.Apply(skeleton, animationLast, animationTime, events, mix, pose, MixDirection.In);
timeline.Apply(skeleton, animationLast, animationTime, events, mix, timelineBlend, MixDirection.In);
}
}
QueueEvents(current, animationTime);
@ -215,17 +216,18 @@ namespace Spine {
return applied;
}
private float ApplyMixingFrom (TrackEntry to, Skeleton skeleton, MixPose currentPose) {
private float ApplyMixingFrom (TrackEntry to, Skeleton skeleton, MixBlend blend) {
TrackEntry from = to.mixingFrom;
if (from.mixingFrom != null) ApplyMixingFrom(from, skeleton, currentPose);
if (from.mixingFrom != null) ApplyMixingFrom(from, skeleton, blend);
float mix;
if (to.mixDuration == 0) { // Single frame mix to undo mixingFrom changes.
mix = 1;
currentPose = MixPose.Setup;
if (blend == MixBlend.First) blend = MixBlend.Setup; // Tracks > 0 are transparent and can't reset to setup pose.
} else {
mix = to.mixTime / to.mixDuration;
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;
@ -234,45 +236,55 @@ namespace Spine {
var timelines = from.animation.timelines;
int timelineCount = timelines.Count;
var timelinesItems = timelines.Items;
var timelineData = from.timelineData.Items;
var timelineDipMix = from.timelineDipMix.Items;
float alphaDip = from.alpha * to.interruptAlpha, alphaMix = alphaDip * (1 - mix);
bool firstFrame = from.timelinesRotation.Count == 0;
if (firstFrame) from.timelinesRotation.Resize(timelines.Count << 1); // from.timelinesRotation.setSize
var timelinesRotation = from.timelinesRotation.Items;
if (blend == MixBlend.Add) {
for (int i = 0; i < timelineCount; i++)
(timelinesItems[i]).Apply(skeleton, animationLast, animationTime, eventBuffer, alphaMix, blend, MixDirection.Out);
} else {
var timelineData = from.timelineData.Items;
var timelineDipMix = from.timelineDipMix.Items;
MixPose pose;
float alphaDip = from.alpha * to.interruptAlpha, alphaMix = alphaDip * (1 - mix), alpha;
from.totalAlpha = 0;
for (int i = 0; i < timelineCount; i++) {
Timeline timeline = timelinesItems[i];
switch (timelineData[i]) {
case Subsequent:
if (!attachments && timeline is AttachmentTimeline) continue;
if (!drawOrder && timeline is DrawOrderTimeline) continue;
pose = currentPose;
alpha = alphaMix;
break;
case First:
pose = MixPose.Setup;
alpha = alphaMix;
break;
case Dip:
pose = MixPose.Setup;
alpha = alphaDip;
break;
default:
pose = MixPose.Setup;
TrackEntry dipMix = timelineDipMix[i];
alpha = alphaDip * Math.Max(0, 1 - dipMix.mixTime / dipMix.mixDuration);
break;
}
from.totalAlpha += alpha;
var rotateTimeline = timeline as RotateTimeline;
if (rotateTimeline != null) {
ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, alpha, pose, timelinesRotation, i << 1, firstFrame);
} else {
timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, alpha, pose, MixDirection.Out);
bool firstFrame = from.timelinesRotation.Count == 0;
if (firstFrame) from.timelinesRotation.Resize(timelines.Count << 1); // from.timelinesRotation.setSize
var timelinesRotation = from.timelinesRotation.Items;
from.totalAlpha = 0;
for (int i = 0; i < timelineCount; i++) {
Timeline timeline = timelinesItems[i];
MixBlend timelineBlend;
float alpha;
switch (timelineData[i]) {
case AnimationState.Subsequent:
if (!attachments && timeline is AttachmentTimeline)
continue;
if (!drawOrder && timeline is DrawOrderTimeline)
continue;
timelineBlend = blend;
alpha = alphaMix;
break;
case AnimationState.First:
timelineBlend = MixBlend.Setup;
alpha = alphaMix;
break;
case AnimationState.Dip:
timelineBlend = MixBlend.Setup;
alpha = alphaDip;
break;
default:
timelineBlend = MixBlend.Setup;
TrackEntry dipMix = timelineDipMix[i];
alpha = alphaDip * Math.Max(0, 1 - dipMix.mixTime / dipMix.mixDuration);
break;
}
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;
}
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) {
if (firstFrame) timelinesRotation[i] = 0;
@ -297,7 +309,7 @@ namespace Spine {
Bone bone = skeleton.bones.Items[rotateTimeline.boneIndex];
float[] frames = rotateTimeline.frames;
if (time < frames[0]) {
if (pose == MixPose.Setup) bone.rotation = bone.data.rotation;
if (pose == MixBlend.Setup) bone.rotation = bone.data.rotation;
return;
}
@ -319,7 +331,7 @@ namespace Spine {
}
// 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;
if (diff == 0) {
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
/// for a track. If the track is empty, it is equivalent to calling <see cref="SetAnimation"/>.</summary>
/// <param name="delay">
/// Seconds to begin this animation after the start of the previous animation. May be &lt;= 0 to use the animation
/// duration of the previous track minus any mix duration plus the negative delay.
/// delay Seconds to begin this animation after the start of the previous animation. If &lt;= 0, uses the duration of the
/// 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>
/// <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>
@ -627,7 +640,7 @@ namespace Spine {
var tracksItems = tracks.Items;
for (int i = 0, n = tracks.Count; i < n; 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 delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f;
internal float alpha, mixTime, mixDuration, interruptAlpha, totalAlpha;
internal MixBlend mixBlend = MixBlend.Replace;
internal readonly ExposedList<int> timelineData = new ExposedList<int>();
internal readonly ExposedList<TrackEntry> timelineDipMix = new ExposedList<TrackEntry>();
internal readonly ExposedList<float> timelinesRotation = new ExposedList<float>();
@ -870,6 +884,17 @@ namespace Spine {
/// </summary>
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>
/// 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>
@ -885,13 +910,17 @@ namespace Spine {
internal void OnEvent (Event e) { if (Event != null) Event(this, e); }
/// <summary>
/// <para>
/// 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.
///
/// 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.</summary>
/// </para>
/// <para>
/// Mixing with <see cref="MixBlend.Replace"/> 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.
/// </para>
/// </summary>
public void ResetRotationDirections () {
timelinesRotation.Clear();
}