[cpp] Added soft IK support. See #1383.

This commit is contained in:
badlogic 2019-06-19 16:42:58 +02:00
parent 7a19650b5b
commit 58e2971d81
9 changed files with 83 additions and 25 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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