From 001e4bc952553b61416e374a957543827768ae30 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Wed, 21 Aug 2019 16:11:10 -0700 Subject: [PATCH] AnimClip now supports relative animations. Relative animations are animations that are rotation deltas from some base reference pose. They are calculated by multiplying each frame of each joint the inverse of that joints in the base reference pose. It is intended for use with additive blending. Added the following fields to AnimClip node. * isRelative (boolean) * baseURL (string) * baseFrame (int) --- libraries/animation/src/AnimClip.cpp | 265 ++++++++++++--------- libraries/animation/src/AnimClip.h | 11 +- libraries/animation/src/AnimNodeLoader.cpp | 6 +- libraries/animation/src/Rig.cpp | 2 +- 4 files changed, 173 insertions(+), 111 deletions(-) diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index e5edb46f69..3d045341ff 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -16,100 +16,6 @@ #include "AnimationLogging.h" #include "AnimUtil.h" - -AnimClip::AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag, bool mirrorFlag) : - AnimNode(AnimNode::Type::Clip, id), - _startFrame(startFrame), - _endFrame(endFrame), - _timeScale(timeScale), - _loopFlag(loopFlag), - _mirrorFlag(mirrorFlag), - _frame(startFrame) -{ - loadURL(url); -} - -AnimClip::~AnimClip() { - -} - -const AnimPoseVec& AnimClip::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { - - // lookup parameters from animVars, using current instance variables as defaults. - _startFrame = animVars.lookup(_startFrameVar, _startFrame); - _endFrame = animVars.lookup(_endFrameVar, _endFrame); - _timeScale = animVars.lookup(_timeScaleVar, _timeScale); - _loopFlag = animVars.lookup(_loopFlagVar, _loopFlag); - _mirrorFlag = animVars.lookup(_mirrorFlagVar, _mirrorFlag); - float frame = animVars.lookup(_frameVar, _frame); - - _frame = ::accumulateTime(_startFrame, _endFrame, _timeScale, frame, dt, _loopFlag, _id, triggersOut); - - // poll network anim to see if it's finished loading yet. - if (_networkAnim && _networkAnim->isLoaded() && _skeleton) { - // loading is complete, copy animation frames from network animation, then throw it away. - copyFromNetworkAnim(); - _networkAnim.reset(); - } - - if (_anim.size()) { - - // lazy creation of mirrored animation frames. - if (_mirrorFlag && _anim.size() != _mirrorAnim.size()) { - buildMirrorAnim(); - } - - int prevIndex = (int)glm::floor(_frame); - int nextIndex; - if (_loopFlag && _frame >= _endFrame) { - nextIndex = (int)glm::ceil(_startFrame); - } else { - nextIndex = (int)glm::ceil(_frame); - } - - // It can be quite possible for the user to set _startFrame and _endFrame to - // values before or past valid ranges. We clamp the frames here. - int frameCount = (int)_anim.size(); - prevIndex = std::min(std::max(0, prevIndex), frameCount - 1); - nextIndex = std::min(std::max(0, nextIndex), frameCount - 1); - - const AnimPoseVec& prevFrame = _mirrorFlag ? _mirrorAnim[prevIndex] : _anim[prevIndex]; - const AnimPoseVec& nextFrame = _mirrorFlag ? _mirrorAnim[nextIndex] : _anim[nextIndex]; - float alpha = glm::fract(_frame); - - ::blend(_poses.size(), &prevFrame[0], &nextFrame[0], alpha, &_poses[0]); - } - - processOutputJoints(triggersOut); - - return _poses; -} - -void AnimClip::loadURL(const QString& url) { - auto animCache = DependencyManager::get(); - _networkAnim = animCache->getAnimation(url); - _url = url; -} - -void AnimClip::setCurrentFrameInternal(float frame) { - // because dt is 0, we should not encounter any triggers - const float dt = 0.0f; - AnimVariantMap triggers; - _frame = ::accumulateTime(_startFrame, _endFrame, _timeScale, frame + _startFrame, dt, _loopFlag, _id, triggers); -} - -static std::vector buildJointIndexMap(const AnimSkeleton& dstSkeleton, const AnimSkeleton& srcSkeleton) { - std::vector jointIndexMap; - int srcJointCount = srcSkeleton.getNumJoints(); - jointIndexMap.reserve(srcJointCount); - for (int srcJointIndex = 0; srcJointIndex < srcJointCount; srcJointIndex++) { - QString srcJointName = srcSkeleton.getJointName(srcJointIndex); - int dstJointIndex = dstSkeleton.nameToJointIndex(srcJointName); - jointIndexMap.push_back(dstJointIndex); - } - return jointIndexMap; -} - #ifdef USE_CUSTOM_ASSERT #undef ASSERT #define ASSERT(x) \ @@ -123,12 +29,44 @@ static std::vector buildJointIndexMap(const AnimSkeleton& dstSkeleton, cons #define ASSERT assert #endif -void AnimClip::copyFromNetworkAnim() { - assert(_networkAnim && _networkAnim->isLoaded() && _skeleton); - _anim.clear(); +static std::vector buildJointIndexMap(const AnimSkeleton& dstSkeleton, const AnimSkeleton& srcSkeleton) { + std::vector jointIndexMap; + int srcJointCount = srcSkeleton.getNumJoints(); + jointIndexMap.reserve(srcJointCount); + for (int srcJointIndex = 0; srcJointIndex < srcJointCount; srcJointIndex++) { + QString srcJointName = srcSkeleton.getJointName(srcJointIndex); + int dstJointIndex = dstSkeleton.nameToJointIndex(srcJointName); + jointIndexMap.push_back(dstJointIndex); + } + return jointIndexMap; +} - auto avatarSkeleton = getSkeleton(); - const HFMModel& animModel = _networkAnim->getHFMModel(); +static void bakeRelativeAnim(std::vector& anim, const AnimPoseVec& basePoses) { + + // invert all the basePoses + AnimPoseVec invBasePoses = basePoses; + for (auto&& invBasePose : invBasePoses) { + invBasePose = invBasePose.inverse(); + } + + // for each frame of the animation + for (auto&& animPoses : anim) { + ASSERT(animPoses.size() == basePoses.size()); + + // for each joint in animPoses + for (size_t i = 0; i < animPoses.size(); ++i) { + + // convert this AnimPose into a relative animation. + animPoses[i] = animPoses[i] * invBasePoses[i]; + } + } +} + +static std::vector copyAndRetargetFromNetworkAnim(AnimationPointer networkAnim, AnimSkeleton::ConstPointer avatarSkeleton) { + ASSERT(networkAnim && networkAnim->isLoaded() && avatarSkeleton); + std::vector anim; + + const HFMModel& animModel = networkAnim->getHFMModel(); AnimSkeleton animSkeleton(animModel); const int animJointCount = animSkeleton.getNumJoints(); const int avatarJointCount = avatarSkeleton->getNumJoints(); @@ -137,7 +75,7 @@ void AnimClip::copyFromNetworkAnim() { std::vector avatarToAnimJointIndexMap = buildJointIndexMap(animSkeleton, *avatarSkeleton); const int animFrameCount = animModel.animationFrames.size(); - _anim.resize(animFrameCount); + anim.resize(animFrameCount); // find the size scale factor for translation in the animation. float boneLengthScale = 1.0f; @@ -223,8 +161,8 @@ void AnimClip::copyFromNetworkAnim() { // convert avatar rotations into relative frame avatarSkeleton->convertAbsoluteRotationsToRelative(avatarRotations); - ASSERT(frame >= 0 && frame < (int)_anim.size()); - _anim[frame].reserve(avatarJointCount); + ASSERT(frame >= 0 && frame < (int)anim.size()); + anim[frame].reserve(avatarJointCount); for (int avatarJointIndex = 0; avatarJointIndex < avatarJointCount; avatarJointIndex++) { const AnimPose& avatarDefaultPose = avatarSkeleton->getRelativeDefaultPose(avatarJointIndex); @@ -251,14 +189,123 @@ void AnimClip::copyFromNetworkAnim() { // build the final pose ASSERT(avatarJointIndex >= 0 && avatarJointIndex < (int)avatarRotations.size()); - _anim[frame].push_back(AnimPose(relativeScale, avatarRotations[avatarJointIndex], relativeTranslation)); + anim[frame].push_back(AnimPose(relativeScale, avatarRotations[avatarJointIndex], relativeTranslation)); } } - // mirrorAnim will be re-built on demand, if needed. - _mirrorAnim.clear(); + return anim; +} - _poses.resize(avatarJointCount); +AnimClip::AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag, bool mirrorFlag, + bool isRelative, const QString& baseURL, float baseFrame) : + AnimNode(AnimNode::Type::Clip, id), + _startFrame(startFrame), + _endFrame(endFrame), + _timeScale(timeScale), + _loopFlag(loopFlag), + _mirrorFlag(mirrorFlag), + _frame(startFrame), + _isRelative(isRelative), + _baseFrame(baseFrame) +{ + loadURL(url); + + if (isRelative) { + auto animCache = DependencyManager::get(); + _baseNetworkAnim = animCache->getAnimation(baseURL); + _baseURL = baseURL; + } +} + +AnimClip::~AnimClip() { + +} + +const AnimPoseVec& AnimClip::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { + + // lookup parameters from animVars, using current instance variables as defaults. + _startFrame = animVars.lookup(_startFrameVar, _startFrame); + _endFrame = animVars.lookup(_endFrameVar, _endFrame); + _timeScale = animVars.lookup(_timeScaleVar, _timeScale); + _loopFlag = animVars.lookup(_loopFlagVar, _loopFlag); + _mirrorFlag = animVars.lookup(_mirrorFlagVar, _mirrorFlag); + float frame = animVars.lookup(_frameVar, _frame); + + _frame = ::accumulateTime(_startFrame, _endFrame, _timeScale, frame, dt, _loopFlag, _id, triggersOut); + + // poll network anim to see if it's finished loading yet. + if (_isRelative) { + if (_networkAnim && _networkAnim->isLoaded() && _skeleton) { + // loading is complete, copy & retarget animation. + _anim = copyAndRetargetFromNetworkAnim(_networkAnim, _skeleton); + + // we no longer need the actual animation resource anymore. + _networkAnim.reset(); + + // mirrorAnim will be re-built on demand, if needed. + _mirrorAnim.clear(); + + _poses.resize(_skeleton->getNumJoints()); + } + } else { + if (_networkAnim && _networkAnim->isLoaded() && _baseNetworkAnim && _baseNetworkAnim->isLoaded() && _skeleton) { + // loading is complete, copy & retarget animation. + _anim = copyAndRetargetFromNetworkAnim(_networkAnim, _skeleton); + + // we no longer need the actual animation resource anymore. + _networkAnim.reset(); + + // mirrorAnim will be re-built on demand, if needed. + _mirrorAnim.clear(); + + _poses.resize(_skeleton->getNumJoints()); + + // copy & retarget baseAnim! + auto baseAnim = copyAndRetargetFromNetworkAnim(_baseNetworkAnim, _skeleton); + + // make _anim relative to the baseAnim reference frame. + bakeRelativeAnim(_anim, baseAnim[(int)_baseFrame]); + } + } + + if (_anim.size()) { + + // lazy creation of mirrored animation frames. + if (_mirrorFlag && _anim.size() != _mirrorAnim.size()) { + buildMirrorAnim(); + } + + int prevIndex = (int)glm::floor(_frame); + int nextIndex; + if (_loopFlag && _frame >= _endFrame) { + nextIndex = (int)glm::ceil(_startFrame); + } else { + nextIndex = (int)glm::ceil(_frame); + } + + // It can be quite possible for the user to set _startFrame and _endFrame to + // values before or past valid ranges. We clamp the frames here. + int frameCount = (int)_anim.size(); + prevIndex = std::min(std::max(0, prevIndex), frameCount - 1); + nextIndex = std::min(std::max(0, nextIndex), frameCount - 1); + + const AnimPoseVec& prevFrame = _mirrorFlag ? _mirrorAnim[prevIndex] : _anim[prevIndex]; + const AnimPoseVec& nextFrame = _mirrorFlag ? _mirrorAnim[nextIndex] : _anim[nextIndex]; + float alpha = glm::fract(_frame); + + ::blend(_poses.size(), &prevFrame[0], &nextFrame[0], alpha, &_poses[0]); + } + + processOutputJoints(triggersOut); + + return _poses; +} + +void AnimClip::setCurrentFrameInternal(float frame) { + // because dt is 0, we should not encounter any triggers + const float dt = 0.0f; + AnimVariantMap triggers; + _frame = ::accumulateTime(_startFrame, _endFrame, _timeScale, frame + _startFrame, dt, _loopFlag, _id, triggers); } void AnimClip::buildMirrorAnim() { @@ -275,3 +322,9 @@ void AnimClip::buildMirrorAnim() { const AnimPoseVec& AnimClip::getPosesInternal() const { return _poses; } + +void AnimClip::loadURL(const QString& url) { + auto animCache = DependencyManager::get(); + _networkAnim = animCache->getAnimation(url); + _url = url; +} diff --git a/libraries/animation/src/AnimClip.h b/libraries/animation/src/AnimClip.h index 92f4c01dcc..04a402356c 100644 --- a/libraries/animation/src/AnimClip.h +++ b/libraries/animation/src/AnimClip.h @@ -25,7 +25,8 @@ class AnimClip : public AnimNode { public: friend class AnimTests; - AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag, bool mirrorFlag); + AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag, bool mirrorFlag, + bool isRelative, const QString& baseURL, float baseFrame); virtual ~AnimClip() override; virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override; @@ -52,19 +53,20 @@ public: void setMirrorFlag(bool mirrorFlag) { _mirrorFlag = mirrorFlag; } float getFrame() const { return _frame; } - void loadURL(const QString& url); + protected: virtual void setCurrentFrameInternal(float frame) override; - void copyFromNetworkAnim(); void buildMirrorAnim(); // for AnimDebugDraw rendering virtual const AnimPoseVec& getPosesInternal() const override; AnimationPointer _networkAnim; + AnimationPointer _baseNetworkAnim; + AnimPoseVec _poses; // _anim[frame][joint] @@ -78,6 +80,9 @@ protected: bool _loopFlag; bool _mirrorFlag; float _frame; + bool _isRelative; + QString _baseURL; + float _baseFrame; QString _startFrameVar; QString _endFrameVar; diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index d43351fff9..9f223e08d1 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -374,6 +374,9 @@ static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString& READ_FLOAT(timeScale, jsonObj, id, jsonUrl, nullptr); READ_BOOL(loopFlag, jsonObj, id, jsonUrl, nullptr); READ_OPTIONAL_BOOL(mirrorFlag, jsonObj, false); + READ_OPTIONAL_BOOL(isRelative, jsonObj, false); + READ_OPTIONAL_STRING(baseURL, jsonObj); + READ_OPTIONAL_FLOAT(baseFrame, jsonObj, 0.0f); READ_OPTIONAL_STRING(startFrameVar, jsonObj); READ_OPTIONAL_STRING(endFrameVar, jsonObj); @@ -381,11 +384,12 @@ static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString& READ_OPTIONAL_STRING(loopFlagVar, jsonObj); READ_OPTIONAL_STRING(mirrorFlagVar, jsonObj); + // animation urls can be relative to the containing url document. auto tempUrl = QUrl(url); tempUrl = jsonUrl.resolved(tempUrl); - auto node = std::make_shared(id, tempUrl.toString(), startFrame, endFrame, timeScale, loopFlag, mirrorFlag); + auto node = std::make_shared(id, tempUrl.toString(), startFrame, endFrame, timeScale, loopFlag, mirrorFlag, isRelative, baseURL, baseFrame); if (!startFrameVar.isEmpty()) { node->setStartFrameVar(startFrameVar); diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 211c54def8..2974212bf1 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -564,7 +564,7 @@ void Rig::overrideRoleAnimation(const QString& role, const QString& url, float f _origRoleAnimations[role] = node; const float REFERENCE_FRAMES_PER_SECOND = 30.0f; float timeScale = fps / REFERENCE_FRAMES_PER_SECOND; - auto clipNode = std::make_shared(role, url, firstFrame, lastFrame, timeScale, loop, false); + auto clipNode = std::make_shared(role, url, firstFrame, lastFrame, timeScale, loop, false, false, "", 0.0f); _roleAnimStates[role] = { role, url, fps, loop, firstFrame, lastFrame }; AnimNode::Pointer parent = node->getParent(); parent->replaceChild(node, clipNode);