mirror of
https://github.com/overte-org/overte.git
synced 2025-04-20 03:44:02 +02:00
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:
parent
eacc2cae0c
commit
936c55a94e
11 changed files with 128 additions and 64 deletions
|
@ -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
32
examples/theBird.js
Normal 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");
|
||||
});
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
|
|
Loading…
Reference in a new issue