From afc76fec72c25497976b060a63f13d36deaa5cbf Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Mon, 22 Oct 2018 15:20:32 -0700 Subject: [PATCH] Added overrideNetworkAnimation and restoreNetworkAnimation methods --- .../resources/avatar/network-animation.json | 196 ++++++++++-------- interface/src/avatar/AvatarManager.cpp | 8 +- interface/src/avatar/MyAvatar.cpp | 17 ++ interface/src/avatar/MyAvatar.h | 41 ++++ libraries/animation/src/Rig.cpp | 143 ++++++++----- libraries/animation/src/Rig.h | 14 +- 6 files changed, 275 insertions(+), 144 deletions(-) diff --git a/interface/resources/avatar/network-animation.json b/interface/resources/avatar/network-animation.json index 0ba4ea465c..3db40742c9 100644 --- a/interface/resources/avatar/network-animation.json +++ b/interface/resources/avatar/network-animation.json @@ -1,70 +1,138 @@ { "version": "1.1", "root": { - "id": "userAnimStateMachine", + "id": "networkAnimStateMachine", "type": "stateMachine", "data": { - "currentState": "idleAnim", + "currentState": "transitAnimStateMachine", "states": [ { - "id": "idleAnim", + "id": "transitAnimStateMachine", "interpTarget": 6, "interpDuration": 6, "transitions": [ - { "var": "postTransitAnim", "state": "postTransitAnim" }, - { "var": "preTransitAnim", "state": "preTransitAnim" } + { "var": "userNetworkAnimA", "state": "userNetworkAnimA" }, + { "var": "userNetworkAnimB", "state": "userNetworkAnimB" } ] }, { - "id": "preTransitAnim", + "id": "userNetworkAnimA", "interpTarget": 6, "interpDuration": 6, "transitions": [ - { "var": "idleAnim", "state": "idleAnim" }, - { "var": "transitAnim", "state": "transitAnim" } + { "var": "transitAnimStateMachine", "state": "transitAnimStateMachine" }, + { "var": "userNetworkAnimB", "state": "userNetworkAnimB" } ] }, { - "id": "transitAnim", + "id": "userNetworkAnimB", "interpTarget": 6, "interpDuration": 6, "transitions": [ - { "var": "preTransitAnim", "state": "preTransitAnim" }, - { "var": "postTransitAnim", "state": "postTransitAnim" } - ] - }, - { - "id": "postTransitAnim", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { "var": "transitAnim", "state": "transitAnim" }, - { "var": "idleAnim", "state": "idleAnim" } - ] - }, - { - "id": "userAnimA", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { "var": "idleAnim", "state": "idleAnim" }, - { "var": "userAnimB", "state": "userAnimB" } - ] - }, - { - "id": "userAnimB", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { "var": "idleAnim", "state": "idleAnim" }, - { "var": "userAnimA", "state": "userAnimA" } + { "var": "transitAnimStateMachine", "state": "transitAnimStateMachine" }, + { "var": "userNetworkAnimA", "state": "userNetworkAnimA" } ] } ] }, "children": [ { - "id": "idleAnim", + "id": "transitAnimStateMachine", + "type": "stateMachine", + "data": { + "currentState": "idleAnim", + "states": [ + { + "id": "idleAnim", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "postTransitAnim", "state": "postTransitAnim" }, + { "var": "preTransitAnim", "state": "preTransitAnim" } + ] + }, + { + "id": "preTransitAnim", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "idleAnim", "state": "idleAnim" }, + { "var": "transitAnim", "state": "transitAnim" } + ] + }, + { + "id": "transitAnim", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "preTransitAnim", "state": "preTransitAnim" }, + { "var": "postTransitAnim", "state": "postTransitAnim" } + ] + }, + { + "id": "postTransitAnim", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "transitAnim", "state": "transitAnim" }, + { "var": "idleAnim", "state": "idleAnim" } + ] + } + ] + }, + "children" : [ + { + "id": "idleAnim", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle.fbx", + "startFrame": 0.0, + "endFrame": 90.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "preTransitAnim", + "type": "clip", + "data": { + "url": "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx", + "startFrame": 0.0, + "endFrame": 10.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "transitAnim", + "type": "clip", + "data": { + "url": "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx", + "startFrame": 11.0, + "endFrame": 11.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "postTransitAnim", + "type": "clip", + "data": { + "url": "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx", + "startFrame": 22.0, + "endFrame": 49.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + } + ] + }, + { + "id": "userNetworkAnimA", "type": "clip", "data": { "url": "qrc:///avatar/animations/idle.fbx", @@ -76,55 +144,7 @@ "children": [] }, { - "id": "preTransitAnim", - "type": "clip", - "data": { - "url": "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx", - "startFrame": 0.0, - "endFrame": 10.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "transitAnim", - "type": "clip", - "data": { - "url": "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx", - "startFrame": 11.0, - "endFrame": 11.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "postTransitAnim", - "type": "clip", - "data": { - "url": "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx", - "startFrame": 22.0, - "endFrame": 49.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "userAnimA", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/idle.fbx", - "startFrame": 0.0, - "endFrame": 90.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "userAnimB", + "id": "userNetworkAnimB", "type": "clip", "data": { "url": "qrc:///avatar/animations/idle.fbx", diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index e12ea67230..0e1dc947f1 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -128,19 +128,19 @@ void AvatarManager::handleTransitAnimations(AvatarTransit::Status status) { switch (status) { case AvatarTransit::Status::STARTED: qDebug() << "START_FRAME"; - _myAvatar->getSkeletonModel()->getRig().triggerNetworkAnimation("preTransitAnim"); + _myAvatar->getSkeletonModel()->getRig().triggerNetworkRole("preTransitAnim"); break; case AvatarTransit::Status::START_TRANSIT: qDebug() << "START_TRANSIT"; - _myAvatar->getSkeletonModel()->getRig().triggerNetworkAnimation("transitAnim"); + _myAvatar->getSkeletonModel()->getRig().triggerNetworkRole("transitAnim"); break; case AvatarTransit::Status::END_TRANSIT: qDebug() << "END_TRANSIT"; - _myAvatar->getSkeletonModel()->getRig().triggerNetworkAnimation("postTransitAnim"); + _myAvatar->getSkeletonModel()->getRig().triggerNetworkRole("postTransitAnim"); break; case AvatarTransit::Status::ENDED: qDebug() << "END_FRAME"; - _myAvatar->getSkeletonModel()->getRig().triggerNetworkAnimation("idleAnim"); + _myAvatar->getSkeletonModel()->getRig().triggerNetworkRole("idleAnim"); break; case AvatarTransit::Status::PRE_TRANSIT: break; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 82baec628d..c502bcb40d 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1162,6 +1162,15 @@ void MyAvatar::overrideAnimation(const QString& url, float fps, bool loop, float _skeletonModel->getRig().overrideAnimation(url, fps, loop, firstFrame, lastFrame); } +void MyAvatar::overrideNetworkAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "overrideNetworkAnimation", Q_ARG(const QString&, url), Q_ARG(float, fps), + Q_ARG(bool, loop), Q_ARG(float, firstFrame), Q_ARG(float, lastFrame)); + return; + } + _skeletonModel->getRig().overrideNetworkAnimation(url, fps, loop, firstFrame, lastFrame); +} + void MyAvatar::restoreAnimation() { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "restoreAnimation"); @@ -1170,6 +1179,14 @@ void MyAvatar::restoreAnimation() { _skeletonModel->getRig().restoreAnimation(); } +void MyAvatar::restoreNetworkAnimation() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "restoreNetworkAnimation"); + return; + } + _skeletonModel->getRig().restoreNetworkAnimation(); +} + QStringList MyAvatar::getAnimationRoles() { if (QThread::currentThread() != thread()) { QStringList result; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 08a7c09fa4..64cc3e6357 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -393,6 +393,47 @@ public: */ Q_INVOKABLE void restoreAnimation(); + /**jsdoc + * Similarly to {@link MyAvatar.overrideAnimation}, this function will override the default avatar animations, + * but only affecting the data the gets sent to other clients over the network. In the client, the avatar will move according + * to the default animations but other clients will render your avatar movements according to the new animation. + * To continue sending the default animations, use {@link MyAvatar.restoreNetworkAnimation}.
+ *

Note: When using pre-built animation data, it's critical that the joint orientation of the source animation and target + * rig are equivalent, since the animation data applies absolute values onto the joints. If the orientations are different, + * the avatar will move in unpredictable ways. For more information about avatar joint orientation standards, see + * Avatar Standards.

+ * @function MyAvatar.overrideNetworkAnimation + * @param url {string} The URL to the animation file. Animation files need to be .FBX format, but only need to contain the + * avatar skeleton and animation data. + * @param fps {number} The frames per second (FPS) rate for the animation playback. 30 FPS is normal speed. + * @param loop {boolean} Set to true if the animation should loop. + * @param firstFrame {number} The frame the animation should start at. + * @param lastFrame {number} The frame the animation should end at. + * @example Make your avatar clap only on other clients for three seconds. + * // Clap your hands for 3 seconds then restore animation back to the avatar. + * var ANIM_URL = "https://s3.amazonaws.com/hifi-public/animations/ClapAnimations/ClapHands_Standing.fbx"; + * MyAvatar.overrideNetworkAnimation(ANIM_URL, 30, true, 0, 53); + * Script.setTimeout(function () { + * MyAvatar.restoreNetworkAnimation(); + * }, 3000); + */ + Q_INVOKABLE void overrideNetworkAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame); + + /**jsdoc + * Similarly to {@link MyAvatar.restoreAnimation} restoreNetworkAnimation() will restore any network animation + * that was previously overriten using the function {@link MyAvatar.overrideNetworkAnimation} + * If you aren't currently playing an override network animation, this function will have no effect. + * @function MyAvatar.restoreNetworkAnimation + * @example Make your avatar clap only on other clients for three seconds. + * // Clap your hands for 3 seconds then restore animation back to the avatar. + * var ANIM_URL = "https://s3.amazonaws.com/hifi-public/animations/ClapAnimations/ClapHands_Standing.fbx"; + * MyAvatar.overrideNetworkAnimation(ANIM_URL, 30, true, 0, 53); + * Script.setTimeout(function () { + * MyAvatar.restoreNetworkAnimation(); + * }, 3000); + */ + Q_INVOKABLE void restoreNetworkAnimation(); + /**jsdoc * Each avatar has an avatar-animation.json file that defines which animations are used and how they are blended together with procedural data * (such as look at vectors, hand sensors etc.). Each animation specified in the avatar-animation.json file is known as an animation role. diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index d198550d9a..a75366c725 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -134,30 +134,6 @@ void Rig::overrideAnimation(const QString& url, float fps, bool loop, float firs _animVars.set("userAnimB", clipNodeEnum == UserAnimState::B); } -void Rig::triggerNetworkAnimation(const QString& animName) { - _networkVars.set("idleAnim", false); - _networkVars.set("preTransitAnim", false); - _networkVars.set("transitAnim", false); - _networkVars.set("postTransitAnim", false); - _sendNetworkNode = true; - - if (animName == "idleAnim") { - _networkVars.set("idleAnim", true); - _networkAnimState.clipNodeEnum = NetworkAnimState::Idle; - _sendNetworkNode = false; - } else if (animName == "preTransitAnim") { - _networkVars.set("preTransitAnim", true); - _networkAnimState.clipNodeEnum = NetworkAnimState::PreTransit; - } else if (animName == "transitAnim") { - _networkVars.set("transitAnim", true); - _networkAnimState.clipNodeEnum = NetworkAnimState::Transit; - } else if (animName == "postTransitAnim") { - _networkVars.set("postTransitAnim", true); - _networkAnimState.clipNodeEnum = NetworkAnimState::PostTransit; - } - -} - void Rig::restoreAnimation() { if (_userAnimState.clipNodeEnum != UserAnimState::None) { _userAnimState.clipNodeEnum = UserAnimState::None; @@ -169,13 +145,87 @@ void Rig::restoreAnimation() { } } -void Rig::restoreNetworkAnimation() { - if (_networkAnimState.clipNodeEnum != NetworkAnimState::Idle) { - _networkAnimState.clipNodeEnum = NetworkAnimState::Idle; +void Rig::overrideNetworkAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame) { + + NetworkAnimState::ClipNodeEnum clipNodeEnum; + if (_networkAnimState.clipNodeEnum == NetworkAnimState::None || _networkAnimState.clipNodeEnum == NetworkAnimState::B) { + clipNodeEnum = NetworkAnimState::A; + } else if (_networkAnimState.clipNodeEnum == NetworkAnimState::A) { + clipNodeEnum = NetworkAnimState::B; + } + + if (_networkNode) { + // find an unused AnimClip clipNode + std::shared_ptr clip; + if (clipNodeEnum == NetworkAnimState::A) { + clip = std::dynamic_pointer_cast(_networkNode->findByName("userNetworkAnimA")); + } else { + clip = std::dynamic_pointer_cast(_networkNode->findByName("userNetworkAnimB")); + } + if (clip) { + // set parameters + clip->setLoopFlag(loop); + clip->setStartFrame(firstFrame); + clip->setEndFrame(lastFrame); + const float REFERENCE_FRAMES_PER_SECOND = 30.0f; + float timeScale = fps / REFERENCE_FRAMES_PER_SECOND; + clip->setTimeScale(timeScale); + clip->loadURL(url); + } + } + + // store current user anim state. + _networkAnimState = { clipNodeEnum, url, fps, loop, firstFrame, lastFrame }; + + // notify the userAnimStateMachine the desired state. + _networkVars.set("transitAnimStateMachine", false); + _networkVars.set("userNetworkAnimA", clipNodeEnum == NetworkAnimState::A); + _networkVars.set("userNetworkAnimB", clipNodeEnum == NetworkAnimState::B); + if (!_computeNetworkAnimation) { + _networkAnimState.blendTime = 0.0f; + _computeNetworkAnimation = true; + } +} + +void Rig::triggerNetworkRole(const QString& role) { + _networkVars.set("transitAnimStateMachine", false); + _networkVars.set("idleAnim", false); + _networkVars.set("userNetworkAnimA", false); + _networkVars.set("userNetworkAnimB", false); + _networkVars.set("preTransitAnim", false); + _networkVars.set("preTransitAnim", false); + _networkVars.set("transitAnim", false); + _networkVars.set("postTransitAnim", false); + _computeNetworkAnimation = true; + if (role == "idleAnim") { _networkVars.set("idleAnim", true); - _networkVars.set("preTransitAnim", false); - _networkVars.set("transitAnim", false); - _networkVars.set("postTransitAnim", false); + _networkAnimState.clipNodeEnum = NetworkAnimState::None; + _computeNetworkAnimation = false; + _networkAnimState.blendTime = 0.0f; + } else if (role == "preTransitAnim") { + _networkVars.set("preTransitAnim", true); + _networkAnimState.clipNodeEnum = NetworkAnimState::PreTransit; + _networkAnimState.blendTime = 0.0f; + } else if (role == "transitAnim") { + _networkVars.set("transitAnim", true); + _networkAnimState.clipNodeEnum = NetworkAnimState::Transit; + } else if (role == "postTransitAnim") { + _networkVars.set("postTransitAnim", true); + _networkAnimState.clipNodeEnum = NetworkAnimState::PostTransit; + } + +} + +void Rig::restoreNetworkAnimation() { + if (_networkAnimState.clipNodeEnum != NetworkAnimState::None) { + if (_computeNetworkAnimation) { + _networkAnimState.blendTime = 0.0f; + _computeNetworkAnimation = false; + } + _networkAnimState.clipNodeEnum = NetworkAnimState::None; + _networkVars.set("transitAnimStateMachine", true); + _networkVars.set("userNetworkAnimA", false); + _networkVars.set("userNetworkAnimB", false); } } @@ -1123,23 +1173,20 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons AnimVariantMap networkTriggersOut; _internalPoseSet._relativePoses = _animNode->evaluate(_animVars, context, deltaTime, triggersOut); if (_networkNode) { - _networkPoseSet._relativePoses = _networkNode->evaluate(_networkVars, context, deltaTime, networkTriggersOut); + float alpha = 1.0f; std::shared_ptr clip; - if (_networkAnimState.clipNodeEnum == NetworkAnimState::PreTransit) { - clip = std::dynamic_pointer_cast(_networkNode->findByName("preTransitAnim")); - if (clip) { - alpha = (clip->getFrame() - clip->getStartFrame()) / 6.0f; - } - } else if (_networkAnimState.clipNodeEnum == NetworkAnimState::PostTransit) { - clip = std::dynamic_pointer_cast(_networkNode->findByName("postTransitAnim")); - if (clip) { - alpha = (clip->getEndFrame() - clip->getFrame()) / 6.0f; - } - } + + const float TOTAL_BLEND_TIME = 0.2f; + _sendNetworkNode = _computeNetworkAnimation || _networkAnimState.blendTime < TOTAL_BLEND_TIME; if (_sendNetworkNode) { + _networkPoseSet._relativePoses = _networkNode->evaluate(_networkVars, context, deltaTime, networkTriggersOut); + _networkAnimState.blendTime += deltaTime; + alpha = _computeNetworkAnimation ? (_networkAnimState.blendTime / TOTAL_BLEND_TIME) : (1.0f - (_networkAnimState.blendTime / TOTAL_BLEND_TIME)); + alpha = alpha > 1.0f ? 1.0f : alpha; + alpha = alpha < 0.0f ? 0.0f : alpha; for (size_t i = 0; i < _networkPoseSet._relativePoses.size(); i++) { - _networkPoseSet._relativePoses[i].blend(_internalPoseSet._relativePoses[i], (alpha > 1.0f ? 1.0f : alpha)); + _networkPoseSet._relativePoses[i].blend(_internalPoseSet._relativePoses[i], alpha); } } } @@ -1809,16 +1856,16 @@ void Rig::initAnimGraph(const QUrl& url) { return; } _networkNode->setSkeleton(sharedSkeletonPtr); - if (_networkAnimState.clipNodeEnum != NetworkAnimState::Idle) { + if (_networkAnimState.clipNodeEnum != NetworkAnimState::None) { // restore the user animation we had before reset. NetworkAnimState origState = _networkAnimState; - _networkAnimState = { NetworkAnimState::Idle, "", 30.0f, false, 0.0f, 0.0f }; + _networkAnimState = { NetworkAnimState::None, "", 30.0f, false, 0.0f, 0.0f }; if (_networkAnimState.clipNodeEnum == NetworkAnimState::PreTransit) { - triggerNetworkAnimation("preTransitAnim"); + triggerNetworkRole("preTransitAnim"); } else if (_networkAnimState.clipNodeEnum == NetworkAnimState::Transit) { - triggerNetworkAnimation("transitAnim"); + triggerNetworkRole("transitAnim"); } else if (_networkAnimState.clipNodeEnum == NetworkAnimState::PostTransit) { - triggerNetworkAnimation("postTransitAnim"); + triggerNetworkRole("postTransitAnim"); } } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 7efc58fb4d..81577be0d5 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -113,8 +113,10 @@ public: void destroyAnimGraph(); void overrideAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame); - void triggerNetworkAnimation(const QString& animName); void restoreAnimation(); + + void overrideNetworkAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame); + void triggerNetworkRole(const QString& role); void restoreNetworkAnimation(); QStringList getAnimationRoles() const; @@ -325,12 +327,14 @@ protected: struct NetworkAnimState { enum ClipNodeEnum { - Idle = 0, + None = 0, PreTransit, Transit, - PostTransit + PostTransit, + A, + B }; - NetworkAnimState() : clipNodeEnum(NetworkAnimState::Idle) {} + NetworkAnimState() : clipNodeEnum(NetworkAnimState::None) {} NetworkAnimState(ClipNodeEnum clipNodeEnumIn, const QString& urlIn, float fpsIn, bool loopIn, float firstFrameIn, float lastFrameIn) : clipNodeEnum(clipNodeEnumIn), url(urlIn), fps(fpsIn), loop(loopIn), firstFrame(firstFrameIn), lastFrame(lastFrameIn) {} @@ -340,6 +344,7 @@ protected: bool loop; float firstFrame; float lastFrame; + float blendTime; }; struct UserAnimState { @@ -409,6 +414,7 @@ protected: int _rigId; bool _headEnabled { false }; + bool _computeNetworkAnimation { false }; bool _sendNetworkNode { false }; AnimContext _lastContext;