|
@@ -98,6 +98,22 @@ namespace Spine {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /// <param name="target">After the first and before the last entry.</param>
|
|
|
+ internal static int binarySearch (float[] values, float target) {
|
|
|
+ int low = 0;
|
|
|
+ int high = values.Length - 2;
|
|
|
+ if (high == 0) return 1;
|
|
|
+ int current = (int)((uint)high >> 1);
|
|
|
+ while (true) {
|
|
|
+ if (values[(current + 1)] <= target)
|
|
|
+ low = current + 1;
|
|
|
+ else
|
|
|
+ high = current;
|
|
|
+ if (low == high) return (low + 1);
|
|
|
+ current = (int)((uint)(low + high) >> 1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
internal static int linearSearch (float[] values, float target, int step) {
|
|
|
for (int i = 0, last = values.Length - step; i <= last; i += step)
|
|
|
if (values[i] > target) return i;
|
|
@@ -107,78 +123,51 @@ namespace Spine {
|
|
|
|
|
|
public interface Timeline {
|
|
|
/// <summary>Sets the value(s) for the specified time.</summary>
|
|
|
- void Apply (Skeleton skeleton, float lastTime, float time, List<Event> firedEvents, float alpha);
|
|
|
+ /// <param name="events">May be null to not collect fired events.</param>
|
|
|
+ void Apply (Skeleton skeleton, float lastTime, float time, List<Event> events, float alpha);
|
|
|
}
|
|
|
|
|
|
/// <summary>Base class for frames that use an interpolation bezier curve.</summary>
|
|
|
abstract public class CurveTimeline : Timeline {
|
|
|
- static protected float LINEAR = 0;
|
|
|
- static protected float STEPPED = -1;
|
|
|
- static protected int BEZIER_SEGMENTS = 10;
|
|
|
+ protected const float LINEAR = 0, STEPPED = 1, BEZIER = 2;
|
|
|
+ protected const int BEZIER_SEGMENTS = 10, BEZIER_SIZE = BEZIER_SEGMENTS * 2 - 1;
|
|
|
|
|
|
- private float[] curves; // dfx, dfy, ddfx, ddfy, dddfx, dddfy, ...
|
|
|
- public int FrameCount { get { return curves.Length / 6 + 1; } }
|
|
|
+ private float[] curves; // type, x, y, ...
|
|
|
+ public int FrameCount { get { return curves.Length / BEZIER_SIZE + 1; } }
|
|
|
|
|
|
public CurveTimeline (int frameCount) {
|
|
|
- curves = new float[(frameCount - 1) * 6];
|
|
|
+ curves = new float[(frameCount - 1) * BEZIER_SIZE];
|
|
|
}
|
|
|
|
|
|
abstract public void Apply (Skeleton skeleton, float lastTime, float time, List<Event> firedEvents, float alpha);
|
|
|
|
|
|
public void SetLinear (int frameIndex) {
|
|
|
- curves[frameIndex * 6] = LINEAR;
|
|
|
+ curves[frameIndex * BEZIER_SIZE] = LINEAR;
|
|
|
}
|
|
|
|
|
|
public void SetStepped (int frameIndex) {
|
|
|
- curves[frameIndex * 6] = STEPPED;
|
|
|
+ curves[frameIndex * BEZIER_SIZE] = STEPPED;
|
|
|
}
|
|
|
|
|
|
/// <summary>Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next.
|
|
|
/// cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of
|
|
|
/// the difference between the keyframe's values.</summary>
|
|
|
public void SetCurve (int frameIndex, float cx1, float cy1, float cx2, float cy2) {
|
|
|
- float subdiv_step = 1f / BEZIER_SEGMENTS;
|
|
|
- float subdiv_step2 = subdiv_step * subdiv_step;
|
|
|
- float subdiv_step3 = subdiv_step2 * subdiv_step;
|
|
|
- float pre1 = 3 * subdiv_step;
|
|
|
- float pre2 = 3 * subdiv_step2;
|
|
|
- float pre4 = 6 * subdiv_step2;
|
|
|
- float pre5 = 6 * subdiv_step3;
|
|
|
- float tmp1x = -cx1 * 2 + cx2;
|
|
|
- float tmp1y = -cy1 * 2 + cy2;
|
|
|
- float tmp2x = (cx1 - cx2) * 3 + 1;
|
|
|
- float tmp2y = (cy1 - cy2) * 3 + 1;
|
|
|
- int i = frameIndex * 6;
|
|
|
+ float subdiv1 = 1f / BEZIER_SEGMENTS, subdiv2 = subdiv1 * subdiv1, subdiv3 = subdiv2 * subdiv1;
|
|
|
+ float pre1 = 3 * subdiv1, pre2 = 3 * subdiv2, pre4 = 6 * subdiv2, pre5 = 6 * subdiv3;
|
|
|
+ float tmp1x = -cx1 * 2 + cx2, tmp1y = -cy1 * 2 + cy2, tmp2x = (cx1 - cx2) * 3 + 1, tmp2y = (cy1 - cy2) * 3 + 1;
|
|
|
+ float dfx = cx1 * pre1 + tmp1x * pre2 + tmp2x * subdiv3, dfy = cy1 * pre1 + tmp1y * pre2 + tmp2y * subdiv3;
|
|
|
+ float ddfx = tmp1x * pre4 + tmp2x * pre5, ddfy = tmp1y * pre4 + tmp2y * pre5;
|
|
|
+ float dddfx = tmp2x * pre5, dddfy = tmp2y * pre5;
|
|
|
+
|
|
|
+ int i = frameIndex * BEZIER_SIZE;
|
|
|
float[] curves = this.curves;
|
|
|
- curves[i] = cx1 * pre1 + tmp1x * pre2 + tmp2x * subdiv_step3;
|
|
|
- curves[i + 1] = cy1 * pre1 + tmp1y * pre2 + tmp2y * subdiv_step3;
|
|
|
- curves[i + 2] = tmp1x * pre4 + tmp2x * pre5;
|
|
|
- curves[i + 3] = tmp1y * pre4 + tmp2y * pre5;
|
|
|
- curves[i + 4] = tmp2x * pre5;
|
|
|
- curves[i + 5] = tmp2y * pre5;
|
|
|
- }
|
|
|
+ curves[i++] = BEZIER;
|
|
|
|
|
|
- public float GetCurvePercent (int frameIndex, float percent) {
|
|
|
- int curveIndex = frameIndex * 6;
|
|
|
- float[] curves = this.curves;
|
|
|
- float dfx = curves[curveIndex];
|
|
|
- if (dfx == LINEAR) return percent;
|
|
|
- if (dfx == STEPPED) return 0;
|
|
|
- float dfy = curves[curveIndex + 1];
|
|
|
- float ddfx = curves[curveIndex + 2];
|
|
|
- float ddfy = curves[curveIndex + 3];
|
|
|
- float dddfx = curves[curveIndex + 4];
|
|
|
- float dddfy = curves[curveIndex + 5];
|
|
|
float x = dfx, y = dfy;
|
|
|
- int i = BEZIER_SEGMENTS - 2;
|
|
|
- while (true) {
|
|
|
- if (x >= percent) {
|
|
|
- float lastX = x - dfx;
|
|
|
- float lastY = y - dfy;
|
|
|
- return lastY + (y - lastY) * (percent - lastX) / (x - lastX);
|
|
|
- }
|
|
|
- if (i == 0) break;
|
|
|
- i--;
|
|
|
+ for (int n = i + BEZIER_SIZE - 1; i < n; i += 2) {
|
|
|
+ curves[i] = x;
|
|
|
+ curves[i + 1] = y;
|
|
|
dfx += ddfx;
|
|
|
dfy += ddfy;
|
|
|
ddfx += dddfx;
|
|
@@ -186,13 +175,38 @@ namespace Spine {
|
|
|
x += dfx;
|
|
|
y += dfy;
|
|
|
}
|
|
|
+ }
|
|
|
+
|
|
|
+ public float GetCurvePercent (int frameIndex, float percent) {
|
|
|
+ float[] curves = this.curves;
|
|
|
+ int i = frameIndex * BEZIER_SIZE;
|
|
|
+ float type = curves[i];
|
|
|
+ if (type == LINEAR) return percent;
|
|
|
+ if (type == STEPPED) return 0;
|
|
|
+ i++;
|
|
|
+ float x = 0;
|
|
|
+ for (int start = i, n = i + BEZIER_SIZE - 1; i < n; i += 2) {
|
|
|
+ x = curves[i];
|
|
|
+ if (x >= percent) {
|
|
|
+ float prevX, prevY;
|
|
|
+ if (i == start) {
|
|
|
+ prevX = 0;
|
|
|
+ prevY = 0;
|
|
|
+ } else {
|
|
|
+ prevX = curves[i - 2];
|
|
|
+ prevY = curves[i - 1];
|
|
|
+ }
|
|
|
+ return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ float y = curves[i - 1];
|
|
|
return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1.
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public class RotateTimeline : CurveTimeline {
|
|
|
- static protected int LAST_FRAME_TIME = -2;
|
|
|
- static protected int FRAME_VALUE = 1;
|
|
|
+ protected const int LAST_FRAME_TIME = -2;
|
|
|
+ protected const int FRAME_VALUE = 1;
|
|
|
|
|
|
internal int boneIndex;
|
|
|
internal float[] frames;
|
|
@@ -235,7 +249,7 @@ namespace Spine {
|
|
|
float lastFrameValue = frames[frameIndex - 1];
|
|
|
float frameTime = frames[frameIndex];
|
|
|
float percent = 1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime);
|
|
|
- percent = GetCurvePercent(frameIndex / 2 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
|
|
|
+ percent = GetCurvePercent((frameIndex >> 1) - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
|
|
|
|
|
|
amount = frames[frameIndex + FRAME_VALUE] - lastFrameValue;
|
|
|
while (amount > 180)
|
|
@@ -252,9 +266,9 @@ namespace Spine {
|
|
|
}
|
|
|
|
|
|
public class TranslateTimeline : CurveTimeline {
|
|
|
- static protected int LAST_FRAME_TIME = -3;
|
|
|
- static protected int FRAME_X = 1;
|
|
|
- static protected int FRAME_Y = 2;
|
|
|
+ protected const int LAST_FRAME_TIME = -3;
|
|
|
+ protected const int FRAME_X = 1;
|
|
|
+ protected const int FRAME_Y = 2;
|
|
|
|
|
|
internal int boneIndex;
|
|
|
internal float[] frames;
|
|
@@ -330,11 +344,11 @@ namespace Spine {
|
|
|
}
|
|
|
|
|
|
public class ColorTimeline : CurveTimeline {
|
|
|
- static protected int LAST_FRAME_TIME = -5;
|
|
|
- static protected int FRAME_R = 1;
|
|
|
- static protected int FRAME_G = 2;
|
|
|
- static protected int FRAME_B = 3;
|
|
|
- static protected int FRAME_A = 4;
|
|
|
+ protected const int LAST_FRAME_TIME = -5;
|
|
|
+ protected const int FRAME_R = 1;
|
|
|
+ protected const int FRAME_G = 2;
|
|
|
+ protected const int FRAME_B = 3;
|
|
|
+ protected const int FRAME_A = 4;
|
|
|
|
|
|
internal int slotIndex;
|
|
|
internal float[] frames;
|
|
@@ -423,13 +437,14 @@ namespace Spine {
|
|
|
|
|
|
public void Apply (Skeleton skeleton, float lastTime, float time, List<Event> firedEvents, float alpha) {
|
|
|
float[] frames = this.frames;
|
|
|
- if (time < frames[0]) return; // Time is before first frame.
|
|
|
+ if (time < frames[0]) {
|
|
|
+ if (lastTime > time) Apply(skeleton, lastTime, int.MaxValue, null, 0);
|
|
|
+ return;
|
|
|
+ } else if (lastTime > time) //
|
|
|
+ lastTime = -1;
|
|
|
|
|
|
- int frameIndex;
|
|
|
- if (time >= frames[frames.Length - 1]) // Time is after last frame.
|
|
|
- frameIndex = frames.Length - 1;
|
|
|
- else
|
|
|
- frameIndex = Animation.binarySearch(frames, time, 1) - 1;
|
|
|
+ int frameIndex = time >= frames[frames.Length - 1] ? frames.Length - 1 : Animation.binarySearch(frames, time) - 1;
|
|
|
+ if (frames[frameIndex] <= lastTime) return;
|
|
|
|
|
|
String attachmentName = attachmentNames[frameIndex];
|
|
|
skeleton.slots[slotIndex].Attachment =
|
|
@@ -473,7 +488,7 @@ namespace Spine {
|
|
|
if (lastTime < frames[0])
|
|
|
frameIndex = 0;
|
|
|
else {
|
|
|
- frameIndex = Animation.binarySearch(frames, lastTime, 1);
|
|
|
+ frameIndex = Animation.binarySearch(frames, lastTime);
|
|
|
float frame = frames[frameIndex];
|
|
|
while (frameIndex > 0) { // Fire multiple events with the same frame.
|
|
|
if (frames[frameIndex - 1] != frame) break;
|
|
@@ -513,7 +528,7 @@ namespace Spine {
|
|
|
if (time >= frames[frames.Length - 1]) // Time is after last frame.
|
|
|
frameIndex = frames.Length - 1;
|
|
|
else
|
|
|
- frameIndex = Animation.binarySearch(frames, time, 1) - 1;
|
|
|
+ frameIndex = Animation.binarySearch(frames, time) - 1;
|
|
|
|
|
|
List<Slot> drawOrder = skeleton.drawOrder;
|
|
|
List<Slot> slots = skeleton.slots;
|
|
@@ -583,7 +598,7 @@ namespace Spine {
|
|
|
}
|
|
|
|
|
|
// Interpolate between the previous frame and the current frame.
|
|
|
- int frameIndex = Animation.binarySearch(frames, time, 1);
|
|
|
+ int frameIndex = Animation.binarySearch(frames, time);
|
|
|
float frameTime = frames[frameIndex];
|
|
|
float percent = 1 - (time - frameTime) / (frames[frameIndex - 1] - frameTime);
|
|
|
percent = GetCurvePercent(frameIndex - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
|
|
@@ -604,4 +619,57 @@ namespace Spine {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ public class IkConstraintTimeline : CurveTimeline {
|
|
|
+ private const int PREV_FRAME_TIME = -3;
|
|
|
+ private const int FRAME_MIX = 1;
|
|
|
+ private const int FRAME_BEND_DIRECTION = 2;
|
|
|
+
|
|
|
+ internal int ikConstraintIndex;
|
|
|
+ internal float[] frames;
|
|
|
+
|
|
|
+ public int IkConstraintIndex { get { return ikConstraintIndex; } set { ikConstraintIndex = value; } }
|
|
|
+ public float[] Frames { get { return frames; } set { frames = value; } } // time, mix, bendDirection, ...
|
|
|
+
|
|
|
+ public IkConstraintTimeline (int frameCount)
|
|
|
+ : base(frameCount) {
|
|
|
+ frames = new float[frameCount * 3];
|
|
|
+ }
|
|
|
+
|
|
|
+ public float[] getFrames () {
|
|
|
+ return frames;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** Sets the time, mix and bend direction of the specified keyframe. */
|
|
|
+ public void setFrame (int frameIndex, float time, float mix, int bendDirection) {
|
|
|
+ frameIndex *= 3;
|
|
|
+ frames[frameIndex] = time;
|
|
|
+ frames[frameIndex + 1] = mix;
|
|
|
+ frames[frameIndex + 2] = bendDirection;
|
|
|
+ }
|
|
|
+
|
|
|
+ override public void Apply (Skeleton skeleton, float lastTime, float time, List<Event> firedEvents, float alpha) {
|
|
|
+ float[] frames = this.frames;
|
|
|
+ if (time < frames[0]) return; // Time is before first frame.
|
|
|
+
|
|
|
+ IkConstraint ikConstraint = skeleton.ikConstraints[ikConstraintIndex];
|
|
|
+
|
|
|
+ if (time >= frames[frames.Length - 3]) { // Time is after last frame.
|
|
|
+ ikConstraint.mix += (frames[frames.Length - 2] - ikConstraint.mix) * alpha;
|
|
|
+ ikConstraint.bendDirection = (int)frames[frames.Length - 1];
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Interpolate between the previous frame and the current frame.
|
|
|
+ int frameIndex = Animation.binarySearch(frames, time, 3);
|
|
|
+ float prevFrameMix = frames[frameIndex - 2];
|
|
|
+ float frameTime = frames[frameIndex];
|
|
|
+ float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime);
|
|
|
+ percent = GetCurvePercent(frameIndex / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
|
|
|
+
|
|
|
+ float mix = prevFrameMix + (frames[frameIndex + FRAME_MIX] - prevFrameMix) * percent;
|
|
|
+ ikConstraint.mix += (mix - ikConstraint.mix) * alpha;
|
|
|
+ ikConstraint.bendDirection = (int)frames[frameIndex + FRAME_BEND_DIRECTION];
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|