blob: 85063c7b7f697d10cef9cd0db7a90dc3155e479a [file] [log] [blame]
#include "rive/constraints/ik_constraint.hpp"
#include "rive/bones/bone.hpp"
#include "rive/artboard.hpp"
#include <algorithm>
#include <math.h> // M_PI
using namespace rive;
StatusCode IKConstraint::onAddedClean(CoreContext* context)
{
if (!parent()->is<Bone>())
{
return StatusCode::InvalidObject;
}
auto boneCount = parentBoneCount();
auto bone = parent()->as<Bone>();
std::vector<Bone*> bones;
bones.push_back(bone);
// Get the reverse FK chain of bones (from tip up).
while (bone->parent()->is<Bone>() && boneCount > 0)
{
boneCount--;
bone = bone->parent()->as<Bone>();
bone->addPeerConstraint(this);
bones.push_back(bone);
}
int numBones = (int)bones.size();
m_FkChain.resize(numBones);
// Now put them in FK order (top to bottom).
int idx = 0;
for (auto boneItr = bones.rbegin(); boneItr != bones.rend(); ++boneItr)
{
BoneChainLink& link = m_FkChain[idx];
link.index = idx++;
link.bone = *boneItr;
link.angle = 0.0f;
}
// Make sure all of the first level children of each bone depend on the
// tip (constrainedComponent).
auto tip = parent()->as<Bone>();
auto artboard = static_cast<Artboard*>(context);
// Find all children of this bone (we don't directly build up
// hierarchy at runtime, so we have to traverse everything and check
// parents).
for (auto core : artboard->objects())
{
if (core == nullptr || !core->is<TransformComponent>())
{
continue;
}
auto transformComponent = core->as<TransformComponent>();
for (int i = 1; i < numBones; i++)
{
auto bone = bones[i];
if (transformComponent->parent() == bone &&
std::find(bones.begin(), bones.end(), transformComponent) ==
bones.end())
{
tip->addDependent(transformComponent);
}
}
}
return Super::onAddedClean(context);
}
void IKConstraint::markConstraintDirty()
{
Super::markConstraintDirty();
// We automatically propagate dirt to the parent constrained bone, but we
// also need to make sure the other bones we influence above it rebuild
// their transforms.
for (int i = 0, length = (int)m_FkChain.size() - 1; i < length; i++)
{
m_FkChain[i].bone->markTransformDirty();
}
}
void IKConstraint::solve1(BoneChainLink* fk1,
const Vec2D& worldTargetTranslation)
{
Mat2D iworld = fk1->parentWorldInverse;
Vec2D pA;
fk1->bone->worldTranslation(pA);
Vec2D pBT(worldTargetTranslation);
// To target in worldspace
Vec2D toTarget;
Vec2D::subtract(toTarget, pBT, pA);
// Note this is directional, hence not transformMat2d
Vec2D toTargetLocal;
Vec2D::transformDir(toTargetLocal, toTarget, iworld);
float r = std::atan2(toTargetLocal[1], toTargetLocal[0]);
constrainRotation(*fk1, r);
fk1->angle = r;
}
void IKConstraint::solve2(BoneChainLink* fk1,
BoneChainLink* fk2,
const Vec2D& worldTargetTranslation)
{
Bone* b1 = fk1->bone;
Bone* b2 = fk2->bone;
BoneChainLink* firstChild = &(m_FkChain[fk1->index + 1]);
const Mat2D& iworld = fk1->parentWorldInverse;
Vec2D pA, pC, pB;
b1->worldTranslation(pA);
firstChild->bone->worldTranslation(pC);
b2->tipWorldTranslation(pB);
Vec2D pBT(worldTargetTranslation);
Vec2D::transform(pA, pA, iworld);
Vec2D::transform(pC, pC, iworld);
Vec2D::transform(pB, pB, iworld);
Vec2D::transform(pBT, pBT, iworld);
// http://mathworld.wolfram.com/LawofCosines.html
Vec2D av, bv, cv;
Vec2D::subtract(av, pB, pC);
float a = Vec2D::length(av);
Vec2D::subtract(bv, pC, pA);
float b = Vec2D::length(bv);
Vec2D::subtract(cv, pBT, pA);
float c = Vec2D::length(cv);
float A = std::acos(std::max(
-1.0f, std::min(1.0f, (-a * a + b * b + c * c) / (2.0f * b * c))));
float C = std::acos(std::max(
-1.0f, std::min(1.0f, (a * a + b * b - c * c) / (2.0f * a * b))));
float r1, r2;
if (b2->parent() != b1)
{
BoneChainLink& secondChild = m_FkChain[fk1->index + 2];
const Mat2D& secondChildWorldInverse = secondChild.parentWorldInverse;
firstChild->bone->worldTranslation(pC);
b2->tipWorldTranslation(pB);
Vec2D avec;
Vec2D::subtract(avec, pB, pC);
Vec2D avLocal;
Vec2D::transformDir(avLocal, avec, secondChildWorldInverse);
float angleCorrection = -std::atan2(avLocal[1], avLocal[0]);
if (invertDirection())
{
r1 = std::atan2(cv[1], cv[0]) - A;
r2 = -C + M_PI + angleCorrection;
}
else
{
r1 = A + std::atan2(cv[1], cv[0]);
r2 = C - M_PI + angleCorrection;
}
}
else if (invertDirection())
{
r1 = std::atan2(cv[1], cv[0]) - A;
r2 = -C + M_PI;
}
else
{
r1 = A + std::atan2(cv[1], cv[0]);
r2 = C - M_PI;
}
constrainRotation(*fk1, r1);
constrainRotation(*firstChild, r2);
if (firstChild != fk2)
{
Bone* bone = fk2->bone;
Mat2D::multiply(bone->mutableWorldTransform(),
getParentWorld(*bone),
bone->transform());
}
// Simple storage, need this for interpolation.
fk1->angle = r1;
firstChild->angle = r2;
}
void IKConstraint::constrainRotation(BoneChainLink& fk, float rotation)
{
Bone* bone = fk.bone;
const Mat2D& parentWorld = getParentWorld(*bone);
Mat2D& transform = bone->mutableTransform();
TransformComponents& c = fk.transformComponents;
if (rotation == 0.0f)
{
Mat2D::identity(transform);
}
else
{
Mat2D::fromRotation(transform, rotation);
}
// Translate
transform[4] = c.x();
transform[5] = c.y();
// Scale
float scaleX = c.scaleX();
float scaleY = c.scaleY();
transform[0] *= scaleX;
transform[1] *= scaleX;
transform[2] *= scaleY;
transform[3] *= scaleY;
// Skew
const float skew = c.skew();
if (skew != 0.0f)
{
transform[2] = transform[0] * skew + transform[2];
transform[3] = transform[1] * skew + transform[3];
}
Mat2D::multiply(bone->mutableWorldTransform(), parentWorld, transform);
}
void IKConstraint::constrain(TransformComponent* component)
{
if (m_Target == nullptr)
{
return;
}
Vec2D worldTargetTranslation;
m_Target->worldTranslation(worldTargetTranslation);
// Decompose the chain.
for (BoneChainLink& item : m_FkChain)
{
auto bone = item.bone;
const Mat2D& parentWorld = getParentWorld(*bone);
Mat2D::invert(item.parentWorldInverse, parentWorld);
Mat2D& boneTransform = bone->mutableTransform();
Mat2D::multiply(
boneTransform, item.parentWorldInverse, bone->worldTransform());
Mat2D::decompose(item.transformComponents, boneTransform);
}
int count = (int)m_FkChain.size();
switch (count)
{
case 1:
solve1(&m_FkChain[0], worldTargetTranslation);
break;
case 2:
solve2(&m_FkChain[0], &m_FkChain[1], worldTargetTranslation);
break;
default:
{
auto last = count - 1;
BoneChainLink* tip = &m_FkChain[last];
for (int i = 0; i < last; i++)
{
BoneChainLink* item = &m_FkChain[i];
solve2(item, tip, worldTargetTranslation);
for (int j = item->index + 1, end = m_FkChain.size() - 1;
j < end;
j++)
{
BoneChainLink& fk = m_FkChain[j];
Mat2D::invert(fk.parentWorldInverse,
getParentWorld(*fk.bone));
}
}
break;
}
}
// At the end, mix the FK angle with the IK angle by strength
if (strength() != 1.0f)
{
for (BoneChainLink& fk : m_FkChain)
{
float fromAngle =
std::fmod(fk.transformComponents.rotation(), (float)M_PI * 2);
float toAngle = std::fmod(fk.angle, (float)M_PI * 2);
float diff = toAngle - fromAngle;
if (diff > M_PI)
{
diff -= M_PI * 2;
}
else if (diff < -M_PI)
{
diff += M_PI * 2;
}
float angle = fromAngle + diff * strength();
constrainRotation(fk, angle);
}
}
}