From eacc2cae0c99bf0a8b2874c4c6e69c12d119867d Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 9 Nov 2015 18:36:23 -0800 Subject: [PATCH] WIP checkpoint --- examples/kneel.js | 2 +- interface/src/Application.cpp | 8 -- interface/src/avatar/MyAvatar.cpp | 103 +++++++--------------- interface/src/avatar/MyAvatar.h | 29 ++++--- libraries/animation/src/AnimNode.h | 26 +++++- libraries/animation/src/Rig.cpp | 134 +++++++++++++---------------- libraries/animation/src/Rig.h | 14 ++- 7 files changed, 140 insertions(+), 176 deletions(-) diff --git a/examples/kneel.js b/examples/kneel.js index 5dd772c507..3270a77df7 100644 --- a/examples/kneel.js +++ b/examples/kneel.js @@ -66,7 +66,7 @@ function kneelDown() { function standUp() { kneeling = false; - MyAvatar.stopAnimation(KNEEL_ANIM_URL); + MyAvatar.stopAnimation(); Overlays.editOverlay(standUpButton, { visible: false }); Overlays.editOverlay(kneelDownButton, { visible: true }); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 7a564bbbf0..07ddd0a1f7 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4294,10 +4294,6 @@ void Application::stopAllScripts(bool restart) { it.value()->stop(); qCDebug(interfaceapp) << "stopping script..." << it.key(); } - // HACK: ATM scripts cannot set/get their animation priorities, so we clear priorities - // whenever a script stops in case it happened to have been setting joint rotations. - // TODO: expose animation priorities and provide a layered animation control system. - getMyAvatar()->clearJointAnimationPriorities(); getMyAvatar()->clearScriptableSettings(); } @@ -4313,10 +4309,6 @@ bool Application::stopScript(const QString& scriptHash, bool restart) { scriptEngine->stop(); stoppedScript = true; qCDebug(interfaceapp) << "stopping script..." << scriptHash; - // HACK: ATM scripts cannot set/get their animation priorities, so we clear priorities - // whenever a script stops in case it happened to have been setting joint rotations. - // TODO: expose animation priorities and provide a layered animation control system. - getMyAvatar()->clearJointAnimationPriorities(); } if (_scriptEnginesHash.empty()) { getMyAvatar()->clearScriptableSettings(); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index ab8b78d6e3..14b66c5465 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -666,65 +666,47 @@ void MyAvatar::startAnimation(const QString& url, float fps, bool loop, float fi _rig->startAnimation(url, fps, loop, firstFrame, lastFrame); } -void MyAvatar::startAnimationByRole(const QString& role, const QString& url, float fps, float priority, - bool loop, bool hold, float firstFrame, float lastFrame, const QStringList& maskedJoints) { +void MyAvatar::stopAnimation() { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "startAnimationByRole", Q_ARG(const QString&, role), Q_ARG(const QString&, url), - Q_ARG(float, fps), Q_ARG(float, priority), Q_ARG(bool, loop), Q_ARG(bool, hold), Q_ARG(float, firstFrame), - Q_ARG(float, lastFrame), Q_ARG(const QStringList&, maskedJoints)); + QMetaObject::invokeMethod(this, "stopAnimation"); return; } - _rig->startAnimationByRole(role, url, fps, priority, loop, hold, firstFrame, lastFrame, maskedJoints); + _rig->stopAnimation(); } -void MyAvatar::stopAnimationByRole(const QString& role) { +QStringList MyAvatar::getAnimationRoles() { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "stopAnimationByRole", Q_ARG(const QString&, role)); - return; - } - _rig->stopAnimationByRole(role); -} - -void MyAvatar::stopAnimation(const QString& url) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "stopAnimation", Q_ARG(const QString&, url)); - return; - } - _rig->stopAnimation(url); -} - -AnimationDetails MyAvatar::getAnimationDetailsByRole(const QString& role) { - AnimationDetails result; - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "getAnimationDetailsByRole", Qt::BlockingQueuedConnection, - Q_RETURN_ARG(AnimationDetails, result), - Q_ARG(const QString&, role)); + QStringList result; + QMetaObject::invokeMethod(this, "getAnimationRoles", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QStringList, result)); return result; } - foreach (const AnimationHandlePointer& handle, _rig->getRunningAnimations()) { - if (handle->getRole() == role) { - result = handle->getAnimationDetails(); - break; - } - } - return result; + return _rig->getAnimationRoles(); } -AnimationDetails MyAvatar::getAnimationDetails(const QString& url) { - AnimationDetails result; +void MyAvatar::overrideAnimationRole(const QString& role, const QString& url, float fps, bool loop, + float firstFrame, float lastFrame) { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "getAnimationDetails", Qt::BlockingQueuedConnection, - Q_RETURN_ARG(AnimationDetails, result), - Q_ARG(const QString&, url)); - return result; + QMetaObject::invokeMethod(this, "overrideAnimationRole", 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; } - foreach (const AnimationHandlePointer& handle, _rig->getRunningAnimations()) { - if (handle->getURL() == url) { - result = handle->getAnimationDetails(); - break; - } + _rig->overrideAnimationRole(role, url, fps, loop, firstFrame, lastFrame); +} + +void MyAvatar::restoreAnimationRole(const QString& role) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "restoreAnimationRole", Q_ARG(const QString&, role)); + return; } - return result; + _rig->restoreAnimationRole(role); +} + +void MyAvatar::prefetchAnimation(const QString& url) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "prefetchAnimation", Q_ARG(const QString&, url)); + return; + } + _rig->prefetchAnimation(url); } void MyAvatar::saveData() { @@ -797,6 +779,7 @@ float loadSetting(QSettings& settings, const char* name, float defaultValue) { // If we demand the animation from the update thread while we're locked, we'll deadlock. // Until we untangle this, code puts the updates back on the main thread temporarilly and starts all the loading. void MyAvatar::safelyLoadAnimations() { + /* _rig->addAnimationByRole("idle"); _rig->addAnimationByRole("walk"); _rig->addAnimationByRole("backup"); @@ -804,6 +787,7 @@ void MyAvatar::safelyLoadAnimations() { _rig->addAnimationByRole("rightTurn"); _rig->addAnimationByRole("leftStrafe"); _rig->addAnimationByRole("rightStrafe"); + */ } void MyAvatar::setEnableRigAnimations(bool isEnabled) { @@ -905,23 +889,6 @@ void MyAvatar::loadData() { settings.endArray(); setAttachmentData(attachmentData); - int animationCount = settings.beginReadArray("animationHandles"); - _rig->deleteAnimations(); - for (int i = 0; i < animationCount; i++) { - settings.setArrayIndex(i); - _rig->addAnimationByRole(settings.value("role", "idle").toString(), - settings.value("url").toString(), - loadSetting(settings, "fps", 30.0f), - loadSetting(settings, "priority", 1.0f), - settings.value("loop", true).toBool(), - settings.value("hold", false).toBool(), - settings.value("firstFrame", 0.0f).toFloat(), - settings.value("lastFrame", INT_MAX).toFloat(), - settings.value("maskedJoints").toStringList(), - settings.value("startAutomatically", true).toBool()); - } - settings.endArray(); - setDisplayName(settings.value("displayName").toString()); setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString()); @@ -1177,14 +1144,7 @@ void MyAvatar::clearJointData(int index) { } void MyAvatar::clearJointsData() { - clearJointAnimationPriorities(); -} - -void MyAvatar::clearJointAnimationPriorities() { - int numStates = _skeletonModel.getJointStateCount(); - for (int i = 0; i < numStates; ++i) { - _rig->clearJointAnimationPriority(i); - } + //clearJointAnimationPriorities(); } void MyAvatar::setFaceModelURL(const QUrl& faceModelURL) { @@ -1382,7 +1342,6 @@ void MyAvatar::setScriptedMotorFrame(QString frame) { } void MyAvatar::clearScriptableSettings() { - clearJointAnimationPriorities(); _scriptedMotorVelocity = glm::vec3(0.0f); _scriptedMotorTimescale = DEFAULT_SCRIPTED_MOTOR_TIMESCALE; } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 0a6fdb4af2..2e5f13f81d 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -117,22 +117,25 @@ public: const QList& getAnimationHandles() const { return _rig->getAnimationHandles(); } AnimationHandlePointer addAnimationHandle() { return _rig->createAnimationHandle(); } void removeAnimationHandle(const AnimationHandlePointer& handle) { _rig->removeAnimationHandle(handle); } - /// Allows scripts to run animations. + + // Interrupt the current animation with a custom animation. Q_INVOKABLE void startAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame); - /// Stops an animation as identified by a URL. - Q_INVOKABLE void stopAnimation(const QString& url); + // Stops the animation that was started with startAnimation and go back to the standard animation. + Q_INVOKABLE void stopAnimation(); + + // 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); + + // remove an animation role override and return to the standard animation. + Q_INVOKABLE void restoreAnimationRole(const QString& role); + + // prefetch animation + Q_INVOKABLE void prefetchAnimation(const QString& url); - /// Starts an animation by its role, using the provided URL and parameters if the avatar doesn't have a custom - /// animation for the role. - Q_INVOKABLE void startAnimationByRole(const QString& role, const QString& url = QString(), float fps = 30.0f, - float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f, - float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList()); - /// Stops an animation identified by its role. - Q_INVOKABLE void stopAnimationByRole(const QString& role); - Q_INVOKABLE AnimationDetails getAnimationDetailsByRole(const QString& role); - Q_INVOKABLE AnimationDetails getAnimationDetails(const QString& url); - void clearJointAnimationPriorities(); // Adds handler(animStateDictionaryIn) => animStateDictionaryOut, which will be invoked just before each animGraph state update. // The handler will be called with an animStateDictionaryIn that has all those properties specified by the (possibly empty) // propertiesList argument. However for debugging, if the properties argument is null, all internal animGraph state is provided. diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index ac3365c272..f773651ddc 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -33,7 +33,7 @@ class QJsonObject; // * evaluate method, perform actual joint manipulations here and return result by reference. // Also, append any triggers that are detected during evaluation. -class AnimNode { +class AnimNode : public std::enable_shared_from_this { public: enum class Type { Clip = 0, @@ -78,6 +78,30 @@ public: void setCurrentFrame(float frame); + template + bool traverse(F func) { + if (func(shared_from_this())) { + for (auto&& child : _children) { + if (!child->traverse(func)) { + return false; + } + } + } + return true; + } + + Pointer findByName(const QString& id) { + Pointer result; + traverse([&](Pointer node) { + if (id == node->getID()) { + result = node; + return true; + } + return false; + }); + return result; + } + protected: virtual void setCurrentFrameInternal(float frame) {} diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index e0369b23a4..3d653985f3 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -79,6 +79,8 @@ void Rig::startAnimation(const QString& url, float fps, bool loop, float firstFr bool hold = true; QStringList maskedJoints; + _currentUserAnimURL = url; + // This is different than startAnimationByRole, in which we use the existing values if the animation already exists. // Here we reuse the animation handle if possible, but in any case, we set the values to those given (or defaulted). AnimationHandlePointer handle = nullptr; @@ -102,79 +104,12 @@ void Rig::startAnimation(const QString& url, float fps, bool loop, float firstFr handle->start(); } } - -AnimationHandlePointer Rig::addAnimationByRole(const QString& role, const QString& url, float fps, float priority, - bool loop, bool hold, float firstFrame, float lastFrame, const QStringList& maskedJoints, bool startAutomatically) { - - // check for a configured animation for the role - //qCDebug(animation) << "addAnimationByRole" << role << url << fps << priority << loop << hold << firstFrame << lastFrame << maskedJoints << startAutomatically; - foreach (const AnimationHandlePointer& candidate, _animationHandles) { - if (candidate->getRole() == role) { - if (startAutomatically) { - candidate->start(); - } - return candidate; - } - } - AnimationHandlePointer handle = createAnimationHandle(); - QString standard = ""; - if (url.isEmpty()) { // Default animations for fight club - const QString& base = "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/"; - if (role == "walk") { - standard = base + "walk_fwd.fbx"; - } else if (role == "backup") { - standard = base + "walk_bwd.fbx"; - } else if (role == "leftTurn") { - standard = base + "turn_left.fbx"; - } else if (role == "rightTurn") { - standard = base + "turn_right.fbx"; - } else if (role == "leftStrafe") { - standard = base + "strafe_left.fbx"; - } else if (role == "rightStrafe") { - standard = base + "strafe_right.fbx"; - } else if (role == "idle") { - standard = base + "idle.fbx"; - fps = 25.0f; - } - if (!standard.isEmpty()) { - loop = true; - } - } - handle->setRole(role); - handle->setURL(url.isEmpty() ? standard : url); - handle->setFPS(fps); - handle->setPriority(priority); - handle->setLoop(loop); - handle->setHold(hold); - handle->setFirstFrame(firstFrame); - handle->setLastFrame(lastFrame); - handle->setMaskedJoints(maskedJoints); - if (startAutomatically) { - handle->start(); - } - return handle; -} - -const float FADE_FRAMES = 30.0f; const float FRAMES_PER_SECOND = 30.0f; +const float FADE_FRAMES = 30.0f; -void Rig::startAnimationByRole(const QString& role, const QString& url, float fps, float priority, - bool loop, bool hold, float firstFrame, float lastFrame, const QStringList& maskedJoints) { - AnimationHandlePointer handle = addAnimationByRole(role, url, fps, priority, loop, hold, firstFrame, lastFrame, maskedJoints, true); - handle->setFadePerSecond(FRAMES_PER_SECOND / FADE_FRAMES); // For now. Could be individualized later. -} - -void Rig::stopAnimationByRole(const QString& role) { - foreach (const AnimationHandlePointer& handle, getRunningAnimations()) { - if (handle->getRole() == role) { - handle->setFadePerSecond(-(FRAMES_PER_SECOND / FADE_FRAMES)); // For now. Could be individualized later. - } - } -} - -void Rig::stopAnimation(const QString& url) { +void Rig::stopAnimation() { if (_enableAnimGraph) { - if (url == _currentUserAnimURL) { + if (_currentUserAnimURL != "") { _currentUserAnimURL = ""; // notify the userAnimStateMachine the desired state. _animVars.set("userAnimNone", true); @@ -183,7 +118,7 @@ void Rig::stopAnimation(const QString& url) { } } else { foreach (const AnimationHandlePointer& handle, getRunningAnimations()) { - if (handle->getURL() == url) { + if (handle->getURL() == _currentUserAnimURL) { handle->setFade(0.0f); // right away. Will be remove during updateAnimations, without locking handle->setFadePerSecond(-(FRAMES_PER_SECOND / FADE_FRAMES)); // so that the updateAnimation code notices } @@ -191,6 +126,59 @@ void Rig::stopAnimation(const QString& url) { } } +QStringList Rig::getAnimationRoles() const { + if (_enableAnimGraph && _animNode) { + QStringList list; + _animNode->traverse([&](AnimNode::Pointer node) { + list.append(node->getID()); + return true; + }); + return list; + } else { + return QStringList(); + } +} + +void Rig::overrideAnimationRole(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); + + } else { + qCWarning(animation) << "Rig::overrideAnimationRole could not find role " << role; + } + } +} + +void Rig::restoreAnimationRole(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); + // } + } + } +} + +void Rig::prefetchAnimation(const QString& url) { + if (_enableAnimGraph) { + // TODO: + } +} + bool Rig::removeRunningAnimation(AnimationHandlePointer animationHandle) { return _runningAnimations.removeOne(animationHandle); } @@ -661,13 +649,13 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos if (isOn) { if (!isRunningRole(role)) { qCDebug(animation) << "Rig STARTING" << role; - startAnimationByRole(role); + //startAnimationByRole(role); } } else { if (isRunningRole(role)) { qCDebug(animation) << "Rig stopping" << role; - stopAnimationByRole(role); + //stopAnimationByRole(role); } } }; diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index f36dd19dda..eb7f07f653 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -113,15 +113,13 @@ public: void deleteAnimations(); void destroyAnimGraph(); const QList& getAnimationHandles() const { return _animationHandles; } + void startAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame); - void stopAnimation(const QString& url); - void startAnimationByRole(const QString& role, const QString& url = QString(), float fps = 30.0f, - float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f, - float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList()); - void stopAnimationByRole(const QString& role); - AnimationHandlePointer addAnimationByRole(const QString& role, const QString& url = QString(), float fps = 30.0f, - float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f, - float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList(), bool startAutomatically = false); + void stopAnimation(); + 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 prefetchAnimation(const QString& url); void initJointStates(QVector states, glm::mat4 rootTransform, int rootJointIndex,