From a1bbb63ec42478b42ffbf2c8d77cb1ae6e47e333 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 12 Apr 2016 14:51:17 -0700 Subject: [PATCH 1/3] Rig: save and restore user animations across resets --- libraries/animation/src/Rig.cpp | 61 +++++++++++++++++++++------------ libraries/animation/src/Rig.h | 27 +++++++++++---- 2 files changed, 61 insertions(+), 27 deletions(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 6a8f190808..a57f0b01d2 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -48,36 +48,47 @@ const glm::vec3 DEFAULT_NECK_POS(0.0f, 0.70f, 0.0f); void Rig::overrideAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame) { - // find an unused AnimClip clipNode - std::shared_ptr clip; - if (_userAnimState == UserAnimState::None || _userAnimState == UserAnimState::B) { - _userAnimState = UserAnimState::A; - clip = std::dynamic_pointer_cast(_animNode->findByName("userAnimA")); - } else if (_userAnimState == UserAnimState::A) { - _userAnimState = UserAnimState::B; - clip = std::dynamic_pointer_cast(_animNode->findByName("userAnimB")); + UserAnimState::ClipNodeEnum clipNodeEnum; + if (_userAnimState.clipNodeEnum == UserAnimState::None || _userAnimState.clipNodeEnum == UserAnimState::B) { + clipNodeEnum = UserAnimState::A; + } else { + clipNodeEnum = UserAnimState::B; } - // 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); + if (_animNode) { + // find an unused AnimClip clipNode + std::shared_ptr clip; + if (clipNodeEnum == UserAnimState::A) { + clip = std::dynamic_pointer_cast(_animNode->findByName("userAnimA")); + } else { + clip = std::dynamic_pointer_cast(_animNode->findByName("userAnimB")); + } - _currentUserAnimURL = url; + 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. + _userAnimState = { clipNodeEnum, url, fps, loop, firstFrame, lastFrame }; // notify the userAnimStateMachine the desired state. _animVars.set("userAnimNone", false); - _animVars.set("userAnimA", _userAnimState == UserAnimState::A); - _animVars.set("userAnimB", _userAnimState == UserAnimState::B); + _animVars.set("userAnimA", clipNodeEnum == UserAnimState::A); + _animVars.set("userAnimB", clipNodeEnum == UserAnimState::B); } void Rig::restoreAnimation() { - if (_currentUserAnimURL != "") { - _currentUserAnimURL = ""; + if (_userAnimState.clipNodeEnum != UserAnimState::None) { + _userAnimState.clipNodeEnum = UserAnimState::None; + // notify the userAnimStateMachine the desired state. _animVars.set("userAnimNone", true); _animVars.set("userAnimA", false); @@ -1129,6 +1140,14 @@ void Rig::initAnimGraph(const QUrl& url) { connect(_animLoader.get(), &AnimNodeLoader::success, [this](AnimNode::Pointer nodeIn) { _animNode = nodeIn; _animNode->setSkeleton(_animSkeleton); + + if (_userAnimState.clipNodeEnum != UserAnimState::None) { + // restore the user animation we had before reset. + UserAnimState origState = _userAnimState; + _userAnimState = { UserAnimState::None, "", 30.0f, false, 0.0f, 0.0f }; + overrideAnimation(origState.url, origState.fps, origState.loop, origState.firstFrame, origState.lastFrame); + } + }); connect(_animLoader.get(), &AnimNodeLoader::error, [url](int error, QString str) { qCCritical(animation) << "Error loading" << url.toDisplayString() << "code = " << error << "str =" << str; diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 3a27b9304b..cbf4696723 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -289,13 +289,28 @@ public: RigRole _state { RigRole::Idle }; RigRole _desiredState { RigRole::Idle }; float _desiredStateAge { 0.0f }; - enum class UserAnimState { - None = 0, - A, - B + + struct UserAnimState { + enum ClipNodeEnum { + None = 0, + A, + B + }; + + UserAnimState() : clipNodeEnum(UserAnimState::None) {} + UserAnimState(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) {} + + ClipNodeEnum clipNodeEnum; + QString url; + float fps; + bool loop; + float firstFrame; + float lastFrame; }; - UserAnimState _userAnimState { UserAnimState::None }; - QString _currentUserAnimURL; + + UserAnimState _userAnimState; + float _leftHandOverlayAlpha { 0.0f }; float _rightHandOverlayAlpha { 0.0f }; From 8d7bf87d740096dd031cca8f82e3fd75ad96e681 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 12 Apr 2016 14:52:22 -0700 Subject: [PATCH 2/3] away.js: simplify kneel animation handling Instead of manually setting anim vars to play the kneel animation and enable/disable IK, use the MyAvatar.overrideAnimation() and MyAvatar.restoreAnimtion() functions, which did not exist when away.js was written. This should make it more robust and eliminate the issue with IK being stuck in the disabled state. --- examples/away.js | 59 +++++++++++++++--------------------------------- 1 file changed, 18 insertions(+), 41 deletions(-) diff --git a/examples/away.js b/examples/away.js index 643edbd149..76b09045d8 100644 --- a/examples/away.js +++ b/examples/away.js @@ -34,49 +34,23 @@ var OVERLAY_DATA_HMD = { alpha: 1, scale: 2, isFacingAvatar: true, - drawInFront: true + drawInFront: true +}; + +var AWAY_INTRO = { + url: "http://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/kneel.fbx", + playbackRate: 30.0, + loopFlag: false, + startFrame: 0.0, + endFrame: 83.0 }; -// ANIMATION -// We currently don't have play/stopAnimation integrated with the animation graph, but we can get the same effect -// using an animation graph with a state that we turn on and off through the animation var defined with that state. -var awayAnimationHandlerId, activeAnimationHandlerId, stopper; function playAwayAnimation() { - function animateAway() { - return {isAway: true, isNotAway: false, isNotMoving: false, ikOverlayAlpha: 0.0}; - } - if (stopper) { - stopper = false; - MyAvatar.removeAnimationStateHandler(activeAnimationHandlerId); // do it now, before making new assignment - } - awayAnimationHandlerId = MyAvatar.addAnimationStateHandler(animateAway, null); + MyAvatar.overrideAnimation(AWAY_INTRO.url, AWAY_INTRO.playbackRate, AWAY_INTRO.loopFlag, AWAY_INTRO.startFrame, AWAY_INTRO.endFrame); } + function stopAwayAnimation() { - MyAvatar.removeAnimationStateHandler(awayAnimationHandlerId); - if (stopper) { - print('WARNING: unexpected double stop'); - return; - } - // How do we know when to turn ikOverlayAlpha back on? - // It cannot be as soon as we want to stop the away animation, because then things will look goofy as we come out of that animation. - // (Imagine an away animation that sits or kneels, and then stands back up when coming out of it. If head is at the HMD, then it won't - // want to track the standing up animation.) - // The anim graph will trigger awayOutroOnDone when awayOutro is finished. - var backToNormal = false; - stopper = true; - function animateActive(state) { - if (state.awayOutroOnDone) { - backToNormal = true; - stopper = false; - } else if (state.ikOverlayAlpha) { - // Once the right state gets reflected back to us, we don't need the hander any more. - // But we are locked against handler changes during the execution of a handler, so remove asynchronously. - Script.setTimeout(function () { MyAvatar.removeAnimationStateHandler(activeAnimationHandlerId); }, 0); - } - // It might be cool to "come back to life" by fading the ik overlay back in over a short time. But let's see how this goes. - return {isAway: false, isNotAway: true, ikOverlayAlpha: backToNormal ? 1.0 : 0.0}; // IWBNI we had a way of deleting an anim var. - } - activeAnimationHandlerId = MyAvatar.addAnimationStateHandler(animateActive, ['ikOverlayAlpha', 'awayOutroOnDone']); + MyAvatar.restoreAnimation(); } // OVERLAY @@ -112,15 +86,17 @@ function showOverlay() { var screen = Controller.getViewportDimensions(); // keep the overlay it's natural size and always center it... - Overlays.editOverlay(overlay, { visible: true, - x: ((screen.x - OVERLAY_WIDTH) / 2), + Overlays.editOverlay(overlay, { visible: true, + x: ((screen.x - OVERLAY_WIDTH) / 2), y: ((screen.y - OVERLAY_HEIGHT) / 2) }); } } + function hideOverlay() { Overlays.editOverlay(overlay, {visible: false}); Overlays.editOverlay(overlayHMD, {visible: false}); } + hideOverlay(); function maybeMoveOverlay() { @@ -163,6 +139,7 @@ function safeGetHMDMounted() { } return HMD.mounted; } + var wasHmdMounted = safeGetHMDMounted(); function goAway() { @@ -263,7 +240,7 @@ Script.update.connect(maybeGoAway); Controller.mousePressEvent.connect(goActive); Controller.keyPressEvent.connect(maybeGoActive); // Note peek() so as to not interfere with other mappings. -eventMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(goActive); +eventMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(goActive); eventMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(goActive); eventMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(goActive); eventMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(goActive); From 87cba810a72530c7f79bb3413dc81d5200a06044 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 12 Apr 2016 15:37:31 -0700 Subject: [PATCH 3/3] remove away states from avatar-animation.json away.js: prefetch the kneel animation, so it is available when we need it. --- examples/away.js | 3 + .../defaultAvatar_full/avatar-animation.json | 77 ------------------- 2 files changed, 3 insertions(+), 77 deletions(-) diff --git a/examples/away.js b/examples/away.js index 76b09045d8..68662be79d 100644 --- a/examples/away.js +++ b/examples/away.js @@ -45,6 +45,9 @@ var AWAY_INTRO = { endFrame: 83.0 }; +// prefetch the kneel animation so it's resident in memory when we need it. +MyAvatar.prefetchAnimation(AWAY_INTRO.url); + function playAwayAnimation() { MyAvatar.overrideAnimation(AWAY_INTRO.url, AWAY_INTRO.playbackRate, AWAY_INTRO.loopFlag, AWAY_INTRO.startFrame, AWAY_INTRO.endFrame); } diff --git a/interface/resources/meshes/defaultAvatar_full/avatar-animation.json b/interface/resources/meshes/defaultAvatar_full/avatar-animation.json index a4c0a7c446..40e8ec74a6 100644 --- a/interface/resources/meshes/defaultAvatar_full/avatar-animation.json +++ b/interface/resources/meshes/defaultAvatar_full/avatar-animation.json @@ -246,7 +246,6 @@ { "var": "isMovingLeft", "state": "strafeLeft" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, - { "var": "isAway", "state": "awayIntro" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, { "var": "isTakeoffRun", "state": "takeoffRun" }, @@ -266,7 +265,6 @@ { "var": "isMovingLeft", "state": "strafeLeft" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, - { "var": "isAway", "state": "awayIntro" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, { "var": "isTakeoffRun", "state": "takeoffRun" }, @@ -285,7 +283,6 @@ { "var": "isMovingLeft", "state": "strafeLeft" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, - { "var": "isAway", "state": "awayIntro" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, { "var": "isTakeoffRun", "state": "takeoffRun" }, @@ -304,7 +301,6 @@ { "var": "isMovingLeft", "state": "strafeLeft" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, - { "var": "isAway", "state": "awayIntro" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, { "var": "isTakeoffRun", "state": "takeoffRun" }, @@ -323,7 +319,6 @@ { "var": "isMovingLeft", "state": "strafeLeft" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, - { "var": "isAway", "state": "awayIntro" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, { "var": "isTakeoffRun", "state": "takeoffRun" }, @@ -342,7 +337,6 @@ { "var": "isMovingRight", "state": "strafeRight" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, - { "var": "isAway", "state": "awayIntro" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, { "var": "isTakeoffRun", "state": "takeoffRun" }, @@ -361,7 +355,6 @@ { "var": "isMovingRight", "state": "strafeRight" }, { "var": "isMovingLeft", "state": "strafeLeft" }, { "var": "isTurningLeft", "state": "turnLeft" }, - { "var": "isAway", "state": "awayIntro" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, { "var": "isTakeoffRun", "state": "takeoffRun" }, @@ -380,7 +373,6 @@ { "var": "isMovingRight", "state": "strafeRight" }, { "var": "isMovingLeft", "state": "strafeLeft" }, { "var": "isTurningRight", "state": "turnRight" }, - { "var": "isAway", "state": "awayIntro" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, { "var": "isTakeoffRun", "state": "takeoffRun" }, @@ -388,37 +380,11 @@ { "var": "isInAirRun", "state": "inAirRun" } ] }, - { - "id": "awayIntro", - "interpTarget": 30, - "interpDuration": 30, - "transitions": [ - { "var": "isNotAway", "state": "awayOutro" }, - { "var": "awayIntroOnDone", "state": "away"} - ] - }, - { - "id": "away", - "interpTarget": 3, - "interpDuration": 3, - "transitions": [ - { "var": "isNotAway", "state": "awayOutro" } - ] - }, - { - "id": "awayOutro", - "interpTarget": 3, - "interpDuration": 3, - "transitions": [ - { "var": "awayOutroOnDone", "state": "idle" } - ] - }, { "id": "fly", "interpTarget": 6, "interpDuration": 6, "transitions": [ - { "var": "isAway", "state": "awayIntro" }, { "var": "isNotFlying", "state": "idle" } ] }, @@ -427,7 +393,6 @@ "interpTarget": 0, "interpDuration": 6, "transitions": [ - { "var": "isAway", "state": "awayIntro" }, { "var": "isNotTakeoff", "state": "inAirStand" } ] }, @@ -436,7 +401,6 @@ "interpTarget": 0, "interpDuration": 6, "transitions": [ - { "var": "isAway", "state": "awayIntro" }, { "var": "isNotTakeoff", "state": "inAirRun" } ] }, @@ -446,7 +410,6 @@ "interpDuration": 6, "interpType": "snapshotPrev", "transitions": [ - { "var": "isAway", "state": "awayIntro" }, { "var": "isNotInAir", "state": "landStandImpact" } ] }, @@ -456,7 +419,6 @@ "interpDuration": 6, "interpType": "snapshotPrev", "transitions": [ - { "var": "isAway", "state": "awayIntro" }, { "var": "isNotInAir", "state": "landRun" } ] }, @@ -465,7 +427,6 @@ "interpTarget": 6, "interpDuration": 4, "transitions": [ - { "var": "isAway", "state": "awayIntro" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, { "var": "isTakeoffRun", "state": "takeoffRun" }, @@ -483,7 +444,6 @@ { "var": "isMovingLeft", "state": "strafeLeft" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, - { "var": "isAway", "state": "awayIntro" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, { "var": "isTakeoffRun", "state": "takeoffRun" }, @@ -497,7 +457,6 @@ "interpTarget": 1, "interpDuration": 7, "transitions": [ - { "var": "isAway", "state": "awayIntro" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, { "var": "isTakeoffRun", "state": "takeoffRun" }, @@ -754,42 +713,6 @@ } ] }, - { - "id": "awayIntro", - "type": "clip", - "data": { - "url": "http://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/kneel.fbx", - "startFrame": 0.0, - "endFrame": 83.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "away", - "type": "clip", - "data": { - "url": "http://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/kneel.fbx", - "startFrame": 83.0, - "endFrame": 84.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "awayOutro", - "type": "clip", - "data": { - "url": "http://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/kneel.fbx", - "startFrame": 84.0, - "endFrame": 167.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, { "id": "fly", "type": "clip",