From b4c8deb2830f13bd696b8cff770bf633d4377062 Mon Sep 17 00:00:00 2001 From: pharan Date: Fri, 19 Jan 2018 05:34:20 +0800 Subject: [PATCH] [csharp] Animation and AnimationState 3.7 --- spine-csharp/src/Animation.cs | 481 +++++++++++++++++++---------- spine-csharp/src/AnimationState.cs | 151 +++++---- 2 files changed, 409 insertions(+), 223 deletions(-) diff --git a/spine-csharp/src/Animation.cs b/spine-csharp/src/Animation.cs index 7bd5e0c5a..4af79b265 100644 --- a/spine-csharp/src/Animation.cs +++ b/spine-csharp/src/Animation.cs @@ -50,8 +50,8 @@ namespace Spine { } /// Applies all the animation's timelines to the specified skeleton. - /// - public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events, float alpha, MixPose pose, MixDirection direction) { + /// + public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList 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. alpha can also be useful to /// apply animations on top of each other (layered). - /// Controls how mixing is applied when alpha is than 1. + /// Controls how mixing is applied when alpha is than 1. /// Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions such as DrawOrderTimeline and AttachmentTimeline. - void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixPose pose, MixDirection direction); + void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, MixDirection direction); int PropertyId { get; } } /// /// Controls how a timeline is mixed with the setup or current pose. - /// - public enum MixPose { - /// The timeline value is mixed with the setup pose (the current pose is not used). + /// + public enum MixBlend { + + /// Transitions from the setup value to the timeline value (the current value is not used). Before the first key, the setup value is set.. Setup, - /// 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. - Current, - /// 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). - CurrentLayered + + /// + /// 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 or , use the setup value before the first key. + /// + /// First is intended for the first animations applied, not for animations layered on top of those. + /// + First, + + /// + /// + /// 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). + /// + /// Replace is intended for animations layered on top of others, not for the first animations applied. + /// + Replace, + + /// + /// + /// 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). + /// + /// Add is intended for animations layered on top of others, not for the first animations applied. + /// + Add } /// - /// Indicates whether a timeline's alpha is mixing out over time toward 0 (the setup or current pose) or mixing in toward 1 (the timeline's pose). - /// + /// Indicates whether a timeline's alpha is mixing out over time toward 0 (the setup or current pose value) or mixing in toward 1 (the timeline's value). + /// 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 firedEvents, float alpha, MixPose pose, MixDirection direction); + abstract public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList 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 firedEvents, float alpha, MixPose pose, MixDirection direction) { + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList 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 firedEvents, float alpha, MixPose pose, MixDirection direction) { + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList 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 firedEvents, float alpha, MixPose pose, MixDirection direction) { + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList 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 firedEvents, float alpha, MixPose pose, MixDirection direction) { + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList 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 firedEvents, float alpha, MixPose pose, MixDirection direction) { + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList 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 firedEvents, float alpha, MixPose pose, MixDirection direction) { + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList 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 firedEvents, float alpha, MixPose pose, MixDirection direction) { + public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList 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 firedEvents, float alpha, MixPose pose, MixDirection direction) { + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList 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 { } /// Fires events for frames > lastTime and <= time. - public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { + public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, MixDirection direction) { if (firedEvents == null) return; float[] frames = this.frames; int frameCount = frames.Length; 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 firedEvents, float alpha, MixPose pose, MixDirection direction) { + public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, MixDirection direction) { ExposedList drawOrder = skeleton.drawOrder; ExposedList 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 firedEvents, float alpha, MixPose pose, MixDirection direction) { + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList 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 firedEvents, float alpha, MixPose pose, MixDirection direction) { + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList 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 firedEvents, float alpha, MixPose pose, MixDirection direction) { + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList 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 firedEvents, float alpha, MixPose pose, MixDirection direction) { + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList 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 firedEvents, float alpha, MixPose pose, MixDirection direction) { + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList 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 { diff --git a/spine-csharp/src/AnimationState.cs b/spine-csharp/src/AnimationState.cs index a1432d6d1..2abde4c6d 100644 --- a/spine-csharp/src/AnimationState.cs +++ b/spine-csharp/src/AnimationState.cs @@ -54,13 +54,13 @@ namespace Spine { public ExposedList 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 { /// Adds an animation to be played delay seconds after the current or last queued animation /// for a track. If the track is empty, it is equivalent to calling . /// - /// Seconds to begin this animation after the start of the previous animation. May be <= 0 to use the animation - /// duration of the previous track minus any mix duration plus the negative delay. + /// delay Seconds to begin this animation after the start of the previous animation. If <= 0, uses the duration of the + /// previous track entry minus any mix duration plus the specifieddelay.If the previous entry is + /// looping, its next loop completion is used instead of the duration. /// /// A track entry to allow further customization of animation playback. References to the track entry must not be kept /// after @@ -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 timelineData = new ExposedList(); internal readonly ExposedList timelineDipMix = new ExposedList(); internal readonly ExposedList timelinesRotation = new ExposedList(); @@ -870,6 +884,17 @@ namespace Spine { /// public float MixDuration { get { return mixDuration; } set { mixDuration = value; } } + /// + /// 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. + /// + /// The mixBlend can be set for a new track entry only before + /// AnimationState.Apply(Skeleton) is first called. + /// + /// + public MixBlend MixBlend { get { return mixBlend; } set { mixBlend = value; } } + /// /// The track entry for the previous animation when mixing from the previous animation to this animation, or null if no /// mixing is currently occuring. When mixing from multiple animations, MixingFrom makes up a linked list. @@ -885,13 +910,17 @@ namespace Spine { internal void OnEvent (Event e) { if (Event != null) Event(this, e); } /// + /// /// Resets the rotation directions for mixing this entry's rotate timelines. This can be useful to avoid bones rotating the /// long way around when using and starting animations on other tracks. - /// - /// Mixing involves finding a rotation between two others, which has two possible solutions: the short way or the long way around. - /// The two rotations likely change over time, so which direction is the short or long way also changes. - /// If the short way was always chosen, bones would flip to the other side when that direction became the long way. - /// TrackEntry chooses the short way the first time it is applied and remembers that direction. + /// + /// + /// Mixing with involves finding a rotation between two others, which has two possible solutions: + /// the short way or the long way around.The two rotations likely change over time, so which direction is the short or long + /// way also changes.If the short way was always chosen, bones would flip to the other side when that direction became the + /// long way. TrackEntry chooses the short way the first time it is applied and remembers that direction. + /// + /// public void ResetRotationDirections () { timelinesRotation.Clear(); }