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:
John 2016-06-01 08:25:30 +08:00 committed by Nathan Sweet
parent 05d60e5377
commit f1406e2f85
12 changed files with 408 additions and 192 deletions

View File

@ -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.

View File

@ -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;
}
}
}

View File

@ -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) {

View File

@ -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; } }

View File

@ -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);
}
}
}

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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)];

View File

@ -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);

View File

@ -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;
}

View File

@ -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.");

View File

@ -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.