Fix ik issue. Fixes rive-app/rive#3313
diff --git a/include/rive/constraints/ik_constraint.hpp b/include/rive/constraints/ik_constraint.hpp index 300d0cf..c6abd1c 100644 --- a/include/rive/constraints/ik_constraint.hpp +++ b/include/rive/constraints/ik_constraint.hpp
@@ -27,6 +27,7 @@ void markConstraintDirty() override; StatusCode onAddedClean(CoreContext* context) override; void constrain(TransformComponent* component) override; + void buildDependencies() override; }; } // namespace rive
diff --git a/src/constraints/ik_constraint.cpp b/src/constraints/ik_constraint.cpp index 9348d12..3400a5e 100644 --- a/src/constraints/ik_constraint.cpp +++ b/src/constraints/ik_constraint.cpp
@@ -6,6 +6,17 @@ using namespace rive; +void IKConstraint::buildDependencies() { + Super::buildDependencies(); + + // IK Constraint needs to depend on the target so that world transform + // changes can propagate to the bones (and they can be reset before IK + // runs). + if (m_Target != nullptr) { + m_Target->addDependent(this); + } +} + StatusCode IKConstraint::onAddedClean(CoreContext* context) { if (!parent()->is<Bone>()) { return StatusCode::InvalidObject; @@ -104,18 +115,14 @@ b2->tipWorldTranslation(pB); Vec2D pBT(worldTargetTranslation); - pA = iworld * pA; - pC = iworld * pC; - pB = iworld * pB; + pA = iworld * pA; + pC = iworld * pC; + pB = iworld * pB; pBT = iworld * pBT; // http://mathworld.wolfram.com/LawofCosines.html - Vec2D av = pB - pC, - bv = pC - pA, - cv = pBT - pA; - float a = av.length(), - b = bv.length(), - c = cv.length(); + Vec2D av = pB - pC, bv = pC - pA, cv = pBT - pA; + float a = av.length(), b = bv.length(), c = cv.length(); float A = std::acos(std::max( -1.0f, std::min(1.0f, (-a * a + b * b + c * c) / (2.0f * b * c)))); @@ -153,7 +160,8 @@ constrainRotation(*firstChild, r2); if (firstChild != fk2) { Bone* bone = fk2->bone; - bone->mutableWorldTransform() = getParentWorld(*bone) * bone->transform(); + bone->mutableWorldTransform() = + getParentWorld(*bone) * bone->transform(); } // Simple storage, need this for interpolation.
diff --git a/test/ik_test.cpp b/test/ik_test.cpp index ab5eb14..fc4f08a 100644 --- a/test/ik_test.cpp +++ b/test/ik_test.cpp
@@ -79,3 +79,79 @@ 240.1275634765625f, 225.07647705078125f))); } + +TEST_CASE("ik keeps working after a lot of iterations", "[file]") { + RiveFileReader reader("../../test/assets/two_bone_ik.riv"); + auto artboard = reader.file()->artboard(); + + REQUIRE(artboard->find<rive::Shape>("circle a") != nullptr); + auto circleA = artboard->find<rive::Shape>("circle a"); + + REQUIRE(artboard->find<rive::Shape>("circle b") != nullptr); + auto circleB = artboard->find<rive::Shape>("circle b"); + + REQUIRE(artboard->find<rive::Bone>("a") != nullptr); + auto boneA = artboard->find<rive::Bone>("a"); + + REQUIRE(artboard->find<rive::Bone>("b") != nullptr); + auto boneB = artboard->find<rive::Bone>("b"); + + REQUIRE(artboard->find<rive::Node>("target") != nullptr); + auto target = artboard->find<rive::Node>("target"); + + REQUIRE(artboard->animation("Animation 1") != nullptr); + auto animation = artboard->animation("Animation 1"); + + // Make sure dependency structure is correct. Important thing here is to + // ensure that circle a is dependent upon the tip of the ik chain (bone b). + // circle b is a child of bone b so it'll be there anyway, but may as well + // validate. + REQUIRE(std::find(boneB->dependents().begin(), + boneB->dependents().end(), + circleA) != boneB->dependents().end()); + REQUIRE(std::find(boneB->dependents().begin(), + boneB->dependents().end(), + circleB) != boneB->dependents().end()); + + for (int i = 0; i < 1000; i++) { + animation->apply(artboard, 0.0f, 1.0f); + artboard->advance(0.0f); + REQUIRE(target->x() == 296.0f); + REQUIRE(target->y() == 202.0f); + REQUIRE(aboutEqual(boneA->worldTransform(), + rive::Mat2D(0.11632211506366729736328125f, + -0.993211567401885986328125f, + 0.993211567401885986328125f, + 0.11632211506366729736328125f, + 26.015254974365234375f, + 475.2149658203125f))); + + REQUIRE(aboutEqual(boneB->worldTransform(), + rive::Mat2D(0.974071562290191650390625f, + 0.2262403070926666259765625f, + -0.2262403070926666259765625f, + 0.974071562290191650390625f, + 64.31568145751953125f, + 148.1883544921875f))); + + animation->apply(artboard, 1.0f, 1.0f); + artboard->advance(0.0f); + REQUIRE(target->x() == 450.0f); + REQUIRE(target->y() == 337.0f); + REQUIRE(aboutEqual(boneA->worldTransform(), + rive::Mat2D(0.650279819965362548828125f, + -0.7596948146820068359375f, + 0.7596948146820068359375f, + 0.650279819965362548828125f, + 26.015254974365234375f, + 475.2149658203125f))); + + REQUIRE(aboutEqual(boneB->worldTransform(), + rive::Mat2D(0.8823678493499755859375f, + 0.470560371875762939453125f, + -0.47056043148040771484375f, + 0.882367908954620361328125f, + 240.1275634765625f, + 225.07647705078125f))); + } +} \ No newline at end of file