mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2025-12-20 17:26:01 +08:00
[csharp] Ported stretchy IK. See https://github.com/EsotericSoftware/spine-runtimes/issues/1142
This commit is contained in:
parent
4b8b4a4adf
commit
d5354cb6bf
@ -1210,9 +1210,9 @@ namespace Spine {
|
||||
}
|
||||
|
||||
public class IkConstraintTimeline : CurveTimeline {
|
||||
public const int ENTRIES = 3;
|
||||
private const int PREV_TIME = -3, PREV_MIX = -2, PREV_BEND_DIRECTION = -1;
|
||||
private const int MIX = 1, BEND_DIRECTION = 2;
|
||||
public const int ENTRIES = 4;
|
||||
private const int PREV_TIME = -4, PREV_MIX = -3, PREV_BEND_DIRECTION = -2, PREV_STRETCH = -1;
|
||||
private const int MIX = 1, BEND_DIRECTION = 2, STRETCH = 3;
|
||||
|
||||
internal int ikConstraintIndex;
|
||||
internal float[] frames;
|
||||
@ -1230,11 +1230,12 @@ namespace Spine {
|
||||
}
|
||||
|
||||
/// <summary>Sets the time, mix and bend direction of the specified keyframe.</summary>
|
||||
public void SetFrame (int frameIndex, float time, float mix, int bendDirection) {
|
||||
public void SetFrame (int frameIndex, float time, float mix, int bendDirection, bool stretch) {
|
||||
frameIndex *= ENTRIES;
|
||||
frames[frameIndex] = time;
|
||||
frames[frameIndex + MIX] = mix;
|
||||
frames[frameIndex + BEND_DIRECTION] = bendDirection;
|
||||
frames[frameIndex + STRETCH] = stretch ? 1 : 0;
|
||||
}
|
||||
|
||||
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
|
||||
@ -1245,10 +1246,12 @@ namespace Spine {
|
||||
case MixBlend.Setup:
|
||||
constraint.mix = constraint.data.mix;
|
||||
constraint.bendDirection = constraint.data.bendDirection;
|
||||
constraint.stretch = constraint.data.stretch;
|
||||
return;
|
||||
case MixBlend.First:
|
||||
constraint.mix += (constraint.data.mix - constraint.mix) * alpha;
|
||||
constraint.bendDirection = constraint.data.bendDirection;
|
||||
constraint.stretch = constraint.data.stretch;
|
||||
return;
|
||||
}
|
||||
return;
|
||||
@ -1257,11 +1260,19 @@ namespace Spine {
|
||||
if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
|
||||
if (blend == MixBlend.Setup) {
|
||||
constraint.mix = constraint.data.mix + (frames[frames.Length + PREV_MIX] - constraint.data.mix) * alpha;
|
||||
constraint.bendDirection = direction == MixDirection.Out ? constraint.data.bendDirection
|
||||
: (int)frames[frames.Length + PREV_BEND_DIRECTION];
|
||||
if (direction == MixDirection.Out) {
|
||||
constraint.bendDirection = constraint.data.bendDirection;
|
||||
constraint.stretch = constraint.data.stretch;
|
||||
} else {
|
||||
constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION];
|
||||
constraint.stretch = frames[frames.Length + PREV_STRETCH] != 0;
|
||||
}
|
||||
} else {
|
||||
constraint.mix += (frames[frames.Length + PREV_MIX] - constraint.mix) * alpha;
|
||||
if (direction == MixDirection.In) constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION];
|
||||
if (direction == MixDirection.In) {
|
||||
constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION];
|
||||
constraint.stretch = frames[frames.Length + PREV_STRETCH] != 0;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -35,14 +35,24 @@ namespace Spine {
|
||||
internal IkConstraintData data;
|
||||
internal ExposedList<Bone> bones = new ExposedList<Bone>();
|
||||
internal Bone target;
|
||||
internal float mix;
|
||||
internal int bendDirection;
|
||||
internal bool stretch;
|
||||
internal float mix;
|
||||
|
||||
public IkConstraintData Data { get { return data; } }
|
||||
public int Order { get { return data.order; } }
|
||||
public ExposedList<Bone> Bones { get { return bones; } }
|
||||
public Bone Target { get { return target; } set { target = value; } }
|
||||
public int BendDirection { get { return bendDirection; } set { bendDirection = value; } }
|
||||
|
||||
/// <summary>
|
||||
/// When true, if the target is out of range, the parent bone is scaled on the X axis to reach it.
|
||||
/// IF the parent bone has nonuniform scale, stretching is not applied.</summary>
|
||||
public bool Stretch {
|
||||
get { return stretch; }
|
||||
set { stretch = value; }
|
||||
}
|
||||
|
||||
public float Mix { get { return mix; } set { mix = value; } }
|
||||
|
||||
public IkConstraint (IkConstraintData data, Skeleton skeleton) {
|
||||
@ -51,6 +61,7 @@ namespace Spine {
|
||||
this.data = data;
|
||||
mix = data.mix;
|
||||
bendDirection = data.bendDirection;
|
||||
stretch = data.stretch;
|
||||
|
||||
bones = new ExposedList<Bone>(data.bones.Count);
|
||||
foreach (BoneData boneData in data.bones)
|
||||
@ -68,10 +79,10 @@ namespace Spine {
|
||||
ExposedList<Bone> bones = this.bones;
|
||||
switch (bones.Count) {
|
||||
case 1:
|
||||
Apply(bones.Items[0], target.worldX, target.worldY, mix);
|
||||
Apply(bones.Items[0], target.worldX, target.worldY, stretch, mix);
|
||||
break;
|
||||
case 2:
|
||||
Apply(bones.Items[0], bones.Items[1], target.worldX, target.worldY, bendDirection, mix);
|
||||
Apply(bones.Items[0], bones.Items[1], target.worldX, target.worldY, bendDirection, stretch, mix);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -82,7 +93,7 @@ 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) {
|
||||
static public void Apply (Bone bone, float targetX, float targetY, bool stretch, float alpha) {
|
||||
if (!bone.appliedValid) bone.UpdateAppliedTransform();
|
||||
Bone p = bone.parent;
|
||||
float id = 1 / (p.a * p.d - p.b * p.c);
|
||||
@ -93,14 +104,21 @@ namespace Spine {
|
||||
if (rotationIK > 180)
|
||||
rotationIK -= 360;
|
||||
else if (rotationIK < -180) rotationIK += 360;
|
||||
bone.UpdateWorldTransform(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, bone.ascaleX, bone.ascaleY, bone.ashearX,
|
||||
|
||||
float sx = bone.ascaleX;
|
||||
if (stretch) {
|
||||
float b = bone.data.length * sx, dd = (float)Math.Sqrt(tx * tx + ty * ty);
|
||||
if (dd > b && b > 0.0001f) sx *= (dd / b - 1) * alpha + 1;
|
||||
}
|
||||
|
||||
bone.UpdateWorldTransform(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, sx, bone.ascaleY, bone.ashearX,
|
||||
bone.ashearY);
|
||||
}
|
||||
|
||||
/// <summary>Adjusts the parent and child bone rotations so the tip of the child is as close to the target position as
|
||||
/// possible. The target is specified in the world coordinate system.</summary>
|
||||
/// <param name="child">A direct descendant of the parent bone.</param>
|
||||
static public void Apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, float alpha) {
|
||||
static public void Apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, bool stretch, float alpha) {
|
||||
if (alpha == 0) {
|
||||
child.UpdateWorldTransform ();
|
||||
return;
|
||||
@ -108,7 +126,7 @@ namespace Spine {
|
||||
//float px = parent.x, py = parent.y, psx = parent.scaleX, psy = parent.scaleY, csx = child.scaleX;
|
||||
if (!parent.appliedValid) parent.UpdateAppliedTransform();
|
||||
if (!child.appliedValid) child.UpdateAppliedTransform();
|
||||
float px = parent.ax, py = parent.ay, psx = parent.ascaleX, psy = parent.ascaleY, csx = child.ascaleX;
|
||||
float px = parent.ax, py = parent.ay, psx = parent.ascaleX, sx = psx, psy = parent.ascaleY, csx = child.ascaleX;
|
||||
int os1, os2, s2;
|
||||
if (psx < 0) {
|
||||
psx = -psx;
|
||||
@ -144,17 +162,20 @@ namespace Spine {
|
||||
c = pp.c;
|
||||
d = pp.d;
|
||||
float id = 1 / (a * d - b * c), x = targetX - pp.worldX, y = targetY - pp.worldY;
|
||||
float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py;
|
||||
float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py, dd = tx * tx + ty * ty;
|
||||
x = cwx - pp.worldX;
|
||||
y = cwy - pp.worldY;
|
||||
float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py;
|
||||
float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2;
|
||||
if (u) {
|
||||
l2 *= psx;
|
||||
float cos = (tx * tx + ty * ty - l1 * l1 - l2 * l2) / (2 * l1 * l2);
|
||||
float cos = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2);
|
||||
if (cos < -1)
|
||||
cos = -1;
|
||||
else if (cos > 1) cos = 1;
|
||||
else if (cos > 1) {
|
||||
cos = 1;
|
||||
if (stretch && l1 + l2 > 0.0001f) sx *= ((float)Math.Sqrt(dd) / (l1 + l2) - 1) * alpha + 1;
|
||||
}
|
||||
a2 = (float)Math.Acos(cos) * bendDir;
|
||||
a = l1 + l2 * cos;
|
||||
b = l2 * (float)Math.Sin(a2);
|
||||
@ -162,7 +183,7 @@ namespace Spine {
|
||||
} else {
|
||||
a = psx * l2;
|
||||
b = psy * l2;
|
||||
float aa = a * a, bb = b * b, dd = tx * tx + ty * ty, ta = (float)Math.Atan2(ty, tx);
|
||||
float aa = a * a, bb = b * b, ta = (float)Math.Atan2(ty, tx);
|
||||
c = bb * l1 * l1 + aa * dd - aa * bb;
|
||||
float c1 = -2 * bb * l1, c2 = bb - aa;
|
||||
d = c1 * c1 - 4 * c2 * c;
|
||||
@ -215,7 +236,7 @@ namespace Spine {
|
||||
if (a1 > 180)
|
||||
a1 -= 360;
|
||||
else if (a1 < -180) a1 += 360;
|
||||
parent.UpdateWorldTransform(px, py, rotation + a1 * alpha, parent.scaleX, parent.ascaleY, 0, 0);
|
||||
parent.UpdateWorldTransform(px, py, rotation + a1 * alpha, sx, parent.ascaleY, 0, 0);
|
||||
rotation = child.arotation;
|
||||
a2 = ((a2 + os) * MathUtils.RadDeg - child.ashearX) * s2 + os2 - rotation;
|
||||
if (a2 > 180)
|
||||
|
||||
@ -39,6 +39,7 @@ namespace Spine {
|
||||
internal List<BoneData> bones = new List<BoneData>();
|
||||
internal BoneData target;
|
||||
internal int bendDirection = 1;
|
||||
internal bool stretch;
|
||||
internal float mix = 1;
|
||||
|
||||
/// <summary>The IK constraint's name, which is unique within the skeleton.</summary>
|
||||
@ -68,6 +69,14 @@ namespace Spine {
|
||||
set { bendDirection = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When true, if the target is out of range, the parent bone is scaled on the X axis to reach it.
|
||||
/// If the bone has local nonuniform scale, stretching is not applied.</summary>
|
||||
public bool Stretch {
|
||||
get { return stretch; }
|
||||
set { stretch = value; }
|
||||
}
|
||||
|
||||
public float Mix { get { return mix; } set { mix = value; } }
|
||||
|
||||
public IkConstraintData (string name) {
|
||||
|
||||
@ -309,6 +309,7 @@ namespace Spine {
|
||||
for (int i = 0, n = ikConstraints.Count; i < n; i++) {
|
||||
IkConstraint constraint = ikConstraintsItems[i];
|
||||
constraint.bendDirection = constraint.data.bendDirection;
|
||||
constraint.stretch = constraint.data.stretch;
|
||||
constraint.mix = constraint.data.mix;
|
||||
}
|
||||
|
||||
|
||||
@ -210,6 +210,7 @@ namespace Spine {
|
||||
data.target = skeletonData.bones.Items[ReadVarint(input, true)];
|
||||
data.mix = ReadFloat(input);
|
||||
data.bendDirection = ReadSByte(input);
|
||||
data.stretch = ReadBoolean(input);
|
||||
skeletonData.ikConstraints.Add(data);
|
||||
}
|
||||
|
||||
@ -648,7 +649,7 @@ namespace Spine {
|
||||
IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount);
|
||||
timeline.ikConstraintIndex = index;
|
||||
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
|
||||
timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input));
|
||||
timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input), ReadBoolean(input));
|
||||
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
|
||||
}
|
||||
timelines.Add(timeline);
|
||||
|
||||
@ -182,6 +182,7 @@ namespace Spine {
|
||||
if (data.target == null) throw new Exception("Target bone not found: " + targetName);
|
||||
|
||||
data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1;
|
||||
data.stretch = GetBoolean(constraintMap, "stretch", false);
|
||||
data.mix = GetFloat(constraintMap, "mix", 1);
|
||||
|
||||
skeletonData.ikConstraints.Add(data);
|
||||
@ -597,7 +598,8 @@ namespace Spine {
|
||||
float time = (float)valueMap["time"];
|
||||
float mix = GetFloat(valueMap, "mix", 1);
|
||||
bool bendPositive = GetBoolean(valueMap, "bendPositive", true);
|
||||
timeline.SetFrame(frameIndex, time, mix, bendPositive ? 1 : -1);
|
||||
bool stretch = GetBoolean(valueMap, "stretch", false);
|
||||
timeline.SetFrame(frameIndex, time, mix, bendPositive ? 1 : -1, stretch);
|
||||
ReadCurve(valueMap, timeline, frameIndex);
|
||||
frameIndex++;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user