mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-08 16:24:53 +08:00
[cpp] Added soft IK support. See #1383.
This commit is contained in:
parent
7a19650b5b
commit
58e2971d81
@ -56,7 +56,7 @@ public:
|
||||
/// 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.
|
||||
/// @param child A direct descendant of the parent bone.
|
||||
static void apply(Bone &parent, Bone &child, float targetX, float targetY, int bendDir, bool stretch, float alpha);
|
||||
static void apply(Bone &parent, Bone &child, float targetX, float targetY, int bendDir, bool stretch, float softness, float alpha);
|
||||
|
||||
IkConstraint(IkConstraintData &data, Skeleton &skeleton);
|
||||
|
||||
@ -91,6 +91,10 @@ public:
|
||||
|
||||
void setMix(float inValue);
|
||||
|
||||
float getSoftness();
|
||||
|
||||
void setSoftness(float inValue);
|
||||
|
||||
bool isActive();
|
||||
|
||||
void setActive(bool inValue);
|
||||
@ -102,6 +106,7 @@ private:
|
||||
bool _compress;
|
||||
bool _stretch;
|
||||
float _mix;
|
||||
float _softness;
|
||||
Bone *_target;
|
||||
bool _active;
|
||||
};
|
||||
|
||||
@ -71,6 +71,9 @@ namespace spine {
|
||||
float getMix();
|
||||
void setMix(float inValue);
|
||||
|
||||
float getSoftness();
|
||||
void setSoftness(float inValue);
|
||||
|
||||
private:
|
||||
Vector<BoneData*> _bones;
|
||||
BoneData* _target;
|
||||
@ -79,6 +82,7 @@ namespace spine {
|
||||
bool _stretch;
|
||||
bool _uniform;
|
||||
float _mix;
|
||||
float _softness;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -50,15 +50,17 @@ namespace spine {
|
||||
virtual int getPropertyId();
|
||||
|
||||
/// Sets the time, mix and bend direction of the specified keyframe.
|
||||
void setFrame (int frameIndex, float time, float mix, int bendDirection, bool compress, bool stretch);
|
||||
void setFrame (int frameIndex, float time, float mix, float softness, int bendDirection, bool compress, bool stretch);
|
||||
|
||||
private:
|
||||
static const int PREV_TIME;
|
||||
static const int PREV_MIX;
|
||||
static const int PREV_SOFTNESS;
|
||||
static const int PREV_BEND_DIRECTION;
|
||||
static const int PREV_COMPRESS;
|
||||
static const int PREV_STRETCH;
|
||||
static const int MIX;
|
||||
static const int SOFTNESS;
|
||||
static const int BEND_DIRECTION;
|
||||
static const int COMPRESS;
|
||||
static const int STRETCH;
|
||||
|
||||
@ -69,12 +69,13 @@ void IkConstraint::apply(Bone &bone, float targetX, float targetY, bool compress
|
||||
sy, bone._ashearX, bone._ashearY);
|
||||
}
|
||||
|
||||
void IkConstraint::apply(Bone &parent, Bone &child, float targetX, float targetY, int bendDir, bool stretch, float alpha) {
|
||||
void IkConstraint::apply(Bone &parent, Bone &child, float targetX, float targetY, int bendDir, bool stretch, float softness, float alpha) {
|
||||
float a, b, c, d;
|
||||
float px, py, psx, sx, psy;
|
||||
float cx, cy, csx, cwx, cwy;
|
||||
int o1, o2, s2, u;
|
||||
Bone *pp = parent.getParent();
|
||||
float tx, ty, dx, dy, dd, l1, l2, a1, a2, r;
|
||||
float tx, ty, dx, dy, dd, l1, l2, a1, a2, r, td, sd, p;
|
||||
float id, x, y;
|
||||
if (alpha == 0) {
|
||||
child.updateWorldTransform();
|
||||
@ -117,18 +118,37 @@ void IkConstraint::apply(Bone &parent, Bone &child, float targetX, float targetY
|
||||
cwx = parent._a * cx + parent._b * cy + parent._worldX;
|
||||
cwy = parent._c * cx + parent._d * cy + parent._worldY;
|
||||
}
|
||||
id = 1 / (pp->_a * pp->_d - pp->_b * pp->_c);
|
||||
x = targetX - pp->_worldX;
|
||||
y = targetY - pp->_worldY;
|
||||
tx = (x * pp->_d - y * pp->_b) * id - px;
|
||||
ty = (y * pp->_a - x * pp->_c) * id - py;
|
||||
dd = tx * tx + ty * ty;
|
||||
a = pp->_a;
|
||||
b = pp->_b;
|
||||
c = pp->_c;
|
||||
d = pp->_d;
|
||||
id = 1 / (a * d - b * c);
|
||||
x = cwx - pp->_worldX;
|
||||
y = cwy - pp->_worldY;
|
||||
dx = (x * pp->_d - y * pp->_b) * id - px;
|
||||
dy = (y * pp->_a - x * pp->_c) * id - py;
|
||||
dx = (x * d - y * b) * id - px;
|
||||
dy = (y * a - x * c) * id - py;
|
||||
l1 = MathUtil::sqrt(dx * dx + dy * dy);
|
||||
l2 = child.getData().getLength() * csx;
|
||||
l2 = child._data.getLength() * csx, a1, a2;
|
||||
if (l1 < 0.0001) {
|
||||
apply(parent, targetX, targetY, false, stretch, false, alpha);
|
||||
child.updateWorldTransform(cx, cy, 0, child._ascaleX, child._ascaleY, child._ashearX, child._ashearY);
|
||||
return;
|
||||
}
|
||||
x = targetX - pp->_worldX;
|
||||
y = targetY - pp->_worldY;
|
||||
tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py;
|
||||
dd = tx * tx + ty * ty;
|
||||
if (softness != 0) {
|
||||
softness *= psx * (csx + 1) / 2;
|
||||
td = MathUtil::sqrt(dd), sd = td - l1 - l2 * psx + softness;
|
||||
if (sd > 0) {
|
||||
p = MathUtil::min(1.0f, sd / (softness * 2)) - 1;
|
||||
p = (sd - softness * (1 - p * p)) / td;
|
||||
tx -= p * tx;
|
||||
ty -= p * ty;
|
||||
dd = tx * tx + ty * ty;
|
||||
}
|
||||
}
|
||||
if (u) {
|
||||
float cosine, a, b;
|
||||
l2 *= psx;
|
||||
@ -136,7 +156,7 @@ void IkConstraint::apply(Bone &parent, Bone &child, float targetX, float targetY
|
||||
if (cosine < -1) cosine = -1;
|
||||
else if (cosine > 1) {
|
||||
cosine = 1;
|
||||
if (stretch && l1 + l2 > 0.0001f) sx *= (MathUtil::sqrt(dd) / (l1 + l2) - 1) * alpha + 1;
|
||||
if (stretch) sx *= (MathUtil::sqrt(dd) / (l1 + l2) - 1) * alpha + 1;
|
||||
}
|
||||
a2 = MathUtil::acos(cosine) * bendDir;
|
||||
a = l1 + l2 * cosine;
|
||||
@ -213,6 +233,7 @@ IkConstraint::IkConstraint(IkConstraintData &data, Skeleton &skeleton) : Updatab
|
||||
_compress(data.getCompress()),
|
||||
_stretch(data.getStretch()),
|
||||
_mix(data.getMix()),
|
||||
_softness(data.getSoftness()),
|
||||
_target(skeleton.findBone(
|
||||
data.getTarget()->getName())),
|
||||
_active(false) {
|
||||
@ -238,7 +259,7 @@ void IkConstraint::update() {
|
||||
case 2: {
|
||||
Bone *bone0 = _bones[0];
|
||||
Bone *bone1 = _bones[1];
|
||||
apply(*bone0, *bone1, _target->getWorldX(), _target->getWorldY(), _bendDirection, _stretch, _mix);
|
||||
apply(*bone0, *bone1, _target->getWorldX(), _target->getWorldY(), _bendDirection, _stretch, _softness, _mix);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@ -44,7 +44,8 @@ IkConstraintData::IkConstraintData(const String &name) :
|
||||
_compress(false),
|
||||
_stretch(false),
|
||||
_uniform(false),
|
||||
_mix(1) {
|
||||
_mix(1),
|
||||
_softness(0) {
|
||||
}
|
||||
|
||||
Vector<BoneData *> &IkConstraintData::getBones() {
|
||||
@ -99,3 +100,12 @@ bool IkConstraintData::getUniform() {
|
||||
void IkConstraintData::setUniform(bool inValue) {
|
||||
_uniform = inValue;
|
||||
}
|
||||
|
||||
float IkConstraintData::getSoftness() {
|
||||
return _softness;
|
||||
}
|
||||
|
||||
void IkConstraintData::setSoftness(float inValue) {
|
||||
_softness = inValue;
|
||||
}
|
||||
|
||||
|
||||
@ -47,16 +47,18 @@ using namespace spine;
|
||||
|
||||
RTTI_IMPL(IkConstraintTimeline, CurveTimeline)
|
||||
|
||||
const int IkConstraintTimeline::ENTRIES = 5;
|
||||
const int IkConstraintTimeline::PREV_TIME = -5;
|
||||
const int IkConstraintTimeline::PREV_MIX = -4;
|
||||
const int IkConstraintTimeline::ENTRIES = 6;
|
||||
const int IkConstraintTimeline::PREV_TIME = -6;
|
||||
const int IkConstraintTimeline::PREV_MIX = -5;
|
||||
const int IkConstraintTimeline::PREV_SOFTNESS = -4;
|
||||
const int IkConstraintTimeline::PREV_BEND_DIRECTION = -3;
|
||||
const int IkConstraintTimeline::PREV_COMPRESS = -2;
|
||||
const int IkConstraintTimeline::PREV_STRETCH = -1;
|
||||
const int IkConstraintTimeline::MIX = 1;
|
||||
const int IkConstraintTimeline::BEND_DIRECTION = 2;
|
||||
const int IkConstraintTimeline::COMPRESS = 3;
|
||||
const int IkConstraintTimeline::STRETCH = 4;
|
||||
const int IkConstraintTimeline::SOFTNESS = 2;
|
||||
const int IkConstraintTimeline::BEND_DIRECTION = 3;
|
||||
const int IkConstraintTimeline::COMPRESS = 4;
|
||||
const int IkConstraintTimeline::STRETCH = 5;
|
||||
|
||||
IkConstraintTimeline::IkConstraintTimeline(int frameCount) : CurveTimeline(frameCount), _ikConstraintIndex(0) {
|
||||
_frames.setSize(frameCount * ENTRIES, 0);
|
||||
@ -75,12 +77,14 @@ void IkConstraintTimeline::apply(Skeleton &skeleton, float lastTime, float time,
|
||||
switch (blend) {
|
||||
case MixBlend_Setup:
|
||||
constraint._mix = constraint._data._mix;
|
||||
constraint._softness = constraint._data._softness;
|
||||
constraint._bendDirection = constraint._data._bendDirection;
|
||||
constraint._compress = constraint._data._compress;
|
||||
constraint._stretch = constraint._data._stretch;
|
||||
return;
|
||||
case MixBlend_First:
|
||||
constraint._mix += (constraint._data._mix - constraint._mix) * alpha;
|
||||
constraint._softness += (constraint._data._softness - constraint._softness) * alpha;
|
||||
constraint._bendDirection = constraint._data._bendDirection;
|
||||
constraint._compress = constraint._data._compress;
|
||||
constraint._stretch = constraint._data._stretch;
|
||||
@ -95,6 +99,8 @@ void IkConstraintTimeline::apply(Skeleton &skeleton, float lastTime, float time,
|
||||
if (blend == MixBlend_Setup) {
|
||||
constraint._mix =
|
||||
constraint._data._mix + (_frames[_frames.size() + PREV_MIX] - constraint._data._mix) * alpha;
|
||||
constraint._softness = constraint._data._softness
|
||||
+ (_frames[_frames.size() + PREV_SOFTNESS] - constraint._data._softness) * alpha;
|
||||
if (direction == MixDirection_Out) {
|
||||
constraint._bendDirection = constraint._data._bendDirection;
|
||||
constraint._compress = constraint._data._compress;
|
||||
@ -106,6 +112,7 @@ void IkConstraintTimeline::apply(Skeleton &skeleton, float lastTime, float time,
|
||||
}
|
||||
} else {
|
||||
constraint._mix += (_frames[_frames.size() + PREV_MIX] - constraint._mix) * alpha;
|
||||
constraint._softness += (_frames[_frames.size() + PREV_SOFTNESS] - constraint._softness) * alpha;
|
||||
if (direction == MixDirection_In) {
|
||||
constraint._bendDirection = (int) _frames[_frames.size() + PREV_BEND_DIRECTION];
|
||||
constraint._compress = _frames[_frames.size() + PREV_COMPRESS] != 0;
|
||||
@ -118,6 +125,7 @@ void IkConstraintTimeline::apply(Skeleton &skeleton, float lastTime, float time,
|
||||
// Interpolate between the previous frame and the current frame.
|
||||
int frame = Animation::binarySearch(_frames, time, ENTRIES);
|
||||
float mix = _frames[frame + PREV_MIX];
|
||||
float softness = _frames[frame + PREV_SOFTNESS];
|
||||
float frameTime = _frames[frame];
|
||||
float percent = getCurvePercent(frame / ENTRIES - 1,
|
||||
1 - (time - frameTime) / (_frames[frame + PREV_TIME] - frameTime));
|
||||
@ -125,6 +133,8 @@ void IkConstraintTimeline::apply(Skeleton &skeleton, float lastTime, float time,
|
||||
if (blend == MixBlend_Setup) {
|
||||
constraint._mix =
|
||||
constraint._data._mix + (mix + (_frames[frame + MIX] - mix) * percent - constraint._data._mix) * alpha;
|
||||
constraint._softness = constraint._data._softness
|
||||
+ (softness + (_frames[frame + SOFTNESS] - softness) * percent - constraint._data._softness) * alpha;
|
||||
if (direction == MixDirection_Out) {
|
||||
constraint._bendDirection = constraint._data._bendDirection;
|
||||
constraint._compress = constraint._data._compress;
|
||||
@ -136,6 +146,7 @@ void IkConstraintTimeline::apply(Skeleton &skeleton, float lastTime, float time,
|
||||
}
|
||||
} else {
|
||||
constraint._mix += (mix + (_frames[frame + MIX] - mix) * percent - constraint._mix) * alpha;
|
||||
constraint._softness += (softness + (_frames[frame + SOFTNESS] - softness) * percent - constraint._softness) * alpha;
|
||||
if (direction == MixDirection_In) {
|
||||
constraint._bendDirection = (int) _frames[frame + PREV_BEND_DIRECTION];
|
||||
constraint._compress = _frames[frame + PREV_COMPRESS] != 0;
|
||||
@ -148,10 +159,11 @@ int IkConstraintTimeline::getPropertyId() {
|
||||
return ((int) TimelineType_IkConstraint << 24) + _ikConstraintIndex;
|
||||
}
|
||||
|
||||
void IkConstraintTimeline::setFrame(int frameIndex, float time, float mix, int bendDirection, bool compress, bool stretch) {
|
||||
void IkConstraintTimeline::setFrame(int frameIndex, float time, float mix, float softness, int bendDirection, bool compress, bool stretch) {
|
||||
frameIndex *= ENTRIES;
|
||||
_frames[frameIndex] = time;
|
||||
_frames[frameIndex + MIX] = mix;
|
||||
_frames[frameIndex + SOFTNESS] = softness;
|
||||
_frames[frameIndex + BEND_DIRECTION] = (float)bendDirection;
|
||||
_frames[frameIndex + COMPRESS] = compress ? 1 : 0;
|
||||
_frames[frameIndex + STRETCH] = stretch ? 1 : 0;
|
||||
|
||||
@ -247,6 +247,7 @@ void Skeleton::setBonesToSetupPose() {
|
||||
constraint._compress = constraint._data._compress;
|
||||
constraint._stretch = constraint._data._stretch;
|
||||
constraint._mix = constraint._data._mix;
|
||||
constraint._softness = constraint._data._softness;
|
||||
}
|
||||
|
||||
for (size_t i = 0, n = _transformConstraints.size(); i < n; ++i) {
|
||||
|
||||
@ -209,6 +209,7 @@ SkeletonData *SkeletonBinary::readSkeletonData(const unsigned char *binary, cons
|
||||
}
|
||||
data->_target = skeletonData->_bones[readVarint(input, true)];
|
||||
data->_mix = readFloat(input);
|
||||
data->_softness = readFloat(input);
|
||||
data->_bendDirection = readSByte(input);
|
||||
data->_compress = readBoolean(input);
|
||||
data->_stretch = readBoolean(input);
|
||||
@ -809,10 +810,11 @@ Animation *SkeletonBinary::readAnimation(const String &name, DataInput *input, S
|
||||
for (int frameIndex = 0; frameIndex < frameCount; ++frameIndex) {
|
||||
float time = readFloat(input);
|
||||
float mix = readFloat(input);
|
||||
float softness = readFloat(input);
|
||||
signed char bendDirection = readSByte(input);
|
||||
bool compress = readBoolean(input);
|
||||
bool stretch = readBoolean(input);
|
||||
timeline->setFrame(frameIndex, time, mix, bendDirection, compress, stretch);
|
||||
timeline->setFrame(frameIndex, time, mix, softness, bendDirection, compress, stretch);
|
||||
if (frameIndex < frameCount - 1) {
|
||||
readCurve(input, frameIndex, timeline);
|
||||
}
|
||||
|
||||
@ -292,6 +292,7 @@ SkeletonData *SkeletonJson::readSkeletonData(const char *json) {
|
||||
}
|
||||
|
||||
data->_mix = Json::getFloat(constraintMap, "mix", 1);
|
||||
data->_softness = Json::getFloat(constraintMap, "softness", 0);
|
||||
data->_bendDirection = Json::getInt(constraintMap, "bendPositive", 1) ? 1 : -1;
|
||||
data->_compress = Json::getInt(constraintMap, "compress", 0) ? true: false;
|
||||
data->_stretch = Json::getInt(constraintMap, "stretch", 0) ? true: false;
|
||||
@ -1004,7 +1005,7 @@ Animation *SkeletonJson::readAnimation(Json *root, SkeletonData *skeletonData) {
|
||||
}
|
||||
}
|
||||
for (valueMap = constraintMap->_child, frameIndex = 0; valueMap; valueMap = valueMap->_next, ++frameIndex) {
|
||||
timeline->setFrame(frameIndex, Json::getFloat(valueMap, "time", 0), Json::getFloat(valueMap, "mix", 1),
|
||||
timeline->setFrame(frameIndex, Json::getFloat(valueMap, "time", 0), Json::getFloat(valueMap, "mix", 1), Json::getFloat(valueMap, "softness", 0),
|
||||
Json::getInt(valueMap, "bendPositive", 1) ? 1 : -1, Json::getInt(valueMap, "compress", 0) ? true : false, Json::getInt(valueMap, "stretch", 0) ? true : false);
|
||||
readCurve(valueMap, timeline, frameIndex);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user