From 936c55a94ee99fe945a774e4a6d67a18e4b224db Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 10 Nov 2015 16:34:38 -0800 Subject: [PATCH] New MyAvatar animation JS interface JavaScript changes: * removed MyAvatar.playAnimation * removed MyAvatar.stopAnimation * removed MyAVatar.getGetAnimationDetails * removed MyAvatar.startAnimationByRole * removed MyAvatar.stopAnimationByRole * removed MyAVatar.getGetAnimationDetailsByRole * removed MyAVatar.clearJointPriorities * added MyAvatar.overrideAnimation(url, fps, loop, firstFrame, lastFrame) * added MyAvatar.restoreAnimation() * added MyAvatar.getAnimationRoles() * added MyAvatar.overrideRoleAnimation(role, url, fps, loop, firstFrame, lastFrame) * added MyAvatar.restoreRoleAnimation(role) * added MyAvatar.prefetchAnimation(url) * update kneel.js with new api. * added theBird.js to test role override api. C++ changes: * Added getParent() and replaceChild() to AnimNode * Added findByName() and traverse() to AnimNode * Changed AnimStateMachine to hold nodes by childIndex instead of smart pointer. This allows script to replace nodes dynamically via overrideRoleAnimation --- examples/kneel.js | 4 +- examples/theBird.js | 32 ++++++++++++ interface/src/avatar/MyAvatar.cpp | 24 ++++----- interface/src/avatar/MyAvatar.h | 10 ++-- libraries/animation/src/AnimNode.cpp | 27 ++++++++++- libraries/animation/src/AnimNode.h | 12 +++-- libraries/animation/src/AnimNodeLoader.cpp | 8 +-- libraries/animation/src/AnimStateMachine.cpp | 6 +-- libraries/animation/src/AnimStateMachine.h | 8 +-- libraries/animation/src/Rig.cpp | 51 +++++++++++--------- libraries/animation/src/Rig.h | 10 ++-- 11 files changed, 128 insertions(+), 64 deletions(-) create mode 100644 examples/theBird.js diff --git a/examples/kneel.js b/examples/kneel.js index 3270a77df7..246655949c 100644 --- a/examples/kneel.js +++ b/examples/kneel.js @@ -57,7 +57,7 @@ function kneelDown() { var startFrame = 0; var endFrame = 82; - MyAvatar.startAnimation(KNEEL_ANIM_URL, playbackRate, loopFlag, startFrame, endFrame); + MyAvatar.overrideAnimation(KNEEL_ANIM_URL, playbackRate, loopFlag, startFrame, endFrame); Overlays.editOverlay(kneelDownButton, { visible: false }); Overlays.editOverlay(standUpButton, { visible: true }); @@ -66,7 +66,7 @@ function kneelDown() { function standUp() { kneeling = false; - MyAvatar.stopAnimation(); + MyAvatar.restoreAnimation(); Overlays.editOverlay(standUpButton, { visible: false }); Overlays.editOverlay(kneelDownButton, { visible: true }); diff --git a/examples/theBird.js b/examples/theBird.js new file mode 100644 index 0000000000..c7fd5a38b7 --- /dev/null +++ b/examples/theBird.js @@ -0,0 +1,32 @@ +// +// theBird.js +// examples +// +// Created by Anthony Thibault on 11/9/2015 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +// Example of how to play an animation on an avatar. +// + +var THE_BIRD_RIGHT_URL = "https://hifi-public.s3.amazonaws.com/ozan/anim/the_bird/the_bird_right.fbx"; + +var roles = MyAvatar.getAnimationRoles(); +var i, l = roles.length +print("getAnimationRoles()"); +for (i = 0; i < l; i++) { + print(roles[i]); +} + +// replace point with the bird! +MyAvatar.overrideRoleAnimation("rightHandPointIntro", THE_BIRD_RIGHT_URL, 30, false, 0, 12); +MyAvatar.overrideRoleAnimation("rightHandPointHold", THE_BIRD_RIGHT_URL, 30, false, 12, 12); +MyAvatar.overrideRoleAnimation("rightHandPointOutro", THE_BIRD_RIGHT_URL, 30, false, 19, 30); + +Script.scriptEnding.connect(function() { + MyAvatar.restoreRoleAnimation("rightHandPointIntro"); + MyAvatar.restoreRoleAnimation("rightHandPointHold"); + MyAvatar.restoreRoleAnimation("rightHandPointOutro"); +}); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 14b66c5465..4dbc6494b8 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -657,21 +657,21 @@ void MyAvatar::loadLastRecording() { _player->loadRecording(_recorder->getRecording()); } -void MyAvatar::startAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame) { +void MyAvatar::overrideAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame) { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "startAnimation", Q_ARG(const QString&, url), Q_ARG(float, fps), + QMetaObject::invokeMethod(this, "overrideAnimation", Q_ARG(const QString&, url), Q_ARG(float, fps), Q_ARG(bool, loop), Q_ARG(float, firstFrame), Q_ARG(float, lastFrame)); return; } - _rig->startAnimation(url, fps, loop, firstFrame, lastFrame); + _rig->overrideAnimation(url, fps, loop, firstFrame, lastFrame); } -void MyAvatar::stopAnimation() { +void MyAvatar::restoreAnimation() { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "stopAnimation"); + QMetaObject::invokeMethod(this, "restoreAnimation"); return; } - _rig->stopAnimation(); + _rig->restoreAnimation(); } QStringList MyAvatar::getAnimationRoles() { @@ -683,22 +683,22 @@ QStringList MyAvatar::getAnimationRoles() { return _rig->getAnimationRoles(); } -void MyAvatar::overrideAnimationRole(const QString& role, const QString& url, float fps, bool loop, +void MyAvatar::overrideRoleAnimation(const QString& role, const QString& url, float fps, bool loop, float firstFrame, float lastFrame) { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "overrideAnimationRole", Q_ARG(const QString&, role), Q_ARG(const QString&, url), + QMetaObject::invokeMethod(this, "overrideRoleAnimation", Q_ARG(const QString&, role), Q_ARG(const QString&, url), Q_ARG(float, fps), Q_ARG(bool, loop), Q_ARG(float, firstFrame), Q_ARG(float, lastFrame)); return; } - _rig->overrideAnimationRole(role, url, fps, loop, firstFrame, lastFrame); + _rig->overrideRoleAnimation(role, url, fps, loop, firstFrame, lastFrame); } -void MyAvatar::restoreAnimationRole(const QString& role) { +void MyAvatar::restoreRoleAnimation(const QString& role) { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "restoreAnimationRole", Q_ARG(const QString&, role)); + QMetaObject::invokeMethod(this, "restoreRoleAnimation", Q_ARG(const QString&, role)); return; } - _rig->restoreAnimationRole(role); + _rig->restoreRoleAnimation(role); } void MyAvatar::prefetchAnimation(const QString& url) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 2e5f13f81d..1331291f03 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -119,19 +119,19 @@ public: void removeAnimationHandle(const AnimationHandlePointer& handle) { _rig->removeAnimationHandle(handle); } // Interrupt the current animation with a custom animation. - Q_INVOKABLE void startAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame); + Q_INVOKABLE void overrideAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame); - // Stops the animation that was started with startAnimation and go back to the standard animation. - Q_INVOKABLE void stopAnimation(); + // Stop the animation that was started with overrideAnimation and go back to the standard animation. + Q_INVOKABLE void restoreAnimation(); // Returns a list of all clips that are available Q_INVOKABLE QStringList getAnimationRoles(); // Replace an existing standard role animation with a custom one. - Q_INVOKABLE void overrideAnimationRole(const QString& role, const QString& url, float fps, bool loop, float firstFrame, float lastFrame); + Q_INVOKABLE void overrideRoleAnimation(const QString& role, const QString& url, float fps, bool loop, float firstFrame, float lastFrame); // remove an animation role override and return to the standard animation. - Q_INVOKABLE void restoreAnimationRole(const QString& role); + Q_INVOKABLE void restoreRoleAnimation(const QString& role); // prefetch animation Q_INVOKABLE void prefetchAnimation(const QString& url); diff --git a/libraries/animation/src/AnimNode.cpp b/libraries/animation/src/AnimNode.cpp index 864252ff47..80093e43db 100644 --- a/libraries/animation/src/AnimNode.cpp +++ b/libraries/animation/src/AnimNode.cpp @@ -8,12 +8,35 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include #include "AnimNode.h" -void AnimNode::removeChild(AnimNode::Pointer child) { +AnimNode::Pointer AnimNode::getParent() { + return _parent.lock(); +} + +void AnimNode::addChild(Pointer child) { + _children.push_back(child); + child->_parent = shared_from_this(); +} + +void AnimNode::removeChild(Pointer child) { auto iter = std::find(_children.begin(), _children.end(), child); if (iter != _children.end()) { _children.erase(iter); + child->_parent.reset(); + } +} + +void AnimNode::replaceChild(Pointer oldChild, Pointer newChild) { + auto iter = std::find(_children.begin(), _children.end(), oldChild); + if (iter != _children.end()) { + oldChild->_parent.reset(); + newChild->_parent = shared_from_this(); + if (_skeleton) { + newChild->setSkeleton(_skeleton); + } + *iter = newChild; } } @@ -22,7 +45,7 @@ AnimNode::Pointer AnimNode::getChild(int i) const { return _children[i]; } -void AnimNode::setSkeleton(const AnimSkeleton::Pointer skeleton) { +void AnimNode::setSkeleton(AnimSkeleton::ConstPointer skeleton) { setSkeletonInternal(skeleton); for (auto&& child : _children) { child->setSkeleton(skeleton); diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index f773651ddc..23f2e1c7b3 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -60,14 +60,15 @@ public: Type getType() const { return _type; } // hierarchy accessors - void addChild(Pointer child) { _children.push_back(child); } + Pointer getParent(); + void addChild(Pointer child); void removeChild(Pointer child); - + void replaceChild(Pointer oldChild, Pointer newChild); Pointer getChild(int i) const; int getChildCount() const { return (int)_children.size(); } // pair this AnimNode graph with a skeleton. - void setSkeleton(const AnimSkeleton::Pointer skeleton); + void setSkeleton(AnimSkeleton::ConstPointer skeleton); AnimSkeleton::ConstPointer getSkeleton() const { return _skeleton; } @@ -95,9 +96,9 @@ public: traverse([&](Pointer node) { if (id == node->getID()) { result = node; - return true; + return false; } - return false; + return true; }); return result; } @@ -114,6 +115,7 @@ protected: QString _id; std::vector _children; AnimSkeleton::ConstPointer _skeleton; + std::weak_ptr _parent; // no copies AnimNode(const AnimNode&) = delete; diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 2a52e04e1d..83f72b9b5a 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -393,9 +393,9 @@ AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QS return node; } -void buildChildMap(std::map& map, AnimNode::Pointer node) { - for ( auto child : node->_children ) { - map.insert(std::pair(child->_id, child)); +void buildChildMap(std::map& map, AnimNode::Pointer node) { + for (int i = 0; i < (int)node->getChildCount(); ++i) { + map.insert(std::pair(node->getChild(i)->getID(), i)); } } @@ -412,7 +412,7 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, } // build a map for all children by name. - std::map childMap; + std::map childMap; buildChildMap(childMap, node); // first pass parse all the states and build up the state and transition map. diff --git a/libraries/animation/src/AnimStateMachine.cpp b/libraries/animation/src/AnimStateMachine.cpp index c0bb66c2a0..3f5a81a861 100644 --- a/libraries/animation/src/AnimStateMachine.cpp +++ b/libraries/animation/src/AnimStateMachine.cpp @@ -46,7 +46,7 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, fl } assert(_currentState); - auto currentStateNode = _currentState->getNode(); + auto currentStateNode = _children[_currentState->getChildIndex()]; assert(currentStateNode); if (_duringInterp) { @@ -79,8 +79,8 @@ void AnimStateMachine::switchState(const AnimVariantMap& animVars, State::Pointe const float FRAMES_PER_SECOND = 30.0f; - auto prevStateNode = _currentState->getNode(); - auto nextStateNode = desiredState->getNode(); + auto prevStateNode = _children[_currentState->getChildIndex()]; + auto nextStateNode = _children[desiredState->getChildIndex()]; _duringInterp = true; _alpha = 0.0f; diff --git a/libraries/animation/src/AnimStateMachine.h b/libraries/animation/src/AnimStateMachine.h index cb6c99f067..dda56235d5 100644 --- a/libraries/animation/src/AnimStateMachine.h +++ b/libraries/animation/src/AnimStateMachine.h @@ -55,16 +55,16 @@ protected: State::Pointer _state; }; - State(const QString& id, AnimNode::Pointer node, float interpTarget, float interpDuration) : + State(const QString& id, int childIndex, float interpTarget, float interpDuration) : _id(id), - _node(node), + _childIndex(childIndex), _interpTarget(interpTarget), _interpDuration(interpDuration) {} void setInterpTargetVar(const QString& interpTargetVar) { _interpTargetVar = interpTargetVar; } void setInterpDurationVar(const QString& interpDurationVar) { _interpDurationVar = interpDurationVar; } - AnimNode::Pointer getNode() const { return _node; } + int getChildIndex() const { return _childIndex; } const QString& getID() const { return _id; } protected: @@ -75,7 +75,7 @@ protected: void addTransition(const Transition& transition) { _transitions.push_back(transition); } QString _id; - AnimNode::Pointer _node; + int _childIndex; float _interpTarget; // frames float _interpDuration; // frames diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 3d653985f3..e1cc5ec7f5 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -45,7 +45,7 @@ void Rig::removeAnimationHandle(const AnimationHandlePointer& handle) { _animationHandles.removeOne(handle); } -void Rig::startAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame) { +void Rig::overrideAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame) { if (_enableAnimGraph) { // find an unused AnimClip clipNode @@ -63,7 +63,8 @@ void Rig::startAnimation(const QString& url, float fps, bool loop, float firstFr clip->setStartFrame(firstFrame); clip->setEndFrame(lastFrame); const float REFERENCE_FRAMES_PER_SECOND = 30.0f; - clip->setTimeScale(fps / REFERENCE_FRAMES_PER_SECOND); + float timeScale = fps / REFERENCE_FRAMES_PER_SECOND; + clip->setTimeScale(timeScale); clip->loadURL(url); _currentUserAnimURL = url; @@ -107,7 +108,7 @@ void Rig::startAnimation(const QString& url, float fps, bool loop, float firstFr const float FRAMES_PER_SECOND = 30.0f; const float FADE_FRAMES = 30.0f; -void Rig::stopAnimation() { +void Rig::restoreAnimation() { if (_enableAnimGraph) { if (_currentUserAnimURL != "") { _currentUserAnimURL = ""; @@ -130,7 +131,14 @@ QStringList Rig::getAnimationRoles() const { if (_enableAnimGraph && _animNode) { QStringList list; _animNode->traverse([&](AnimNode::Pointer node) { - list.append(node->getID()); + // only report clip nodes as valid roles. + auto clipNode = std::dynamic_pointer_cast(node); + if (clipNode) { + // filter out the userAnims, they are for internal use only. + if (!clipNode->getID().startsWith("userAnim")) { + list.append(node->getID()); + } + } return true; }); return list; @@ -139,36 +147,33 @@ QStringList Rig::getAnimationRoles() const { } } -void Rig::overrideAnimationRole(const QString& role, const QString& url, float fps, bool loop, float firstFrame, float lastFrame) { +void Rig::overrideRoleAnimation(const QString& role, const QString& url, float fps, bool loop, float firstFrame, float lastFrame) { if (_enableAnimGraph && _animNode) { AnimNode::Pointer node = _animNode->findByName(role); if (node) { - //_previousRoleAnimations[role] = node; - // TODO: create clip node. - // TODO: AnimNode needs the following methods. - // Pointer getParent() const; - // void swapChild(Pointer child, Pointer newChild); - // - // pseudo code - // - // auto clipNode = std::make_shared(role, url, fps, firstFrame, lastFrame, loop); - // node->getParent()->swapChild(node, clipNode); - + _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); + AnimNode::Pointer parent = node->getParent(); + parent->replaceChild(node, clipNode); } else { - qCWarning(animation) << "Rig::overrideAnimationRole could not find role " << role; + qCWarning(animation) << "Rig::overrideRoleAnimation could not find role " << role; } } } -void Rig::restoreAnimationRole(const QString& role) { +void Rig::restoreRoleAnimation(const QString& role) { if (_enableAnimGraph && _animNode) { AnimNode::Pointer node = _animNode->findByName(role); if (node) { - // TODO: pseudo code - // origNode = _previousRoleAnimations.find(role); - // if (origNode) { - // node->getParent()->swapChild(node, origNode); - // } + auto iter = _origRoleAnimations.find(role); + if (iter != _origRoleAnimations.end()) { + node->getParent()->replaceChild(node, iter->second); + _origRoleAnimations.erase(iter); + } else { + qCWarning(animation) << "Rig::restoreRoleAnimation could not find role " << role; + } } } } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index eb7f07f653..cd5152152a 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -114,11 +114,11 @@ public: void destroyAnimGraph(); const QList& getAnimationHandles() const { return _animationHandles; } - void startAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame); - void stopAnimation(); + void overrideAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame); + void restoreAnimation(); QStringList getAnimationRoles() const; - void overrideAnimationRole(const QString& role, const QString& url, float fps, bool loop, float firstFrame, float lastFrame); - void restoreAnimationRole(const QString& role); + void overrideRoleAnimation(const QString& role, const QString& url, float fps, bool loop, float firstFrame, float lastFrame); + void restoreRoleAnimation(const QString& role); void prefetchAnimation(const QString& url); void initJointStates(QVector states, glm::mat4 rootTransform, @@ -264,6 +264,8 @@ public: SimpleMovingAverage _averageForwardSpeed { 10 }; SimpleMovingAverage _averageLateralSpeed { 10 }; + std::map _origRoleAnimations; + private: QMap _stateHandlers; int _nextStateHandlerId { 0 };