diff --git a/examples/away.js b/examples/away.js index 0997eac05b..9c5aed98fa 100644 --- a/examples/away.js +++ b/examples/away.js @@ -35,49 +35,26 @@ var OVERLAY_DATA_HMD = { alpha: 1, scale: 2, isFacingAvatar: true, - drawInFront: true + drawInFront: true }; -// 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; +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 +}; + +// prefetch the kneel animation so it's resident in memory when we need it. +MyAvatar.prefetchAnimation(AWAY_INTRO.url); + 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 @@ -113,15 +90,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() { @@ -171,6 +150,7 @@ function safeGetHMDMounted() { } return HMD.mounted; } + var wasHmdMounted = safeGetHMDMounted(); function goAway() { @@ -277,7 +257,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); 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", 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 };