diff --git a/spine-cpp/spine-cpp/include/spine/IkConstraint.h b/spine-cpp/spine-cpp/include/spine/IkConstraint.h index 84920c49d..f2a935c9e 100644 --- a/spine-cpp/spine-cpp/include/spine/IkConstraint.h +++ b/spine-cpp/spine-cpp/include/spine/IkConstraint.h @@ -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; }; diff --git a/spine-cpp/spine-cpp/include/spine/IkConstraintData.h b/spine-cpp/spine-cpp/include/spine/IkConstraintData.h index 05c203c04..b11018545 100644 --- a/spine-cpp/spine-cpp/include/spine/IkConstraintData.h +++ b/spine-cpp/spine-cpp/include/spine/IkConstraintData.h @@ -71,6 +71,9 @@ namespace spine { float getMix(); void setMix(float inValue); + float getSoftness(); + void setSoftness(float inValue); + private: Vector _bones; BoneData* _target; @@ -79,6 +82,7 @@ namespace spine { bool _stretch; bool _uniform; float _mix; + float _softness; }; } diff --git a/spine-cpp/spine-cpp/include/spine/IkConstraintTimeline.h b/spine-cpp/spine-cpp/include/spine/IkConstraintTimeline.h index 8285acf00..e07a1b694 100644 --- a/spine-cpp/spine-cpp/include/spine/IkConstraintTimeline.h +++ b/spine-cpp/spine-cpp/include/spine/IkConstraintTimeline.h @@ -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; diff --git a/spine-cpp/spine-cpp/src/spine/IkConstraint.cpp b/spine-cpp/spine-cpp/src/spine/IkConstraint.cpp index 73fdb5354..977408190 100644 --- a/spine-cpp/spine-cpp/src/spine/IkConstraint.cpp +++ b/spine-cpp/spine-cpp/src/spine/IkConstraint.cpp @@ -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; } diff --git a/spine-cpp/spine-cpp/src/spine/IkConstraintData.cpp b/spine-cpp/spine-cpp/src/spine/IkConstraintData.cpp index df95b28bc..cf4329a48 100644 --- a/spine-cpp/spine-cpp/src/spine/IkConstraintData.cpp +++ b/spine-cpp/spine-cpp/src/spine/IkConstraintData.cpp @@ -44,7 +44,8 @@ IkConstraintData::IkConstraintData(const String &name) : _compress(false), _stretch(false), _uniform(false), - _mix(1) { + _mix(1), + _softness(0) { } Vector &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; +} + diff --git a/spine-cpp/spine-cpp/src/spine/IkConstraintTimeline.cpp b/spine-cpp/spine-cpp/src/spine/IkConstraintTimeline.cpp index a8b14e3b3..158819760 100644 --- a/spine-cpp/spine-cpp/src/spine/IkConstraintTimeline.cpp +++ b/spine-cpp/spine-cpp/src/spine/IkConstraintTimeline.cpp @@ -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; diff --git a/spine-cpp/spine-cpp/src/spine/Skeleton.cpp b/spine-cpp/spine-cpp/src/spine/Skeleton.cpp index aa2b65542..cac69f473 100644 --- a/spine-cpp/spine-cpp/src/spine/Skeleton.cpp +++ b/spine-cpp/spine-cpp/src/spine/Skeleton.cpp @@ -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) { diff --git a/spine-cpp/spine-cpp/src/spine/SkeletonBinary.cpp b/spine-cpp/spine-cpp/src/spine/SkeletonBinary.cpp index a70ab42d7..d8226253f 100644 --- a/spine-cpp/spine-cpp/src/spine/SkeletonBinary.cpp +++ b/spine-cpp/spine-cpp/src/spine/SkeletonBinary.cpp @@ -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); } diff --git a/spine-cpp/spine-cpp/src/spine/SkeletonJson.cpp b/spine-cpp/spine-cpp/src/spine/SkeletonJson.cpp index 042ab4a0f..efc75b032 100644 --- a/spine-cpp/spine-cpp/src/spine/SkeletonJson.cpp +++ b/spine-cpp/spine-cpp/src/spine/SkeletonJson.cpp @@ -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); }