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
This commit is contained in:
Anthony J. Thibault 2015-11-10 16:34:38 -08:00
parent eacc2cae0c
commit 936c55a94e
11 changed files with 128 additions and 64 deletions

View file

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

32
examples/theBird.js Normal file
View file

@ -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");
});

View file

@ -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) {

View file

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

View file

@ -8,12 +8,35 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QtGlobal>
#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);

View file

@ -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<AnimNode::Pointer> _children;
AnimSkeleton::ConstPointer _skeleton;
std::weak_ptr<AnimNode> _parent;
// no copies
AnimNode(const AnimNode&) = delete;

View file

@ -393,9 +393,9 @@ AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QS
return node;
}
void buildChildMap(std::map<QString, AnimNode::Pointer>& map, AnimNode::Pointer node) {
for ( auto child : node->_children ) {
map.insert(std::pair<QString, AnimNode::Pointer>(child->_id, child));
void buildChildMap(std::map<QString, int>& map, AnimNode::Pointer node) {
for (int i = 0; i < (int)node->getChildCount(); ++i) {
map.insert(std::pair<QString, int>(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<QString, AnimNode::Pointer> childMap;
std::map<QString, int> childMap;
buildChildMap(childMap, node);
// first pass parse all the states and build up the state and transition map.

View file

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

View file

@ -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

View file

@ -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<AnimClip>(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<AnimClip>(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<AnimClip>(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;
}
}
}
}

View file

@ -114,11 +114,11 @@ public:
void destroyAnimGraph();
const QList<AnimationHandlePointer>& 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<JointState> states, glm::mat4 rootTransform,
@ -264,6 +264,8 @@ public:
SimpleMovingAverage _averageForwardSpeed { 10 };
SimpleMovingAverage _averageLateralSpeed { 10 };
std::map<QString, AnimNode::Pointer> _origRoleAnimations;
private:
QMap<int, StateHandler> _stateHandlers;
int _nextStateHandlerId { 0 };