mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-06 15:24:55 +08:00
Update spine-csharp to v3.2 (shearing) (#562)
* spine-csharp updated to 3.2.00. * Some cleanup, catchup and fixes. * Mix transform constraint scale and shear offsets. * Update readmes. * Clean up. * Formatting. * Fixed single bone IK with nonuniform scale. Improved two bone IK.
This commit is contained in:
parent
05d60e5377
commit
f1406e2f85
@ -10,7 +10,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f
|
||||
|
||||
## Spine version
|
||||
|
||||
spine-csharp works with data exported from Spine 3.1.08. Updating spine-csharp to [v3.2](https://trello.com/c/k7KtGdPW/76-update-runtimes-to-support-v3-2-shearing) is in progress.
|
||||
spine-csharp works with data exported from the latest version of Spine.
|
||||
|
||||
spine-csharp supports all Spine features.
|
||||
|
||||
|
||||
@ -209,14 +209,14 @@ namespace Spine {
|
||||
}
|
||||
|
||||
public class RotateTimeline : CurveTimeline {
|
||||
protected const int PREV_FRAME_TIME = -2;
|
||||
protected const int FRAME_VALUE = 1;
|
||||
internal const int PREV_TIME = -2;
|
||||
internal const int VALUE = 1;
|
||||
|
||||
internal int boneIndex;
|
||||
internal float[] frames;
|
||||
|
||||
public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } }
|
||||
public float[] Frames { get { return frames; } set { frames = value; } } // time, value, ...
|
||||
public float[] Frames { get { return frames; } set { frames = value; } } // time, angle, ...
|
||||
|
||||
public RotateTimeline (int frameCount)
|
||||
: base(frameCount) {
|
||||
@ -249,13 +249,13 @@ namespace Spine {
|
||||
}
|
||||
|
||||
// Interpolate between the previous frame and the current frame.
|
||||
int frameIndex = Animation.binarySearch(frames, time, 2);
|
||||
float prevFrameValue = frames[frameIndex - 1];
|
||||
float frameTime = frames[frameIndex];
|
||||
float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime);
|
||||
percent = GetCurvePercent((frameIndex >> 1) - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
|
||||
int frame = Animation.binarySearch(frames, time, 2);
|
||||
float prevFrameValue = frames[frame - 1];
|
||||
float frameTime = frames[frame];
|
||||
float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime);
|
||||
percent = GetCurvePercent((frame >> 1) - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
|
||||
|
||||
amount = frames[frameIndex + FRAME_VALUE] - prevFrameValue;
|
||||
amount = frames[frame + VALUE] - prevFrameValue;
|
||||
while (amount > 180)
|
||||
amount -= 360;
|
||||
while (amount < -180)
|
||||
@ -270,9 +270,9 @@ namespace Spine {
|
||||
}
|
||||
|
||||
public class TranslateTimeline : CurveTimeline {
|
||||
protected const int PREV_FRAME_TIME = -3;
|
||||
protected const int FRAME_X = 1;
|
||||
protected const int FRAME_Y = 2;
|
||||
protected const int PREV_TIME = -3;
|
||||
protected const int X = 1;
|
||||
protected const int Y = 2;
|
||||
|
||||
internal int boneIndex;
|
||||
internal float[] frames;
|
||||
@ -306,15 +306,15 @@ namespace Spine {
|
||||
}
|
||||
|
||||
// Interpolate between the previous frame and the current frame.
|
||||
int frameIndex = Animation.binarySearch(frames, time, 3);
|
||||
float prevFrameX = frames[frameIndex - 2];
|
||||
float prevFrameY = frames[frameIndex - 1];
|
||||
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));
|
||||
int frame = Animation.binarySearch(frames, time, 3);
|
||||
float prevFrameX = frames[frame - 2];
|
||||
float prevFrameY = frames[frame - 1];
|
||||
float frameTime = frames[frame];
|
||||
float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime);
|
||||
percent = GetCurvePercent(frame / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
|
||||
|
||||
bone.x += (bone.data.x + prevFrameX + (frames[frameIndex + FRAME_X] - prevFrameX) * percent - bone.x) * alpha;
|
||||
bone.y += (bone.data.y + prevFrameY + (frames[frameIndex + FRAME_Y] - prevFrameY) * percent - bone.y) * alpha;
|
||||
bone.x += (bone.data.x + prevFrameX + (frames[frame + X] - prevFrameX) * percent - bone.x) * alpha;
|
||||
bone.y += (bone.data.y + prevFrameY + (frames[frame + Y] - prevFrameY) * percent - bone.y) * alpha;
|
||||
}
|
||||
}
|
||||
|
||||
@ -335,24 +335,53 @@ namespace Spine {
|
||||
}
|
||||
|
||||
// Interpolate between the previous frame and the current frame.
|
||||
int frameIndex = Animation.binarySearch(frames, time, 3);
|
||||
float prevFrameX = frames[frameIndex - 2];
|
||||
float prevFrameY = frames[frameIndex - 1];
|
||||
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));
|
||||
int frame = Animation.binarySearch(frames, time, 3);
|
||||
float prevFrameX = frames[frame - 2];
|
||||
float prevFrameY = frames[frame - 1];
|
||||
float frameTime = frames[frame];
|
||||
float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime);
|
||||
percent = GetCurvePercent(frame / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
|
||||
|
||||
bone.scaleX += (bone.data.scaleX * (prevFrameX + (frames[frameIndex + FRAME_X] - prevFrameX) * percent) - bone.scaleX) * alpha;
|
||||
bone.scaleY += (bone.data.scaleY * (prevFrameY + (frames[frameIndex + FRAME_Y] - prevFrameY) * percent) - bone.scaleY) * alpha;
|
||||
bone.scaleX += (bone.data.scaleX * (prevFrameX + (frames[frame + X] - prevFrameX) * percent) - bone.scaleX) * alpha;
|
||||
bone.scaleY += (bone.data.scaleY * (prevFrameY + (frames[frame + Y] - prevFrameY) * percent) - bone.scaleY) * alpha;
|
||||
}
|
||||
}
|
||||
|
||||
public class ShearTimeline : TranslateTimeline {
|
||||
public ShearTimeline (int frameCount)
|
||||
: base (frameCount) {
|
||||
}
|
||||
|
||||
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
|
||||
float[] frames = this.frames;
|
||||
if (time < frames[0]) return; // Time is before first frame.
|
||||
|
||||
Bone bone = skeleton.bones.Items[boneIndex];
|
||||
if (time >= frames[frames.Length - 3]) { // Time is after last frame.
|
||||
bone.shearX += (bone.data.shearX + frames[frames.Length - 2] - bone.shearX) * alpha;
|
||||
bone.shearY += (bone.data.shearY + frames[frames.Length - 1] - bone.shearY) * alpha;
|
||||
return;
|
||||
}
|
||||
|
||||
// Interpolate between the previous frame and the current frame.
|
||||
int frame = Animation.binarySearch(frames, time, 3);
|
||||
float prevFrameX = frames[frame - 2];
|
||||
float prevFrameY = frames[frame - 1];
|
||||
float frameTime = frames[frame];
|
||||
float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime);
|
||||
percent = GetCurvePercent(frame / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
|
||||
|
||||
bone.shearX += (bone.data.shearX + (prevFrameX + (frames[frame + X] - prevFrameX) * percent) - bone.shearX) * alpha;
|
||||
bone.shearY += (bone.data.shearY + (prevFrameY + (frames[frame + Y] - prevFrameY) * percent) - bone.shearY) * alpha;
|
||||
}
|
||||
}
|
||||
|
||||
public class ColorTimeline : CurveTimeline {
|
||||
protected const int PREV_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;
|
||||
protected const int PREV_TIME = -5;
|
||||
protected const int R = 1;
|
||||
protected const int G = 2;
|
||||
protected const int B = 3;
|
||||
protected const int A = 4;
|
||||
|
||||
internal int slotIndex;
|
||||
internal float[] frames;
|
||||
@ -380,8 +409,7 @@ namespace Spine {
|
||||
if (time < frames[0]) return; // Time is before first frame.
|
||||
|
||||
float r, g, b, a;
|
||||
if (time >= frames[frames.Length - 5]) {
|
||||
// Time is after last frame.
|
||||
if (time >= frames[frames.Length - 5]) { // Time is after last frame.
|
||||
int i = frames.Length - 1;
|
||||
r = frames[i - 3];
|
||||
g = frames[i - 2];
|
||||
@ -389,19 +417,19 @@ namespace Spine {
|
||||
a = frames[i];
|
||||
} else {
|
||||
// Interpolate between the previous frame and the current frame.
|
||||
int frameIndex = Animation.binarySearch(frames, time, 5);
|
||||
float prevFrameR = frames[frameIndex - 4];
|
||||
float prevFrameG = frames[frameIndex - 3];
|
||||
float prevFrameB = frames[frameIndex - 2];
|
||||
float prevFrameA = frames[frameIndex - 1];
|
||||
float frameTime = frames[frameIndex];
|
||||
float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime);
|
||||
percent = GetCurvePercent(frameIndex / 5 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
|
||||
int frame = Animation.binarySearch(frames, time, 5);
|
||||
float frameTime = frames[frame];
|
||||
float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime);
|
||||
percent = GetCurvePercent(frame / 5 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
|
||||
|
||||
r = prevFrameR + (frames[frameIndex + FRAME_R] - prevFrameR) * percent;
|
||||
g = prevFrameG + (frames[frameIndex + FRAME_G] - prevFrameG) * percent;
|
||||
b = prevFrameB + (frames[frameIndex + FRAME_B] - prevFrameB) * percent;
|
||||
a = prevFrameA + (frames[frameIndex + FRAME_A] - prevFrameA) * percent;
|
||||
r = frames[frame - 4];
|
||||
g = frames[frame - 3];
|
||||
b = frames[frame - 2];
|
||||
a = frames[frame - 1];
|
||||
r += (frames[frame + R] - r) * percent;
|
||||
g += (frames[frame + G] - g) * percent;
|
||||
b += (frames[frame + B] - b) * percent;
|
||||
a += (frames[frame + A] - a) * percent;
|
||||
}
|
||||
Slot slot = skeleton.slots.Items[slotIndex];
|
||||
if (alpha < 1) {
|
||||
@ -488,19 +516,19 @@ namespace Spine {
|
||||
return;
|
||||
if (time < frames[0]) return; // Time is before first frame.
|
||||
|
||||
int frameIndex;
|
||||
int frame;
|
||||
if (lastTime < frames[0])
|
||||
frameIndex = 0;
|
||||
frame = 0;
|
||||
else {
|
||||
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;
|
||||
frameIndex--;
|
||||
frame = Animation.binarySearch(frames, lastTime);
|
||||
float frameTime = frames[frame];
|
||||
while (frame > 0) { // Fire multiple events with the same frame.
|
||||
if (frames[frame - 1] != frameTime) break;
|
||||
frame--;
|
||||
}
|
||||
}
|
||||
for (; frameIndex < frameCount && time >= frames[frameIndex]; frameIndex++)
|
||||
firedEvents.Add(events[frameIndex]);
|
||||
for (; frame < frameCount && time >= frames[frame]; frame++)
|
||||
firedEvents.Add(events[frame]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -528,15 +556,15 @@ namespace Spine {
|
||||
float[] frames = this.frames;
|
||||
if (time < frames[0]) return; // Time is before first frame.
|
||||
|
||||
int frameIndex;
|
||||
int frame;
|
||||
if (time >= frames[frames.Length - 1]) // Time is after last frame.
|
||||
frameIndex = frames.Length - 1;
|
||||
frame = frames.Length - 1;
|
||||
else
|
||||
frameIndex = Animation.binarySearch(frames, time) - 1;
|
||||
frame = Animation.binarySearch(frames, time) - 1;
|
||||
|
||||
ExposedList<Slot> drawOrder = skeleton.drawOrder;
|
||||
ExposedList<Slot> slots = skeleton.slots;
|
||||
int[] drawOrderToSetupIndex = drawOrders[frameIndex];
|
||||
int[] drawOrderToSetupIndex = drawOrders[frame];
|
||||
if (drawOrderToSetupIndex == null) {
|
||||
drawOrder.Clear();
|
||||
for (int i = 0, n = slots.Count; i < n; i++)
|
||||
@ -605,13 +633,13 @@ namespace Spine {
|
||||
}
|
||||
|
||||
// Interpolate between the previous frame and the current frame.
|
||||
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));
|
||||
int frame = Animation.binarySearch(frames, time);
|
||||
float frameTime = frames[frame];
|
||||
float percent = 1 - (time - frameTime) / (frames[frame - 1] - frameTime);
|
||||
percent = GetCurvePercent(frame - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
|
||||
|
||||
float[] prevVertices = frameVertices[frameIndex - 1];
|
||||
float[] nextVertices = frameVertices[frameIndex];
|
||||
float[] prevVertices = frameVertices[frame - 1];
|
||||
float[] nextVertices = frameVertices[frame];
|
||||
|
||||
if (alpha < 1) {
|
||||
for (int i = 0; i < vertexCount; i++) {
|
||||
@ -629,10 +657,10 @@ namespace Spine {
|
||||
}
|
||||
|
||||
public class IkConstraintTimeline : CurveTimeline {
|
||||
private const int PREV_FRAME_TIME = -3;
|
||||
private const int PREV_FRAME_MIX = -2;
|
||||
private const int PREV_FRAME_BEND_DIRECTION = -1;
|
||||
private const int FRAME_MIX = 1;
|
||||
private const int PREV_TIME = -3;
|
||||
private const int PREV_MIX = -2;
|
||||
private const int PREV_BEND_DIRECTION = -1;
|
||||
private const int MIX = 1;
|
||||
|
||||
internal int ikConstraintIndex;
|
||||
internal float[] frames;
|
||||
@ -644,8 +672,8 @@ namespace Spine {
|
||||
: base(frameCount) {
|
||||
frames = new float[frameCount * 3];
|
||||
}
|
||||
|
||||
/** Sets the time, mix and bend direction of the specified keyframe. */
|
||||
|
||||
/// <summary>Sets the time, mix and bend direction of the specified keyframe.</summary>
|
||||
public void SetFrame (int frameIndex, float time, float mix, int bendDirection) {
|
||||
frameIndex *= 3;
|
||||
frames[frameIndex] = time;
|
||||
@ -657,24 +685,86 @@ namespace Spine {
|
||||
float[] frames = this.frames;
|
||||
if (time < frames[0]) return; // Time is before first frame.
|
||||
|
||||
IkConstraint ikConstraint = skeleton.ikConstraints.Items[ikConstraintIndex];
|
||||
IkConstraint constraint = skeleton.ikConstraints.Items[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];
|
||||
constraint.mix += (frames[frames.Length + PREV_MIX] - constraint.mix) * alpha;
|
||||
constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION];
|
||||
return;
|
||||
}
|
||||
|
||||
// Interpolate between the previous frame and the current frame.
|
||||
int frameIndex = Animation.binarySearch(frames, time, 3);
|
||||
float prevFrameMix = frames[frameIndex + PREV_FRAME_MIX];
|
||||
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));
|
||||
int frame = Animation.binarySearch(frames, time, 3);
|
||||
float frameTime = frames[frame];
|
||||
float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime);
|
||||
percent = GetCurvePercent(frame / 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 + PREV_FRAME_BEND_DIRECTION];
|
||||
float mix = frames[frame + PREV_MIX];
|
||||
constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha;
|
||||
constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION];
|
||||
}
|
||||
}
|
||||
|
||||
public class TransformConstraintTimeline : CurveTimeline {
|
||||
private const int PREV_TIME = -5;
|
||||
private const int PREV_ROTATE_MIX = -4;
|
||||
private const int PREV_TRANSLATE_MIX = -3;
|
||||
private const int PREV_SCALE_MIX = -2;
|
||||
private const int PREV_SHEAR_MIX = -1;
|
||||
private const int ROTATE_MIX = 1;
|
||||
private const int TRANSLATE_MIX = 2;
|
||||
private const int SCALE_MIX = 3;
|
||||
private const int SHEAR_MIX = 4;
|
||||
|
||||
internal int transformConstraintIndex;
|
||||
internal float[] frames;
|
||||
|
||||
public int TransformConstraintIndex { get { return transformConstraintIndex; } set { transformConstraintIndex = value; } }
|
||||
public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, scale mix, shear mix, ...
|
||||
|
||||
public TransformConstraintTimeline (int frameCount)
|
||||
: base(frameCount) {
|
||||
frames = new float[frameCount * 5];
|
||||
}
|
||||
|
||||
public void SetFrame (int frameIndex, float time, float rotateMix, float translateMix, float scaleMix, float shearMix) {
|
||||
frameIndex *= 5;
|
||||
frames[frameIndex] = time;
|
||||
frames[frameIndex + 1] = rotateMix;
|
||||
frames[frameIndex + 2] = translateMix;
|
||||
frames[frameIndex + 3] = scaleMix;
|
||||
frames[frameIndex + 4] = shearMix;
|
||||
}
|
||||
|
||||
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
|
||||
float[] frames = this.frames;
|
||||
if (time < frames[0]) return; // Time is before first frame.
|
||||
|
||||
TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex];
|
||||
|
||||
if (time >= frames[frames.Length - 5]) { // Time is after last frame.
|
||||
int i = frames.Length - 1;
|
||||
constraint.rotateMix += (frames[i - 3] - constraint.rotateMix) * alpha;
|
||||
constraint.translateMix += (frames[i - 2] - constraint.translateMix) * alpha;
|
||||
constraint.scaleMix += (frames[i - 1] - constraint.scaleMix) * alpha;
|
||||
constraint.shearMix += (frames[i] - constraint.shearMix) * alpha;
|
||||
return;
|
||||
}
|
||||
|
||||
// Interpolate between the previous frame and the current frame.
|
||||
int frame = Animation.binarySearch(frames, time, 5);
|
||||
float frameTime = frames[frame];
|
||||
float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime);
|
||||
percent = GetCurvePercent(frame / 5 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
|
||||
|
||||
float rotate = frames[frame + PREV_ROTATE_MIX];
|
||||
float translate = frames[frame + PREV_TRANSLATE_MIX];
|
||||
float scale = frames[frame + PREV_SCALE_MIX];
|
||||
float shear = frames[frame + PREV_SHEAR_MIX];
|
||||
constraint.rotateMix += (rotate + (frames[frame + ROTATE_MIX] - rotate) * percent - constraint.rotateMix) * alpha;
|
||||
constraint.translateMix += (translate + (frames[frame + TRANSLATE_MIX] - translate) * percent - constraint.translateMix) * alpha;
|
||||
constraint.scaleMix += (scale + (frames[frame + SCALE_MIX] - scale) * percent - constraint.scaleMix) * alpha;
|
||||
constraint.shearMix += (shear + (frames[frame + SHEAR_MIX] - shear) * percent - constraint.shearMix) * alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,7 +40,7 @@ namespace Spine {
|
||||
internal Skeleton skeleton;
|
||||
internal Bone parent;
|
||||
internal ExposedList<Bone> children = new ExposedList<Bone>();
|
||||
internal float x, y, rotation, scaleX, scaleY;
|
||||
internal float x, y, rotation, scaleX, scaleY, shearX, shearY;
|
||||
internal float appliedRotation, appliedScaleX, appliedScaleY;
|
||||
|
||||
internal float a, b, worldX;
|
||||
@ -62,6 +62,8 @@ namespace Spine {
|
||||
public float AppliedScaleY { get { return appliedScaleY; } set { appliedScaleY = value; } }
|
||||
public float ScaleX { get { return scaleX; } set { scaleX = value; } }
|
||||
public float ScaleY { get { return scaleY; } set { scaleY = value; } }
|
||||
public float ShearX { get { return shearX; } set { shearX = value; } }
|
||||
public float ShearY { get { return shearY; } set { shearY = value; } }
|
||||
|
||||
public float A { get { return a; } }
|
||||
public float B { get { return b; } }
|
||||
@ -88,22 +90,24 @@ namespace Spine {
|
||||
|
||||
/// <summary>Same as {@link #updateWorldTransform()}. This method exists for Bone to implement {@link Updatable}.</summary>
|
||||
public void Update () {
|
||||
UpdateWorldTransform(x, y, rotation, scaleX, scaleY);
|
||||
UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY);
|
||||
}
|
||||
|
||||
/// <summary>Computes the world SRT using the parent bone and this bone's local SRT.</summary>
|
||||
public void UpdateWorldTransform () {
|
||||
UpdateWorldTransform(x, y, rotation, scaleX, scaleY);
|
||||
UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY);
|
||||
}
|
||||
|
||||
/// <summary>Computes the world SRT using the parent bone and the specified local SRT.</summary>
|
||||
public void UpdateWorldTransform (float x, float y, float rotation, float scaleX, float scaleY) {
|
||||
public void UpdateWorldTransform (float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) {
|
||||
appliedRotation = rotation;
|
||||
appliedScaleX = scaleX;
|
||||
appliedScaleY = scaleY;
|
||||
|
||||
float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation);
|
||||
float la = cos * scaleX, lb = -sin * scaleY, lc = sin * scaleX, ld = cos * scaleY;
|
||||
float rotationY = rotation + 90 + shearY;
|
||||
float la = MathUtils.CosDeg(rotation + shearX) * scaleX, lb = MathUtils.CosDeg(rotationY) * scaleY;
|
||||
float lc = MathUtils.SinDeg(rotation + shearX) * scaleX, ld = MathUtils.SinDeg(rotationY) * scaleY;
|
||||
|
||||
Bone parent = this.parent;
|
||||
if (parent == null) { // Root bone.
|
||||
Skeleton skeleton = this.skeleton;
|
||||
@ -146,8 +150,7 @@ namespace Spine {
|
||||
pc = 0;
|
||||
pd = 1;
|
||||
do {
|
||||
cos = MathUtils.CosDeg(parent.appliedRotation);
|
||||
sin = MathUtils.SinDeg(parent.appliedRotation);
|
||||
float cos = MathUtils.CosDeg(parent.appliedRotation), sin = MathUtils.SinDeg(parent.appliedRotation);
|
||||
float temp = pa * cos + pb * sin;
|
||||
pb = pa * -sin + pb * cos;
|
||||
pa = temp;
|
||||
@ -168,9 +171,7 @@ namespace Spine {
|
||||
pc = 0;
|
||||
pd = 1;
|
||||
do {
|
||||
float r = parent.rotation;
|
||||
cos = MathUtils.CosDeg(r);
|
||||
sin = MathUtils.SinDeg(r);
|
||||
float r = parent.appliedRotation, cos = MathUtils.CosDeg(r), sin = MathUtils.SinDeg(r);
|
||||
float psx = parent.appliedScaleX, psy = parent.appliedScaleY;
|
||||
float za = cos * psx, zb = -sin * psy, zc = sin * psx, zd = cos * psy;
|
||||
float temp = pa * za + pb * zc;
|
||||
@ -221,6 +222,8 @@ namespace Spine {
|
||||
rotation = data.rotation;
|
||||
scaleX = data.scaleX;
|
||||
scaleY = data.scaleY;
|
||||
shearX = data.shearX;
|
||||
shearY = data.shearY;
|
||||
}
|
||||
|
||||
public void WorldToLocal (float worldX, float worldY, out float localX, out float localY) {
|
||||
|
||||
@ -35,7 +35,7 @@ namespace Spine {
|
||||
public class BoneData {
|
||||
internal BoneData parent;
|
||||
internal String name;
|
||||
internal float length, x, y, rotation, scaleX = 1, scaleY = 1;
|
||||
internal float length, x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY;
|
||||
internal bool inheritScale = true, inheritRotation = true;
|
||||
|
||||
/// <summary>May be null.</summary>
|
||||
@ -47,6 +47,8 @@ namespace Spine {
|
||||
public float Rotation { get { return rotation; } set { rotation = value; } }
|
||||
public float ScaleX { get { return scaleX; } set { scaleX = value; } }
|
||||
public float ScaleY { get { return scaleY; } set { scaleY = value; } }
|
||||
public float ShearX { get { return shearX; } set { shearX = value; } }
|
||||
public float ShearY { get { return shearY; } set { shearY = value; } }
|
||||
public bool InheritScale { get { return inheritScale; } set { inheritScale = value; } }
|
||||
public bool InheritRotation { get { return inheritRotation; } set { inheritRotation = value; } }
|
||||
|
||||
|
||||
@ -83,14 +83,17 @@ namespace Spine {
|
||||
/// <summary>Adjusts the bone rotation so the tip is as close to the target position as possible. The target is specified
|
||||
/// in the world coordinate system.</summary>
|
||||
static public void Apply (Bone bone, float targetX, float targetY, float alpha) {
|
||||
float parentRotation = bone.parent == null ? 0 : bone.parent.WorldRotationX;
|
||||
float rotation = bone.rotation;
|
||||
float rotationIK = MathUtils.Atan2(targetY - bone.worldY, targetX - bone.worldX) * MathUtils.radDeg - parentRotation;
|
||||
if ((bone.worldSignX != bone.worldSignY) != (bone.skeleton.flipX != (bone.skeleton.flipY != Bone.yDown)))
|
||||
rotationIK = 360 - rotationIK;
|
||||
if (rotationIK > 180) rotationIK -= 360;
|
||||
Bone pp = bone.parent;
|
||||
float id = 1 / (pp.a * pp.d - pp.b * pp.c);
|
||||
float x = targetX - pp.worldX, y = targetY - pp.worldY;
|
||||
float tx = (x * pp.d - y * pp.b) * id - bone.x, ty = (y * pp.a - x * pp.c) * id - bone.y;
|
||||
float rotationIK = MathUtils.Atan2(ty, tx) * MathUtils.radDeg - bone.shearX;
|
||||
if (bone.scaleX < 0) rotationIK += 180;
|
||||
if (rotationIK > 180)
|
||||
rotationIK -= 360;
|
||||
else if (rotationIK < -180) rotationIK += 360;
|
||||
bone.UpdateWorldTransform(bone.x, bone.y, rotation + (rotationIK - rotation) * alpha, bone.appliedScaleX, bone.appliedScaleY);
|
||||
bone.UpdateWorldTransform(bone.x, bone.y, bone.rotation + (rotationIK - bone.rotation) * alpha, bone.appliedScaleX,
|
||||
bone.appliedScaleY, bone.shearX, bone.shearY);
|
||||
}
|
||||
|
||||
/// <summary>Adjusts the parent and child bone rotations so the tip of the child is as close to the target position as
|
||||
@ -125,22 +128,12 @@ namespace Spine {
|
||||
} else
|
||||
os2 = 0;
|
||||
Bone pp = parent.parent;
|
||||
float tx, ty, dx, dy;
|
||||
if (pp == null) {
|
||||
tx = targetX - px;
|
||||
ty = targetY - py;
|
||||
dx = child.worldX - px;
|
||||
dy = child.worldY - py;
|
||||
} else {
|
||||
float a = pp.a, b = pp.b, c = pp.c, d = pp.d, invDet = 1 / (a * d - b * c);
|
||||
float wx = pp.worldX, wy = pp.worldY, x = targetX - wx, y = targetY - wy;
|
||||
tx = (x * d - y * b) * invDet - px;
|
||||
ty = (y * a - x * c) * invDet - py;
|
||||
x = child.worldX - wx;
|
||||
y = child.worldY - wy;
|
||||
dx = (x * d - y * b) * invDet - px;
|
||||
dy = (y * a - x * c) * invDet - py;
|
||||
}
|
||||
float ppa = pp.a, ppb = pp.b, ppc = pp.c, ppd = pp.d, id = 1 / (ppa * ppd - ppb * ppc);
|
||||
float x = targetX - pp.worldX, y = targetY - pp.worldY;
|
||||
float tx = (x * ppd - y * ppb) * id - px, ty = (y * ppa - x * ppc) * id - py;
|
||||
x = child.worldX - pp.worldX;
|
||||
y = child.worldY - pp.worldY;
|
||||
float dx = (x * ppd - y * ppb) * id - px, dy = (y * ppa - x * ppc) * id - py;
|
||||
float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2;
|
||||
if (u) {
|
||||
l2 *= psx;
|
||||
@ -162,40 +155,41 @@ namespace Spine {
|
||||
float r0 = q / c2, r1 = c0 / q;
|
||||
float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1;
|
||||
if (r * r <= dd) {
|
||||
float y1 = (float)Math.Sqrt(dd - r * r) * bendDir;
|
||||
a1 = ta - MathUtils.Atan2(y1, r);
|
||||
a2 = MathUtils.Atan2(y1 / psy, (r - l1) / psx);
|
||||
y = (float)Math.Sqrt(dd - r * r) * bendDir;
|
||||
a1 = ta - MathUtils.Atan2(y, r);
|
||||
a2 = MathUtils.Atan2(y / psy, (r - l1) / psx);
|
||||
goto outer;
|
||||
}
|
||||
}
|
||||
float minAngle = 0, minDist = float.MaxValue, minX = 0, minY = 0;
|
||||
float maxAngle = 0, maxDist = 0, maxX = 0, maxY = 0;
|
||||
float x = l1 + a, dist = x * x;
|
||||
if (dist > maxDist) {
|
||||
x = l1 + a;
|
||||
d = x * x;
|
||||
if (d > maxDist) {
|
||||
maxAngle = 0;
|
||||
maxDist = dist;
|
||||
maxDist = d;
|
||||
maxX = x;
|
||||
}
|
||||
x = l1 - a;
|
||||
dist = x * x;
|
||||
if (dist < minDist) {
|
||||
d = x * x;
|
||||
if (d < minDist) {
|
||||
minAngle = MathUtils.PI;
|
||||
minDist = dist;
|
||||
minDist = d;
|
||||
minX = x;
|
||||
}
|
||||
float angle = (float)Math.Acos(-a * l1 / (aa - bb));
|
||||
x = a * MathUtils.Cos(angle) + l1;
|
||||
float y = b * MathUtils.Sin(angle);
|
||||
dist = x * x + y * y;
|
||||
if (dist < minDist) {
|
||||
y = b * MathUtils.Sin(angle);
|
||||
d = x * x + y * y;
|
||||
if (d < minDist) {
|
||||
minAngle = angle;
|
||||
minDist = dist;
|
||||
minDist = d;
|
||||
minX = x;
|
||||
minY = y;
|
||||
}
|
||||
if (dist > maxDist) {
|
||||
if (d > maxDist) {
|
||||
maxAngle = angle;
|
||||
maxDist = dist;
|
||||
maxDist = d;
|
||||
maxX = x;
|
||||
maxY = y;
|
||||
}
|
||||
@ -210,15 +204,15 @@ namespace Spine {
|
||||
outer:
|
||||
float os = MathUtils.Atan2(cy, cx) * s2;
|
||||
a1 = (a1 - os) * MathUtils.radDeg + os1;
|
||||
a2 = (a2 + os) * MathUtils.radDeg * s2 + os2;
|
||||
a2 = ((a2 + os) * MathUtils.radDeg - child.shearX) * s2 + os2;
|
||||
if (a1 > 180) a1 -= 360;
|
||||
else if (a1 < -180) a1 += 360;
|
||||
if (a2 > 180) a2 -= 360;
|
||||
else if (a2 < -180) a2 += 360;
|
||||
float rotation = parent.rotation;
|
||||
parent.UpdateWorldTransform(px, py, rotation + (a1 - rotation) * alpha, parent.appliedScaleX, parent.appliedScaleY);
|
||||
parent.UpdateWorldTransform(px, py, rotation + (a1 - rotation) * alpha, parent.appliedScaleX, parent.appliedScaleY, 0, 0);
|
||||
rotation = child.rotation;
|
||||
child.UpdateWorldTransform(cx, cy, rotation + (a2 - rotation) * alpha, child.appliedScaleX, child.appliedScaleY);
|
||||
child.UpdateWorldTransform(cx, cy, rotation + (a2 - rotation) * alpha, child.appliedScaleX, child.appliedScaleY, child.shearX, child.shearY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,6 +34,7 @@ using System;
|
||||
namespace Spine {
|
||||
public static class MathUtils {
|
||||
public const float PI = 3.1415927f;
|
||||
public const float PI2 = PI * 2;
|
||||
public const float radDeg = 180f / PI;
|
||||
public const float degRad = PI / 180;
|
||||
|
||||
|
||||
@ -127,8 +127,7 @@ namespace Spine {
|
||||
for (int i = 0; i < transformConstraintsCount; i++) {
|
||||
TransformConstraint transformConstraint = transformConstraints.Items[i];
|
||||
for (int ii = updateCache.Count - 1; i >= 0; ii--) {
|
||||
IUpdatable updateable = updateCache.Items[ii];
|
||||
if (updateable == transformConstraint.bone || updateable == transformConstraint.target) {
|
||||
if (updateCache.Items[ii] == transformConstraint.bone) {
|
||||
updateCache.Insert(ii + 1, transformConstraint);
|
||||
break;
|
||||
}
|
||||
@ -165,9 +164,11 @@ namespace Spine {
|
||||
ExposedList<TransformConstraint> transformConstraints = this.transformConstraints;
|
||||
for (int i = 0, n = transformConstraints.Count; i < n; i++) {
|
||||
TransformConstraint constraint = transformConstraints.Items[i];
|
||||
constraint.translateMix = constraint.data.translateMix;
|
||||
constraint.x = constraint.data.x;
|
||||
constraint.y = constraint.data.y;
|
||||
TransformConstraintData data = constraint.data;
|
||||
constraint.rotateMix = data.rotateMix;
|
||||
constraint.translateMix = data.translateMix;
|
||||
constraint.scaleMix = data.scaleMix;
|
||||
constraint.shearMix = data.shearMix;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -29,6 +29,10 @@
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#if (UNITY_5 || UNITY_4_0 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_4 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7 || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1)
|
||||
#define IS_UNITY
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
@ -40,11 +44,12 @@ using Windows.Storage;
|
||||
|
||||
namespace Spine {
|
||||
public class SkeletonBinary {
|
||||
public const int TIMELINE_SCALE = 0;
|
||||
public const int TIMELINE_ROTATE = 1;
|
||||
public const int TIMELINE_TRANSLATE = 2;
|
||||
public const int TIMELINE_ATTACHMENT = 3;
|
||||
public const int TIMELINE_COLOR = 4;
|
||||
public const int TIMELINE_ROTATE = 0;
|
||||
public const int TIMELINE_TRANSLATE = 1;
|
||||
public const int TIMELINE_SCALE = 2;
|
||||
public const int TIMELINE_SHEAR = 3;
|
||||
public const int TIMELINE_ATTACHMENT = 4;
|
||||
public const int TIMELINE_COLOR = 5;
|
||||
|
||||
public const int CURVE_LINEAR = 0;
|
||||
public const int CURVE_STEPPED = 1;
|
||||
@ -65,10 +70,8 @@ namespace Spine {
|
||||
this.attachmentLoader = attachmentLoader;
|
||||
Scale = 1;
|
||||
}
|
||||
|
||||
#if !(UNITY_5 || UNITY_4 || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1)
|
||||
#if WINDOWS_STOREAPP
|
||||
|
||||
|
||||
#if !ISUNITY && WINDOWS_STOREAPP
|
||||
private async Task<SkeletonData> ReadFile(string path) {
|
||||
var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
|
||||
using (var input = new BufferedStream(await folder.GetFileAsync(path).AsTask().ConfigureAwait(false))) {
|
||||
@ -95,7 +98,6 @@ namespace Spine {
|
||||
}
|
||||
|
||||
#endif // WINDOWS_STOREAPP
|
||||
#endif // !(UNITY)
|
||||
|
||||
public SkeletonData ReadSkeletonData (Stream input) {
|
||||
if (input == null) throw new ArgumentNullException("input");
|
||||
@ -121,14 +123,16 @@ namespace Spine {
|
||||
String name = ReadString(input);
|
||||
BoneData parent = i == 0 ? null : skeletonData.bones.Items[ReadVarint(input, true)];
|
||||
BoneData boneData = new BoneData(name, parent);
|
||||
boneData.rotation = ReadFloat(input);
|
||||
boneData.x = ReadFloat(input) * scale;
|
||||
boneData.y = ReadFloat(input) * scale;
|
||||
boneData.scaleX = ReadFloat(input);
|
||||
boneData.scaleY = ReadFloat(input);
|
||||
boneData.rotation = ReadFloat(input);
|
||||
boneData.shearX = ReadFloat(input);
|
||||
boneData.shearY = ReadFloat(input);
|
||||
boneData.length = ReadFloat(input) * scale;
|
||||
boneData.inheritScale = ReadBoolean(input);
|
||||
boneData.inheritRotation = ReadBoolean(input);
|
||||
boneData.inheritScale = ReadBoolean(input);
|
||||
if (nonessential) ReadInt(input); // Skip bone color.
|
||||
skeletonData.bones.Add(boneData);
|
||||
}
|
||||
@ -149,9 +153,16 @@ namespace Spine {
|
||||
TransformConstraintData transformConstraintData = new TransformConstraintData(ReadString(input));
|
||||
transformConstraintData.bone = skeletonData.bones.Items[ReadVarint(input, true)];
|
||||
transformConstraintData.target = skeletonData.bones.Items[ReadVarint(input, true)];
|
||||
transformConstraintData.offsetRotation = ReadFloat(input);
|
||||
transformConstraintData.offsetX = ReadFloat(input) * scale;
|
||||
transformConstraintData.offsetY = ReadFloat(input) * scale;
|
||||
transformConstraintData.offsetScaleX = ReadFloat(input);
|
||||
transformConstraintData.offsetScaleY = ReadFloat(input);
|
||||
transformConstraintData.offsetShearY = ReadFloat(input);
|
||||
transformConstraintData.rotateMix = ReadFloat(input);
|
||||
transformConstraintData.translateMix = ReadFloat(input);
|
||||
transformConstraintData.x = ReadFloat(input) * scale;
|
||||
transformConstraintData.y = ReadFloat(input) * scale;
|
||||
transformConstraintData.scaleMix = ReadFloat(input);
|
||||
transformConstraintData.shearMix = ReadFloat(input);
|
||||
skeletonData.transformConstraints.Add(transformConstraintData);
|
||||
}
|
||||
|
||||
@ -247,11 +258,11 @@ namespace Spine {
|
||||
switch (type) {
|
||||
case AttachmentType.region: {
|
||||
String path = ReadString(input);
|
||||
float rotation = ReadFloat(input);
|
||||
float x = ReadFloat(input);
|
||||
float y = ReadFloat(input);
|
||||
float scaleX = ReadFloat(input);
|
||||
float scaleY = ReadFloat(input);
|
||||
float rotation = ReadFloat(input);
|
||||
float width = ReadFloat(input);
|
||||
float height = ReadFloat(input);
|
||||
int color = ReadInt(input);
|
||||
@ -508,11 +519,14 @@ namespace Spine {
|
||||
break;
|
||||
}
|
||||
case TIMELINE_TRANSLATE:
|
||||
case TIMELINE_SCALE: {
|
||||
case TIMELINE_SCALE:
|
||||
case TIMELINE_SHEAR: {
|
||||
TranslateTimeline timeline;
|
||||
float timelineScale = 1;
|
||||
if (timelineType == TIMELINE_SCALE)
|
||||
timeline = new ScaleTimeline(frameCount);
|
||||
else if (timelineType == TIMELINE_SHEAR)
|
||||
timeline = new ShearTimeline(frameCount);
|
||||
else {
|
||||
timeline = new TranslateTimeline(frameCount);
|
||||
timelineScale = scale;
|
||||
@ -533,10 +547,10 @@ namespace Spine {
|
||||
|
||||
// IK timelines.
|
||||
for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
|
||||
IkConstraintData ikConstraint = skeletonData.ikConstraints.Items[ReadVarint(input, true)];
|
||||
IkConstraintData constraint = skeletonData.ikConstraints.Items[ReadVarint(input, true)];
|
||||
int frameCount = ReadVarint(input, true);
|
||||
IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount);
|
||||
timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(ikConstraint);
|
||||
timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint);
|
||||
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
|
||||
timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input));
|
||||
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
|
||||
@ -545,6 +559,20 @@ namespace Spine {
|
||||
duration = Math.Max(duration, timeline.frames[frameCount * 3 - 3]);
|
||||
}
|
||||
|
||||
// Transform constraint timelines.
|
||||
for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
|
||||
TransformConstraintData constraint = skeletonData.transformConstraints.Items[ReadVarint(input, true)];
|
||||
int frameCount = ReadVarint(input, true);
|
||||
TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount);
|
||||
timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint);
|
||||
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
|
||||
timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input));
|
||||
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
|
||||
}
|
||||
timelines.Add(timeline);
|
||||
duration = Math.Max(duration, timeline.frames[frameCount * 5 - 5]);
|
||||
}
|
||||
|
||||
// FFD timelines.
|
||||
for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
|
||||
Skin skin = skeletonData.skins.Items[ReadVarint(input, true)];
|
||||
|
||||
@ -59,9 +59,7 @@ namespace Spine {
|
||||
Scale = 1;
|
||||
}
|
||||
|
||||
#if !(IS_UNITY)
|
||||
#if WINDOWS_STOREAPP
|
||||
|
||||
#if !(IS_UNITY) && WINDOWS_STOREAPP
|
||||
private async Task<SkeletonData> ReadFile(string path) {
|
||||
var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
|
||||
var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false);
|
||||
@ -88,9 +86,7 @@ namespace Spine {
|
||||
return skeletonData;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // WINDOWS_STOREAPP
|
||||
#endif // !UNITY
|
||||
|
||||
public SkeletonData ReadSkeletonData (TextReader reader) {
|
||||
if (reader == null) throw new ArgumentNullException("reader cannot be null.");
|
||||
@ -125,6 +121,8 @@ namespace Spine {
|
||||
boneData.rotation = GetFloat(boneMap, "rotation", 0);
|
||||
boneData.scaleX = GetFloat(boneMap, "scaleX", 1);
|
||||
boneData.scaleY = GetFloat(boneMap, "scaleY", 1);
|
||||
boneData.shearX = GetFloat(boneMap, "shearX", 1);
|
||||
boneData.shearY = GetFloat(boneMap, "shearY", 1);
|
||||
boneData.inheritScale = GetBoolean(boneMap, "inheritScale", true);
|
||||
boneData.inheritRotation = GetBoolean(boneMap, "inheritRotation", true);
|
||||
skeletonData.bones.Add(boneData);
|
||||
@ -165,9 +163,17 @@ namespace Spine {
|
||||
transformConstraintData.target = skeletonData.FindBone(targetName);
|
||||
if (transformConstraintData.target == null) throw new Exception("Target bone not found: " + targetName);
|
||||
|
||||
transformConstraintData.offsetRotation = GetFloat(transformMap, "rotation", 0);
|
||||
transformConstraintData.offsetX = GetFloat(transformMap, "x", 0) * scale;
|
||||
transformConstraintData.offsetY = GetFloat(transformMap, "y", 0) * scale;
|
||||
transformConstraintData.offsetScaleX = GetFloat(transformMap, "scaleX", 0);
|
||||
transformConstraintData.offsetScaleY = GetFloat(transformMap, "scaleY", 0);
|
||||
transformConstraintData.offsetShearY = GetFloat(transformMap, "shearY", 0);
|
||||
|
||||
transformConstraintData.rotateMix = GetFloat(transformMap, "rotateMix", 1);
|
||||
transformConstraintData.translateMix = GetFloat(transformMap, "translateMix", 1);
|
||||
transformConstraintData.x = GetFloat(transformMap, "x", 0) * scale;
|
||||
transformConstraintData.y = GetFloat(transformMap, "y", 0) * scale;
|
||||
transformConstraintData.scaleMix = GetFloat(transformMap, "scaleMix", 1);
|
||||
transformConstraintData.shearMix = GetFloat(transformMap, "shearMix", 1);
|
||||
|
||||
skeletonData.transformConstraints.Add(transformConstraintData);
|
||||
}
|
||||
@ -520,11 +526,13 @@ namespace Spine {
|
||||
timelines.Add(timeline);
|
||||
duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 2 - 2]);
|
||||
|
||||
} else if (timelineName == "translate" || timelineName == "scale") {
|
||||
} else if (timelineName == "translate" || timelineName == "scale" || timelineName == "shear") {
|
||||
TranslateTimeline timeline;
|
||||
float timelineScale = 1;
|
||||
if (timelineName == "scale")
|
||||
timeline = new ScaleTimeline(values.Count);
|
||||
else if (timelineName == "shear")
|
||||
timeline = new ShearTimeline(values.Count);
|
||||
else {
|
||||
timeline = new TranslateTimeline(values.Count);
|
||||
timelineScale = scale;
|
||||
@ -534,8 +542,8 @@ namespace Spine {
|
||||
int frameIndex = 0;
|
||||
foreach (Dictionary<String, Object> valueMap in values) {
|
||||
float time = (float)valueMap["time"];
|
||||
float x = valueMap.ContainsKey("x") ? (float)valueMap["x"] : 0;
|
||||
float y = valueMap.ContainsKey("y") ? (float)valueMap["y"] : 0;
|
||||
float x = GetFloat(valueMap, "x", 0);
|
||||
float y = GetFloat(valueMap, "y", 0);
|
||||
timeline.SetFrame(frameIndex, time, (float)x * timelineScale, (float)y * timelineScale);
|
||||
ReadCurve(timeline, frameIndex, valueMap);
|
||||
frameIndex++;
|
||||
@ -549,17 +557,18 @@ namespace Spine {
|
||||
}
|
||||
}
|
||||
|
||||
// IK timelines.
|
||||
if (map.ContainsKey("ik")) {
|
||||
foreach (KeyValuePair<String, Object> ikMap in (Dictionary<String, Object>)map["ik"]) {
|
||||
IkConstraintData ikConstraint = skeletonData.FindIkConstraint(ikMap.Key);
|
||||
var values = (List<Object>)ikMap.Value;
|
||||
foreach (KeyValuePair<String, Object> constraintMap in (Dictionary<String, Object>)map["ik"]) {
|
||||
IkConstraintData constraint = skeletonData.FindIkConstraint(constraintMap.Key);
|
||||
var values = (List<Object>)constraintMap.Value;
|
||||
var timeline = new IkConstraintTimeline(values.Count);
|
||||
timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(ikConstraint);
|
||||
timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint);
|
||||
int frameIndex = 0;
|
||||
foreach (Dictionary<String, Object> valueMap in values) {
|
||||
float time = (float)valueMap["time"];
|
||||
float mix = valueMap.ContainsKey("mix") ? (float)valueMap["mix"] : 1;
|
||||
bool bendPositive = valueMap.ContainsKey("bendPositive") ? (bool)valueMap["bendPositive"] : true;
|
||||
float mix = GetFloat(valueMap, "mix", 1);
|
||||
bool bendPositive = GetBoolean(valueMap, "bendPositive", true);
|
||||
timeline.SetFrame(frameIndex, time, mix, bendPositive ? 1 : -1);
|
||||
ReadCurve(timeline, frameIndex, valueMap);
|
||||
frameIndex++;
|
||||
@ -569,6 +578,30 @@ namespace Spine {
|
||||
}
|
||||
}
|
||||
|
||||
// Transform constraint timelines.
|
||||
if (map.ContainsKey("transform")) {
|
||||
foreach (KeyValuePair<String, Object> constraintMap in (Dictionary<String, Object>)map["transform"]) {
|
||||
TransformConstraintData constraint = skeletonData.FindTransformConstraint(constraintMap.Key);
|
||||
var values = (List<Object>)constraintMap.Value;
|
||||
var timeline = new TransformConstraintTimeline(values.Count);
|
||||
timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint);
|
||||
int frameIndex = 0;
|
||||
foreach (Dictionary<String, Object> valueMap in values) {
|
||||
float time = (float)valueMap["time"];
|
||||
float rotateMix = GetFloat(valueMap, "rotateMix", 1);
|
||||
float translateMix = GetFloat(valueMap, "translateMix", 1);
|
||||
float scaleMix = GetFloat(valueMap, "scaleMix", 1);
|
||||
float shearMix = GetFloat(valueMap, "shearMix", 1);
|
||||
timeline.SetFrame(frameIndex, time, rotateMix, translateMix, scaleMix, shearMix);
|
||||
ReadCurve(timeline, frameIndex, valueMap);
|
||||
frameIndex++;
|
||||
}
|
||||
timelines.Add(timeline);
|
||||
duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 5 - 5]);
|
||||
}
|
||||
}
|
||||
|
||||
// FFD timelines.
|
||||
if (map.ContainsKey("ffd")) {
|
||||
foreach (KeyValuePair<String, Object> ffdMap in (Dictionary<String, Object>)map["ffd"]) {
|
||||
Skin skin = skeletonData.FindSkin(ffdMap.Key);
|
||||
|
||||
@ -36,38 +36,94 @@ namespace Spine {
|
||||
public class TransformConstraint : IUpdatable {
|
||||
internal TransformConstraintData data;
|
||||
internal Bone bone, target;
|
||||
internal float translateMix;
|
||||
internal float x, y;
|
||||
internal float rotateMix, translateMix, scaleMix, shearMix;
|
||||
internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY;
|
||||
|
||||
public TransformConstraintData Data { get { return data; } }
|
||||
public Bone Bone { get { return bone; } set { bone = value; } }
|
||||
public Bone Target { get { return target; } set { target = value; } }
|
||||
public float RotateMix { get { return rotateMix; } set { rotateMix = value; } }
|
||||
public float TranslateMix { get { return translateMix; } set { translateMix = value; } }
|
||||
public float X { get { return x; } set { x = value; } }
|
||||
public float Y { get { return y; } set { y = value; } }
|
||||
public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } }
|
||||
public float ShearMix { get { return shearMix; } set { shearMix = value; } }
|
||||
|
||||
public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } }
|
||||
public float OffsetX { get { return offsetX; } set { offsetX = value; } }
|
||||
public float OffsetY { get { return offsetY; } set { offsetY = value; } }
|
||||
public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } }
|
||||
public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } }
|
||||
public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } }
|
||||
|
||||
public TransformConstraint (TransformConstraintData data, Skeleton skeleton) {
|
||||
if (data == null) throw new ArgumentNullException("data cannot be null.");
|
||||
if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null.");
|
||||
this.data = data;
|
||||
translateMix = data.translateMix;
|
||||
x = data.x;
|
||||
y = data.y;
|
||||
rotateMix = data.rotateMix;
|
||||
scaleMix = data.scaleMix;
|
||||
shearMix = data.shearMix;
|
||||
offsetX = data.offsetX;
|
||||
offsetY = data.offsetY;
|
||||
offsetScaleX = data.offsetScaleX;
|
||||
offsetScaleY = data.offsetScaleY;
|
||||
offsetShearY = data.offsetShearY;
|
||||
|
||||
bone = skeleton.FindBone(data.bone.name);
|
||||
target = skeleton.FindBone(data.target.name);
|
||||
}
|
||||
|
||||
public void Update () {
|
||||
Apply();
|
||||
public void Apply () {
|
||||
Update();
|
||||
}
|
||||
|
||||
public void Apply () {
|
||||
public void Update () {
|
||||
Bone bone = this.bone;
|
||||
Bone target = this.target;
|
||||
|
||||
if (rotateMix > 0) {
|
||||
float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
|
||||
float r = MathUtils.Atan2(target.c, target.a) - MathUtils.Atan2(c, a) + offsetRotation * MathUtils.degRad;
|
||||
if (r > MathUtils.PI)
|
||||
r -= MathUtils.PI2;
|
||||
else if (r < -MathUtils.PI) r += MathUtils.PI2;
|
||||
r *= rotateMix;
|
||||
float cos = MathUtils.Cos(r), sin = MathUtils.Sin(r);
|
||||
bone.a = cos * a - sin * c;
|
||||
bone.b = cos * b - sin * d;
|
||||
bone.c = sin * a + cos * c;
|
||||
bone.d = sin * b + cos * d;
|
||||
}
|
||||
|
||||
if (scaleMix > 0) {
|
||||
float bs = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c);
|
||||
float ts = (float)Math.Sqrt(target.a * target.a + target.c * target.c);
|
||||
float s = bs > 0.00001f ? (bs + (ts - bs + offsetScaleX) * scaleMix) / bs : 0;
|
||||
bone.a *= s;
|
||||
bone.c *= s;
|
||||
bs = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d);
|
||||
ts = (float)Math.Sqrt(target.b * target.b + target.d * target.d);
|
||||
s = bs > 0.00001f ? (bs + (ts - bs + offsetScaleY) * scaleMix) / bs : 0;
|
||||
bone.b *= s;
|
||||
bone.d *= s;
|
||||
}
|
||||
|
||||
if (shearMix > 0) {
|
||||
float b = bone.b, d = bone.d;
|
||||
float by = MathUtils.Atan2(d, b);
|
||||
float r = MathUtils.Atan2(target.d, target.b) - MathUtils.Atan2(target.c, target.a) - (by - MathUtils.Atan2(bone.c, bone.a));
|
||||
if (r > MathUtils.PI)
|
||||
r -= MathUtils.PI2;
|
||||
else if (r < -MathUtils.PI) r += MathUtils.PI2;
|
||||
r = by + (r + offsetShearY * MathUtils.degRad) * shearMix;
|
||||
float s = (float)Math.Sqrt(b * b + d * d);
|
||||
bone.b = MathUtils.Cos(r) * s;
|
||||
bone.d = MathUtils.Sin(r) * s;
|
||||
}
|
||||
|
||||
float translateMix = this.translateMix;
|
||||
if (translateMix > 0) {
|
||||
Bone bone = this.bone;
|
||||
float tx, ty;
|
||||
target.LocalToWorld(x, y, out tx, out ty);
|
||||
target.LocalToWorld(offsetX, offsetY, out tx, out ty);
|
||||
bone.worldX += (tx - bone.worldX) * translateMix;
|
||||
bone.worldY += (ty - bone.worldY) * translateMix;
|
||||
}
|
||||
|
||||
@ -36,15 +36,23 @@ namespace Spine {
|
||||
public class TransformConstraintData {
|
||||
internal String name;
|
||||
internal BoneData bone, target;
|
||||
internal float translateMix;
|
||||
internal float x, y;
|
||||
internal float rotateMix, translateMix, scaleMix, shearMix;
|
||||
internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY;
|
||||
|
||||
public String Name { get { return name; } }
|
||||
public BoneData Bone { get { return bone; } set { bone = value; } }
|
||||
public BoneData Target { get { return target; } set { target = value; } }
|
||||
public float RotateMix { get { return rotateMix; } set { rotateMix = value; } }
|
||||
public float TranslateMix { get { return translateMix; } set { translateMix = value; } }
|
||||
public float X { get { return x; } set { x = value; } }
|
||||
public float Y { get { return y; } set { y = value; } }
|
||||
public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } }
|
||||
public float ShearMix { get { return shearMix; } set { shearMix = value; } }
|
||||
|
||||
public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } }
|
||||
public float OffsetX { get { return offsetX; } set { offsetX = value; } }
|
||||
public float OffsetY { get { return offsetY; } set { offsetY = value; } }
|
||||
public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } }
|
||||
public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } }
|
||||
public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } }
|
||||
|
||||
public TransformConstraintData (String name) {
|
||||
if (name == null) throw new ArgumentNullException("name cannot be null.");
|
||||
|
||||
@ -14,7 +14,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f
|
||||
|
||||
## Spine version
|
||||
|
||||
spine-unity works with data exported from Spine 3.1.08. Updating spine-unity to [v3.2](https://trello.com/c/k7KtGdPW/76-update-runtimes-to-support-v3-2-shearing) is in progress.
|
||||
spine-unity works with data exported from the latest version of Spine.
|
||||
|
||||
spine-unity supports all Spine features.
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user