overte/libraries/animation/src/AnimSplineIK.cpp
2019-02-20 17:59:45 -08:00

473 lines
20 KiB
C++

//
// AnimSplineIK.cpp
//
// Created by Angus Antley on 1/7/19.
// Copyright (c) 2019 High Fidelity, Inc. All rights reserved.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "AnimSplineIK.h"
#include "AnimationLogging.h"
#include "CubicHermiteSpline.h"
#include <DebugDraw.h>
#include "AnimUtil.h"
static const float FRAMES_PER_SECOND = 30.0f;
AnimSplineIK::AnimSplineIK(const QString& id, float alpha, bool enabled, float interpDuration,
const QString& baseJointName,
const QString& midJointName,
const QString& tipJointName,
const QString& basePositionVar,
const QString& baseRotationVar,
const QString& midPositionVar,
const QString& midRotationVar,
const QString& tipPositionVar,
const QString& tipRotationVar,
const QString& alphaVar,
const QString& enabledVar,
const std::vector<float> tipTargetFlexCoefficients,
const std::vector<float> midTargetFlexCoefficients) :
AnimNode(AnimNode::Type::SplineIK, id),
_alpha(alpha),
_enabled(enabled),
_interpDuration(interpDuration),
_baseJointName(baseJointName),
_midJointName(midJointName),
_tipJointName(tipJointName),
_basePositionVar(basePositionVar),
_baseRotationVar(baseRotationVar),
_midPositionVar(midPositionVar),
_midRotationVar(midRotationVar),
_tipPositionVar(tipPositionVar),
_tipRotationVar(tipRotationVar),
_alphaVar(alphaVar),
_enabledVar(enabledVar)
{
for (int i = 0; i < (int)tipTargetFlexCoefficients.size(); i++) {
if (i < MAX_NUMBER_FLEX_VARIABLES) {
_tipTargetFlexCoefficients[i] = tipTargetFlexCoefficients[i];
}
}
_numTipTargetFlexCoefficients = std::min((int)tipTargetFlexCoefficients.size(), MAX_NUMBER_FLEX_VARIABLES);
for (int i = 0; i < (int)midTargetFlexCoefficients.size(); i++) {
if (i < MAX_NUMBER_FLEX_VARIABLES) {
_midTargetFlexCoefficients[i] = midTargetFlexCoefficients[i];
}
}
_numMidTargetFlexCoefficients = std::min((int)midTargetFlexCoefficients.size(), MAX_NUMBER_FLEX_VARIABLES);
}
AnimSplineIK::~AnimSplineIK() {
}
const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) {
assert(_children.size() == 1);
if (_children.size() != 1) {
return _poses;
}
const float MIN_ALPHA = 0.0f;
const float MAX_ALPHA = 1.0f;
float alpha = glm::clamp(animVars.lookup(_alphaVar, _alpha), MIN_ALPHA, MAX_ALPHA);
// evaluate underPoses
AnimPoseVec underPoses = _children[0]->evaluate(animVars, context, dt, triggersOut);
// if we don't have a skeleton, or jointName lookup failed or the spline alpha is 0 or there are no underposes.
if (!_skeleton || _baseJointIndex == -1 || _midJointIndex == -1 || _tipJointIndex == -1 || alpha < EPSILON || underPoses.size() == 0) {
// pass underPoses through unmodified.
_poses = underPoses;
return _poses;
}
// guard against size change
if (underPoses.size() != _poses.size()) {
_poses = underPoses;
}
// determine if we should interpolate
bool enabled = animVars.lookup(_enabledVar, _enabled);
if (enabled != _enabled) {
AnimChain poseChain;
poseChain.buildFromRelativePoses(_skeleton, _poses, _tipJointIndex);
if (enabled) {
beginInterp(InterpType::SnapshotToSolve, poseChain);
} else {
beginInterp(InterpType::SnapshotToUnderPoses, poseChain);
}
}
_enabled = enabled;
// now that we have saved the previous _poses in _snapshotChain, we can update to the current underposes
_poses = underPoses;
// don't build chains or do IK if we are disabled & not interping.
if (_interpType == InterpType::None && !enabled) {
return _poses;
}
// compute under chain for possible interpolation
AnimChain underChain;
underChain.buildFromRelativePoses(_skeleton, underPoses, _tipJointIndex);
AnimPose baseTargetAbsolutePose;
// if there is a baseJoint ik target in animvars then set the joint to that
// otherwise use the underpose
AnimPose baseJointUnderPose = _skeleton->getAbsolutePose(_baseJointIndex, _poses);
baseTargetAbsolutePose.rot() = animVars.lookupRigToGeometry(_baseRotationVar, baseJointUnderPose.rot());
baseTargetAbsolutePose.trans() = animVars.lookupRigToGeometry(_basePositionVar, baseJointUnderPose.trans());
int baseParentIndex = _skeleton->getParentIndex(_baseJointIndex);
AnimPose baseParentAbsPose(Quaternions::IDENTITY,glm::vec3());
if (baseParentIndex >= 0) {
baseParentAbsPose = _skeleton->getAbsolutePose(baseParentIndex, _poses);
}
_poses[_baseJointIndex] = baseParentAbsPose.inverse() * baseTargetAbsolutePose;
_poses[_baseJointIndex].scale() = glm::vec3(1.0f);
// initialize the middle joint target
IKTarget midTarget;
midTarget.setType((int)IKTarget::Type::Spline);
midTarget.setIndex(_midJointIndex);
AnimPose absPoseMid = _skeleton->getAbsolutePose(_midJointIndex, _poses);
glm::quat midTargetRotation = animVars.lookupRigToGeometry(_midRotationVar, absPoseMid.rot());
glm::vec3 midTargetPosition = animVars.lookupRigToGeometry(_midPositionVar, absPoseMid.trans());
midTarget.setPose(midTargetRotation, midTargetPosition);
midTarget.setWeight(1.0f);
midTarget.setFlexCoefficients(_numMidTargetFlexCoefficients, _midTargetFlexCoefficients);
// solve the lower spine spline
AnimChain midJointChain;
AnimPoseVec absolutePosesAfterBaseTipSpline;
absolutePosesAfterBaseTipSpline.resize(_poses.size());
computeAbsolutePoses(absolutePosesAfterBaseTipSpline);
midJointChain.buildFromRelativePoses(_skeleton, _poses, midTarget.getIndex());
solveTargetWithSpline(context, _baseJointIndex, midTarget, absolutePosesAfterBaseTipSpline, context.getEnableDebugDrawIKChains(), midJointChain);
midJointChain.outputRelativePoses(_poses);
// initialize the tip target
IKTarget tipTarget;
tipTarget.setType((int)IKTarget::Type::Spline);
tipTarget.setIndex(_tipJointIndex);
AnimPose absPoseTip = _skeleton->getAbsolutePose(_tipJointIndex, _poses);
glm::quat tipRotation = animVars.lookupRigToGeometry(_tipRotationVar, absPoseTip.rot());
glm::vec3 tipTranslation = animVars.lookupRigToGeometry(_tipPositionVar, absPoseTip.trans());
tipTarget.setPose(tipRotation, tipTranslation);
tipTarget.setWeight(1.0f);
tipTarget.setFlexCoefficients(_numTipTargetFlexCoefficients, _tipTargetFlexCoefficients);
// solve the upper spine spline
AnimChain upperJointChain;
AnimPoseVec finalAbsolutePoses;
finalAbsolutePoses.resize(_poses.size());
computeAbsolutePoses(finalAbsolutePoses);
upperJointChain.buildFromRelativePoses(_skeleton, _poses, tipTarget.getIndex());
solveTargetWithSpline(context, _midJointIndex, tipTarget, finalAbsolutePoses, context.getEnableDebugDrawIKChains(), upperJointChain);
upperJointChain.buildDirtyAbsolutePoses();
upperJointChain.outputRelativePoses(_poses);
// compute chain
AnimChain ikChain;
ikChain.buildFromRelativePoses(_skeleton, _poses, _tipJointIndex);
// blend with the underChain
ikChain.blend(underChain, alpha);
// apply smooth interpolation when turning ik on and off
if (_interpType != InterpType::None) {
_interpAlpha += _interpAlphaVel * dt;
// ease in expo
float easeInAlpha = 1.0f - powf(2.0f, -10.0f * _interpAlpha);
if (_interpAlpha < 1.0f) {
AnimChain interpChain;
if (_interpType == InterpType::SnapshotToUnderPoses) {
interpChain = underChain;
interpChain.blend(_snapshotChain, easeInAlpha);
} else if (_interpType == InterpType::SnapshotToSolve) {
interpChain = ikChain;
interpChain.blend(_snapshotChain, easeInAlpha);
}
// copy interpChain into _poses
interpChain.outputRelativePoses(_poses);
} else {
// interpolation complete
_interpType = InterpType::None;
}
}
if (_interpType == InterpType::None) {
if (enabled) {
// copy chain into _poses
ikChain.outputRelativePoses(_poses);
} else {
// copy under chain into _poses
underChain.outputRelativePoses(_poses);
}
}
// debug render ik targets
if (context.getEnableDebugDrawIKTargets()) {
const vec4 WHITE(1.0f);
const vec4 GREEN(0.0f, 1.0f, 0.0f, 1.0f);
glm::mat4 rigToAvatarMat = createMatFromQuatAndPos(Quaternions::Y_180, glm::vec3());
glm::mat4 geomTargetMat = createMatFromQuatAndPos(tipTarget.getRotation(), tipTarget.getTranslation());
glm::mat4 avatarTargetMat = rigToAvatarMat * context.getGeometryToRigMatrix() * geomTargetMat;
QString name = QString("ikTargetSplineTip");
DebugDraw::getInstance().addMyAvatarMarker(name, glmExtractRotation(avatarTargetMat), extractTranslation(avatarTargetMat), WHITE);
glm::mat4 geomTargetMat2 = createMatFromQuatAndPos(midTarget.getRotation(), midTarget.getTranslation());
glm::mat4 avatarTargetMat2 = rigToAvatarMat * context.getGeometryToRigMatrix() * geomTargetMat2;
QString name2 = QString("ikTargetSplineMid");
DebugDraw::getInstance().addMyAvatarMarker(name2, glmExtractRotation(avatarTargetMat2), extractTranslation(avatarTargetMat2), WHITE);
glm::mat4 geomTargetMat3 = createMatFromQuatAndPos(baseTargetAbsolutePose.rot(), baseTargetAbsolutePose.trans());
glm::mat4 avatarTargetMat3 = rigToAvatarMat * context.getGeometryToRigMatrix() * geomTargetMat3;
QString name3 = QString("ikTargetSplineBase");
DebugDraw::getInstance().addMyAvatarMarker(name3, glmExtractRotation(avatarTargetMat3), extractTranslation(avatarTargetMat3), WHITE);
} else if (context.getEnableDebugDrawIKTargets() != _previousEnableDebugIKTargets) {
// remove markers if they were added last frame.
QString name = QString("ikTargetSplineTip");
DebugDraw::getInstance().removeMyAvatarMarker(name);
QString name2 = QString("ikTargetSplineMid");
DebugDraw::getInstance().removeMyAvatarMarker(name2);
QString name3 = QString("ikTargetSplineBase");
DebugDraw::getInstance().removeMyAvatarMarker(name3);
}
_previousEnableDebugIKTargets = context.getEnableDebugDrawIKTargets();
return _poses;
}
void AnimSplineIK::lookUpIndices() {
assert(_skeleton);
// look up bone indices by name
std::vector<int> indices = _skeleton->lookUpJointIndices({ _baseJointName, _tipJointName, _midJointName });
// cache the results
_baseJointIndex = indices[0];
_tipJointIndex = indices[1];
_midJointIndex = indices[2];
}
void AnimSplineIK::computeAbsolutePoses(AnimPoseVec& absolutePoses) const {
int numJoints = (int)_poses.size();
assert(numJoints <= _skeleton->getNumJoints());
assert(numJoints == (int)absolutePoses.size());
for (int i = 0; i < numJoints; ++i) {
int parentIndex = _skeleton->getParentIndex(i);
if (parentIndex < 0) {
absolutePoses[i] = _poses[i];
} else {
absolutePoses[i] = absolutePoses[parentIndex] * _poses[i];
}
}
}
// for AnimDebugDraw rendering
const AnimPoseVec& AnimSplineIK::getPosesInternal() const {
return _poses;
}
void AnimSplineIK::setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) {
AnimNode::setSkeletonInternal(skeleton);
lookUpIndices();
}
void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, int base, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug, AnimChain& chainInfoOut) const {
// build spline from tip to base
AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation());
AnimPose basePose = absolutePoses[base];
CubicHermiteSplineFunctorWithArcLength spline;
if (target.getIndex() == _tipJointIndex) {
// set gain factors so that more curvature occurs near the tip of the spline.
const float HIPS_GAIN = 0.5f;
const float HEAD_GAIN = 1.0f;
spline = CubicHermiteSplineFunctorWithArcLength(tipPose.rot(), tipPose.trans(), basePose.rot(), basePose.trans(), HIPS_GAIN, HEAD_GAIN);
} else {
spline = CubicHermiteSplineFunctorWithArcLength(tipPose.rot(),tipPose.trans(), basePose.rot(), basePose.trans());
}
float totalArcLength = spline.arcLength(1.0f);
// This prevents the rotation interpolation from rotating the wrong physical way (but correct mathematical way)
// when the head is arched backwards very far.
glm::quat halfRot = safeLerp(basePose.rot(), tipPose.rot(), 0.5f);
if (glm::dot(halfRot * Vectors::UNIT_Z, basePose.rot() * Vectors::UNIT_Z) < 0.0f) {
tipPose.rot() = -tipPose.rot();
}
// find or create splineJointInfo for this target
const std::vector<SplineJointInfo>* splineJointInfoVec = findOrCreateSplineJointInfo(context, base, target);
if (splineJointInfoVec && splineJointInfoVec->size() > 0) {
const int baseParentIndex = _skeleton->getParentIndex(base);
AnimPose parentAbsPose = (baseParentIndex >= 0) ? absolutePoses[baseParentIndex] : AnimPose();
// go thru splineJointInfoVec backwards (base to tip)
for (int i = (int)splineJointInfoVec->size() - 1; i >= 0; i--) {
const SplineJointInfo& splineJointInfo = (*splineJointInfoVec)[i];
float t = spline.arcLengthInverse(splineJointInfo.ratio * totalArcLength);
glm::vec3 trans = spline(t);
// for base->tip splines, preform most twist toward the tip by using ease in function. t^2
float rotT = t;
if (target.getIndex() == _tipJointIndex) {
rotT = t * t;
}
glm::quat twistRot = safeLerp(basePose.rot(), tipPose.rot(), rotT);
// compute the rotation by using the derivative of the spline as the y-axis, and the twistRot x-axis
glm::vec3 y = glm::normalize(spline.d(t));
glm::vec3 x = twistRot * Vectors::UNIT_X;
glm::vec3 u, v, w;
generateBasisVectors(y, x, v, u, w);
glm::mat3 m(u, v, glm::cross(u, v));
glm::quat rot = glm::normalize(glm::quat_cast(m));
AnimPose desiredAbsPose = AnimPose(glm::vec3(1.0f), rot, trans) * splineJointInfo.offsetPose;
// apply flex coefficent
AnimPose flexedAbsPose;
// get the number of flex coeff for this spline
float interpedCoefficient = 1.0f;
int numFlexCoeff = target.getNumFlexCoefficients();
if (numFlexCoeff == (int)splineJointInfoVec->size()) {
// then do nothing special
interpedCoefficient = target.getFlexCoefficient(i);
} else {
// interp based on ratio of the joint.
if (splineJointInfo.ratio < 1.0f) {
float flexInterp = splineJointInfo.ratio * (float)(numFlexCoeff - 1);
int startCoeff = (int)glm::floor(flexInterp);
float partial = flexInterp - startCoeff;
interpedCoefficient = target.getFlexCoefficient(startCoeff) * (1.0f - partial) + target.getFlexCoefficient(startCoeff + 1) * partial;
} else {
interpedCoefficient = target.getFlexCoefficient(numFlexCoeff - 1);
}
}
::blend(1, &absolutePoses[splineJointInfo.jointIndex], &desiredAbsPose, interpedCoefficient, &flexedAbsPose);
AnimPose relPose = parentAbsPose.inverse() * flexedAbsPose;
if (splineJointInfo.jointIndex != base) {
// constrain the amount the spine can stretch or compress
float length = glm::length(relPose.trans());
const float EPSILON = 0.0001f;
if (length > EPSILON) {
float defaultLength = glm::length(_skeleton->getRelativeDefaultPose(splineJointInfo.jointIndex).trans());
const float STRETCH_COMPRESS_PERCENTAGE = 0.15f;
const float MAX_LENGTH = defaultLength * (1.0f + STRETCH_COMPRESS_PERCENTAGE);
const float MIN_LENGTH = defaultLength * (1.0f - STRETCH_COMPRESS_PERCENTAGE);
if (length > MAX_LENGTH) {
relPose.trans() = (relPose.trans() / length) * MAX_LENGTH;
} else if (length < MIN_LENGTH) {
relPose.trans() = (relPose.trans() / length) * MIN_LENGTH;
}
} else {
relPose.trans() = glm::vec3(0.0f);
}
}
if (!chainInfoOut.setRelativePoseAtJointIndex(splineJointInfo.jointIndex, relPose)) {
qCDebug(animation) << "error: joint not found in spline chain";
}
parentAbsPose = flexedAbsPose;
}
}
if (debug) {
const vec4 CYAN(0.0f, 1.0f, 1.0f, 1.0f);
chainInfoOut.debugDraw(context.getRigToWorldMatrix() * context.getGeometryToRigMatrix(), CYAN);
}
}
const std::vector<AnimSplineIK::SplineJointInfo>* AnimSplineIK::findOrCreateSplineJointInfo(const AnimContext& context, int base, const IKTarget& target) const {
// find or create splineJointInfo for this target
auto iter = _splineJointInfoMap.find(target.getIndex());
if (iter != _splineJointInfoMap.end()) {
return &(iter->second);
} else {
computeAndCacheSplineJointInfosForIKTarget(context, base, target);
auto iter = _splineJointInfoMap.find(target.getIndex());
if (iter != _splineJointInfoMap.end()) {
return &(iter->second);
}
}
return nullptr;
}
// pre-compute information about each joint influenced by this spline IK target.
void AnimSplineIK::computeAndCacheSplineJointInfosForIKTarget(const AnimContext& context, int base, const IKTarget& target) const {
std::vector<SplineJointInfo> splineJointInfoVec;
// build spline between the default poses.
AnimPose tipPose = _skeleton->getAbsoluteDefaultPose(target.getIndex());
AnimPose basePose = _skeleton->getAbsoluteDefaultPose(base);
CubicHermiteSplineFunctorWithArcLength spline;
if (target.getIndex() == _tipJointIndex) {
// set gain factors so that more curvature occurs near the tip of the spline.
const float HIPS_GAIN = 0.5f;
const float HEAD_GAIN = 1.0f;
spline = CubicHermiteSplineFunctorWithArcLength(tipPose.rot(), tipPose.trans(), basePose.rot(), basePose.trans(), HIPS_GAIN, HEAD_GAIN);
} else {
spline = CubicHermiteSplineFunctorWithArcLength(tipPose.rot(), tipPose.trans(), basePose.rot(), basePose.trans());
}
// measure the total arc length along the spline
float totalArcLength = spline.arcLength(1.0f);
glm::vec3 baseToTip = tipPose.trans() - basePose.trans();
float baseToTipLength = glm::length(baseToTip);
glm::vec3 baseToTipNormal = baseToTip / baseToTipLength;
int index = target.getIndex();
int endIndex = _skeleton->getParentIndex(base);
while (index != endIndex) {
AnimPose defaultPose = _skeleton->getAbsoluteDefaultPose(index);
glm::vec3 baseToCurrentJoint = defaultPose.trans() - basePose.trans();
float ratio = glm::dot(baseToCurrentJoint, baseToTipNormal) / baseToTipLength;
// compute offset from spline to the default pose.
float t = spline.arcLengthInverse(ratio * totalArcLength);
// compute the rotation by using the derivative of the spline as the y-axis, and the defaultPose x-axis
glm::vec3 y = glm::normalize(spline.d(t));
glm::vec3 x = defaultPose.rot() * Vectors::UNIT_X;
glm::vec3 u, v, w;
generateBasisVectors(y, x, v, u, w);
glm::mat3 m(u, v, glm::cross(u, v));
glm::quat rot = glm::normalize(glm::quat_cast(m));
AnimPose pose(glm::vec3(1.0f), rot, spline(t));
AnimPose offsetPose = pose.inverse() * defaultPose;
SplineJointInfo splineJointInfo = { index, ratio, offsetPose };
splineJointInfoVec.push_back(splineJointInfo);
index = _skeleton->getParentIndex(index);
}
_splineJointInfoMap[target.getIndex()] = splineJointInfoVec;
}
void AnimSplineIK::beginInterp(InterpType interpType, const AnimChain& chain) {
// capture the current poses in a snapshot.
_snapshotChain = chain;
_interpType = interpType;
_interpAlphaVel = FRAMES_PER_SECOND / _interpDuration;
_interpAlpha = 0.0f;
}