mirror of
https://github.com/overte-org/overte.git
synced 2025-07-16 03:16:53 +02:00
296 lines
11 KiB
C++
296 lines
11 KiB
C++
//
|
|
// AnimTwoBoneIK.cpp
|
|
//
|
|
// Created by Anthony J. Thibault on 5/12/18.
|
|
// Copyright (c) 2018 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 "AnimTwoBoneIK.h"
|
|
|
|
#include <DebugDraw.h>
|
|
|
|
#include "AnimationLogging.h"
|
|
#include "AnimUtil.h"
|
|
|
|
const float FRAMES_PER_SECOND = 30.0f;
|
|
|
|
AnimTwoBoneIK::AnimTwoBoneIK(const QString& id, float alpha, bool enabled, float interpDuration,
|
|
const QString& baseJointName, const QString& midJointName,
|
|
const QString& tipJointName, const glm::vec3& midHingeAxis,
|
|
const QString& alphaVar, const QString& enabledVar,
|
|
const QString& endEffectorRotationVarVar, const QString& endEffectorPositionVarVar) :
|
|
AnimNode(AnimNode::Type::TwoBoneIK, id),
|
|
_alpha(alpha),
|
|
_enabled(enabled),
|
|
_interpDuration(interpDuration),
|
|
_baseJointName(baseJointName),
|
|
_midJointName(midJointName),
|
|
_tipJointName(tipJointName),
|
|
_midHingeAxis(glm::normalize(midHingeAxis)),
|
|
_alphaVar(alphaVar),
|
|
_enabledVar(enabledVar),
|
|
_endEffectorRotationVarVar(endEffectorRotationVarVar),
|
|
_endEffectorPositionVarVar(endEffectorPositionVarVar),
|
|
_prevEndEffectorRotationVar(),
|
|
_prevEndEffectorPositionVar()
|
|
{
|
|
|
|
}
|
|
|
|
AnimTwoBoneIK::~AnimTwoBoneIK() {
|
|
|
|
}
|
|
|
|
const AnimPoseVec& AnimTwoBoneIK::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) {
|
|
|
|
assert(_children.size() == 1);
|
|
if (_children.size() != 1) {
|
|
return _poses;
|
|
}
|
|
|
|
// evalute underPoses
|
|
AnimPoseVec underPoses = _children[0]->evaluate(animVars, context, dt, triggersOut);
|
|
|
|
// if we don't have a skeleton, or jointName lookup failed.
|
|
if (!_skeleton || _baseJointIndex == -1 || _midJointIndex == -1 || _tipJointIndex == -1 || underPoses.size() == 0) {
|
|
// pass underPoses through unmodified.
|
|
_poses = underPoses;
|
|
return _poses;
|
|
}
|
|
|
|
// guard against size changes
|
|
if (underPoses.size() != _poses.size()) {
|
|
_poses = underPoses;
|
|
}
|
|
|
|
const float MIN_ALPHA = 0.0f;
|
|
const float MAX_ALPHA = 1.0f;
|
|
float alpha = glm::clamp(animVars.lookup(_alphaVar, _alpha), MIN_ALPHA, MAX_ALPHA);
|
|
|
|
// don't perform IK if we have bad indices, or alpha is zero
|
|
if (_tipJointIndex == -1 || _midJointIndex == -1 || _baseJointIndex == -1 || alpha == 0.0f) {
|
|
_poses = underPoses;
|
|
return _poses;
|
|
}
|
|
|
|
// 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;
|
|
|
|
// don't build chains or do IK if we are disbled & not interping.
|
|
if (_interpType == InterpType::None && !enabled) {
|
|
_poses = underPoses;
|
|
return _poses;
|
|
}
|
|
|
|
// compute chain
|
|
AnimChain underChain;
|
|
underChain.buildFromRelativePoses(_skeleton, underPoses, _tipJointIndex);
|
|
AnimChain ikChain = underChain;
|
|
|
|
AnimPose baseParentPose = ikChain.getAbsolutePoseFromJointIndex(_baseParentJointIndex);
|
|
AnimPose basePose = ikChain.getAbsolutePoseFromJointIndex(_baseJointIndex);
|
|
AnimPose midPose = ikChain.getAbsolutePoseFromJointIndex(_midJointIndex);
|
|
AnimPose tipPose = ikChain.getAbsolutePoseFromJointIndex(_tipJointIndex);
|
|
|
|
QString endEffectorRotationVar = animVars.lookup(_endEffectorRotationVarVar, QString(""));
|
|
QString endEffectorPositionVar = animVars.lookup(_endEffectorPositionVarVar, QString(""));
|
|
|
|
// if either of the endEffectorVars have changed
|
|
if ((!_prevEndEffectorRotationVar.isEmpty() && (_prevEndEffectorRotationVar != endEffectorRotationVar)) ||
|
|
(!_prevEndEffectorPositionVar.isEmpty() && (_prevEndEffectorPositionVar != endEffectorPositionVar))) {
|
|
// begin interp to smooth out transition between prev and new end effector.
|
|
AnimChain poseChain;
|
|
poseChain.buildFromRelativePoses(_skeleton, _poses, _tipJointIndex);
|
|
beginInterp(InterpType::SnapshotToSolve, poseChain);
|
|
}
|
|
|
|
// Look up end effector from animVars, make sure to convert into geom space.
|
|
// First look in the triggers then look in the animVars, so we can follow output joints underneath us in the anim graph
|
|
AnimPose targetPose(tipPose);
|
|
if (triggersOut.hasKey(endEffectorRotationVar)) {
|
|
targetPose.rot() = triggersOut.lookupRigToGeometry(endEffectorRotationVar, tipPose.rot());
|
|
} else if (animVars.hasKey(endEffectorRotationVar)) {
|
|
targetPose.rot() = animVars.lookupRigToGeometry(endEffectorRotationVar, tipPose.rot());
|
|
}
|
|
|
|
if (triggersOut.hasKey(endEffectorPositionVar)) {
|
|
targetPose.trans() = triggersOut.lookupRigToGeometry(endEffectorPositionVar, tipPose.trans());
|
|
} else if (animVars.hasKey(endEffectorRotationVar)) {
|
|
targetPose.trans() = animVars.lookupRigToGeometry(endEffectorPositionVar, tipPose.trans());
|
|
}
|
|
|
|
_prevEndEffectorRotationVar = endEffectorRotationVar;
|
|
_prevEndEffectorPositionVar = endEffectorPositionVar;
|
|
|
|
glm::vec3 bicepVector = midPose.trans() - basePose.trans();
|
|
float r0 = glm::length(bicepVector);
|
|
bicepVector = bicepVector / r0;
|
|
|
|
glm::vec3 forearmVector = tipPose.trans() - midPose.trans();
|
|
float r1 = glm::length(forearmVector);
|
|
forearmVector = forearmVector / r1;
|
|
|
|
float d = glm::length(targetPose.trans() - basePose.trans());
|
|
|
|
// http://mathworld.wolfram.com/Circle-CircleIntersection.html
|
|
float midAngle = 0.0f;
|
|
if (d < r0 + r1) {
|
|
float y = sqrtf((-d + r1 - r0) * (-d - r1 + r0) * (-d + r1 + r0) * (d + r1 + r0)) / (2.0f * d);
|
|
midAngle = PI - (acosf(y / r0) + acosf(y / r1));
|
|
}
|
|
|
|
// compute midJoint rotation
|
|
glm::quat relMidRot = glm::angleAxis(midAngle, _midHingeAxis);
|
|
|
|
// insert new relative pose into the chain and rebuild it.
|
|
ikChain.setRelativePoseAtJointIndex(_midJointIndex, AnimPose(relMidRot, underPoses[_midJointIndex].trans()));
|
|
ikChain.buildDirtyAbsolutePoses();
|
|
|
|
// recompute tip pose after mid joint has been rotated
|
|
AnimPose newTipPose = ikChain.getAbsolutePoseFromJointIndex(_tipJointIndex);
|
|
|
|
glm::vec3 leverArm = newTipPose.trans() - basePose.trans();
|
|
glm::vec3 targetLine = targetPose.trans() - basePose.trans();
|
|
|
|
// compute delta rotation that brings leverArm parallel to targetLine
|
|
glm::vec3 axis = glm::cross(leverArm, targetLine);
|
|
float axisLength = glm::length(axis);
|
|
const float MIN_AXIS_LENGTH = 1.0e-4f;
|
|
if (axisLength > MIN_AXIS_LENGTH) {
|
|
axis /= axisLength;
|
|
float cosAngle = glm::clamp(glm::dot(leverArm, targetLine) / (glm::length(leverArm) * glm::length(targetLine)), -1.0f, 1.0f);
|
|
float angle = acosf(cosAngle);
|
|
glm::quat deltaRot = glm::angleAxis(angle, axis);
|
|
|
|
// combine deltaRot with basePose.
|
|
glm::quat absRot = deltaRot * basePose.rot();
|
|
|
|
// transform result back into parent relative frame.
|
|
glm::quat relBaseRot = glm::inverse(baseParentPose.rot()) * absRot;
|
|
ikChain.setRelativePoseAtJointIndex(_baseJointIndex, AnimPose(relBaseRot, underPoses[_baseJointIndex].trans()));
|
|
}
|
|
|
|
// recompute midJoint pose after base has been rotated.
|
|
ikChain.buildDirtyAbsolutePoses();
|
|
AnimPose midJointPose = ikChain.getAbsolutePoseFromJointIndex(_midJointIndex);
|
|
|
|
// transform target rotation in to parent relative frame.
|
|
glm::quat relTipRot = glm::inverse(midJointPose.rot()) * targetPose.rot();
|
|
ikChain.setRelativePoseAtJointIndex(_tipJointIndex, AnimPose(relTipRot, underPoses[_tipJointIndex].trans()));
|
|
|
|
// blend with the underChain
|
|
ikChain.blend(underChain, alpha);
|
|
|
|
// start off by initializing output poses with the underPoses
|
|
_poses = underPoses;
|
|
|
|
// apply smooth interpolation
|
|
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);
|
|
}
|
|
}
|
|
|
|
if (context.getEnableDebugDrawIKTargets()) {
|
|
const vec4 RED(1.0f, 0.0f, 0.0f, 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(targetPose.rot(), targetPose.trans());
|
|
glm::mat4 avatarTargetMat = rigToAvatarMat * context.getGeometryToRigMatrix() * geomTargetMat;
|
|
|
|
QString name = QString("%1_target").arg(_id);
|
|
DebugDraw::getInstance().addMyAvatarMarker(name, glmExtractRotation(avatarTargetMat),
|
|
extractTranslation(avatarTargetMat), _enabled ? GREEN : RED);
|
|
} else if (_lastEnableDebugDrawIKTargets) {
|
|
QString name = QString("%1_target").arg(_id);
|
|
DebugDraw::getInstance().removeMyAvatarMarker(name);
|
|
}
|
|
_lastEnableDebugDrawIKTargets = context.getEnableDebugDrawIKTargets();
|
|
|
|
if (context.getEnableDebugDrawIKChains()) {
|
|
if (_interpType == InterpType::None && enabled) {
|
|
const vec4 CYAN(0.0f, 1.0f, 1.0f, 1.0f);
|
|
ikChain.debugDraw(context.getRigToWorldMatrix() * context.getGeometryToRigMatrix(), CYAN);
|
|
}
|
|
}
|
|
|
|
processOutputJoints(triggersOut);
|
|
|
|
return _poses;
|
|
}
|
|
|
|
// for AnimDebugDraw rendering
|
|
const AnimPoseVec& AnimTwoBoneIK::getPosesInternal() const {
|
|
return _poses;
|
|
}
|
|
|
|
void AnimTwoBoneIK::setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) {
|
|
AnimNode::setSkeletonInternal(skeleton);
|
|
lookUpIndices();
|
|
}
|
|
|
|
void AnimTwoBoneIK::lookUpIndices() {
|
|
assert(_skeleton);
|
|
|
|
// look up bone indices by name
|
|
std::vector<int> indices = _skeleton->lookUpJointIndices({_baseJointName, _midJointName, _tipJointName});
|
|
|
|
// cache the results
|
|
_baseJointIndex = indices[0];
|
|
_midJointIndex = indices[1];
|
|
_tipJointIndex = indices[2];
|
|
|
|
if (_baseJointIndex != -1) {
|
|
_baseParentJointIndex = _skeleton->getParentIndex(_baseJointIndex);
|
|
}
|
|
}
|
|
|
|
void AnimTwoBoneIK::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;
|
|
}
|