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);