diff --git a/interface/resources/icons/loadingDark.gif b/interface/resources/icons/loadingDark.gif new file mode 100644 index 0000000000..1fc22dc637 Binary files /dev/null and b/interface/resources/icons/loadingDark.gif differ diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index 554df82d9e..0927d46fdf 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -91,7 +91,7 @@ Item { } MouseArea { anchors.fill: parent - enabled: (selected || isMyCard) && activeTab == "nearbyTab"; + enabled: (selected && activeTab == "nearbyTab") || isMyCard; hoverEnabled: enabled onClicked: { userInfoViewer.url = defaultBaseUrl + "/users/" + userName; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 99af5b1578..9c523f53e8 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -6417,7 +6417,7 @@ void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRa // If we're not doing an animated snapshot as well... if (!includeAnimated || !(SnapshotAnimated::alsoTakeAnimatedSnapshot.get())) { // Tell the dependency manager that the capture of the still snapshot has taken place. - emit DependencyManager::get()->snapshotTaken(path, "", notify); + emit DependencyManager::get()->stillSnapshotTaken(path, notify); } else { // Get an animated GIF snapshot and save it SnapshotAnimated::saveSnapshotAnimated(path, aspectRatio, qApp, DependencyManager::get()); diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index b7bed7d85f..d4ff278fea 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -71,9 +71,10 @@ signals: void domainChanged(const QString& domainHostname); void svoImportRequested(const QString& url); void domainConnectionRefused(const QString& reasonMessage, int reasonCode, const QString& extraInfo); - void snapshotTaken(const QString& pathStillSnapshot, const QString& pathAnimatedSnapshot, bool notify); + void stillSnapshotTaken(const QString& pathStillSnapshot, bool notify); void snapshotShared(const QString& error); - void processingGif(); + void processingGifStarted(const QString& pathStillSnapshot); + void processingGifCompleted(const QString& pathAnimatedSnapshot); void connectionAdded(const QString& connectionName); void connectionError(const QString& errorString); diff --git a/interface/src/ui/SnapshotAnimated.cpp b/interface/src/ui/SnapshotAnimated.cpp index cf89504d92..70767b007d 100644 --- a/interface/src/ui/SnapshotAnimated.cpp +++ b/interface/src/ui/SnapshotAnimated.cpp @@ -86,7 +86,8 @@ void SnapshotAnimated::captureFrames() { SnapshotAnimated::snapshotAnimatedTimerRunning = false; // Notify the user that we're processing the snapshot - emit SnapshotAnimated::snapshotAnimatedDM->processingGif(); + // This also pops up the "Share" dialog. The unprocessed GIF will be visualized as a loading icon until processingGifCompleted() is called. + emit SnapshotAnimated::snapshotAnimatedDM->processingGifStarted(SnapshotAnimated::snapshotStillPath); // Kick off the thread that'll pack the frames into the GIF QtConcurrent::run(processFrames); @@ -132,7 +133,7 @@ void SnapshotAnimated::processFrames() { // Reset the current frame timestamp SnapshotAnimated::snapshotAnimatedTimestamp = 0; SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp = 0; - - // Let the window scripting interface know that the snapshots have been taken. - emit SnapshotAnimated::snapshotAnimatedDM->snapshotTaken(SnapshotAnimated::snapshotStillPath, SnapshotAnimated::snapshotAnimatedPath, false); + + // Update the "Share" dialog with the processed GIF. + emit SnapshotAnimated::snapshotAnimatedDM->processingGifCompleted(SnapshotAnimated::snapshotAnimatedPath); } diff --git a/libraries/shared/src/SimpleMovingAverage.h b/libraries/shared/src/SimpleMovingAverage.h index 858ee21371..0404ab9646 100644 --- a/libraries/shared/src/SimpleMovingAverage.h +++ b/libraries/shared/src/SimpleMovingAverage.h @@ -71,7 +71,7 @@ public: void addSample(T sample) { if (numSamples > 0) { - average = ((float)sample * WEIGHTING) + ((float)average * ONE_MINUS_WEIGHTING); + average = (sample * WEIGHTING) + (average * ONE_MINUS_WEIGHTING); } else { average = sample; } diff --git a/plugins/hifiKinect/src/KinectPlugin.cpp b/plugins/hifiKinect/src/KinectPlugin.cpp index 6d29a261dd..3a36be0982 100644 --- a/plugins/hifiKinect/src/KinectPlugin.cpp +++ b/plugins/hifiKinect/src/KinectPlugin.cpp @@ -24,7 +24,6 @@ Q_DECLARE_LOGGING_CATEGORY(inputplugins) Q_LOGGING_CATEGORY(inputplugins, "hifi.inputplugins") - const char* KinectPlugin::NAME = "Kinect"; const char* KinectPlugin::KINECT_ID_STRING = "Kinect"; @@ -493,11 +492,23 @@ void KinectPlugin::ProcessBody(INT64 time, int bodyCount, IBody** bodies) { //_joints[j].orientation = jointOrientation; if (joints[j].JointType == JointType_HandRight) { static const quat kinectToHandRight = glm::angleAxis(-PI / 2.0f, Vectors::UNIT_Y); - _joints[j].orientation = jointOrientation * kinectToHandRight; + // add moving average of orientation quaternion + glm::quat jointSample = jointOrientation * kinectToHandRight; + if (glm::dot(jointSample, _RightHandOrientationAverage.getAverage()) < 0) { + jointSample = -jointSample; + } + _RightHandOrientationAverage.addSample(jointSample); + _joints[j].orientation = glm::normalize(_RightHandOrientationAverage.getAverage()); } else if (joints[j].JointType == JointType_HandLeft) { // To transform from Kinect to our LEFT Hand.... Postive 90 deg around Y static const quat kinectToHandLeft = glm::angleAxis(PI / 2.0f, Vectors::UNIT_Y); - _joints[j].orientation = jointOrientation * kinectToHandLeft; + // add moving average of orientation quaternion + glm::quat jointSample = jointOrientation * kinectToHandLeft; + if (glm::dot(jointSample, _LeftHandOrientationAverage.getAverage()) < 0) { + jointSample = -jointSample; + } + _LeftHandOrientationAverage.addSample(jointSample); + _joints[j].orientation = glm::normalize(_LeftHandOrientationAverage.getAverage()); } else { _joints[j].orientation = jointOrientation; } @@ -643,4 +654,4 @@ void KinectPlugin::InputDevice::clearState() { int poseIndex = KinectJointIndexToPoseIndex((KinectJointIndex)i); _poseStateMap[poseIndex] = controller::Pose(); } -} +} \ No newline at end of file diff --git a/plugins/hifiKinect/src/KinectPlugin.h b/plugins/hifiKinect/src/KinectPlugin.h index 90794fa6b0..158e66ee62 100644 --- a/plugins/hifiKinect/src/KinectPlugin.h +++ b/plugins/hifiKinect/src/KinectPlugin.h @@ -23,6 +23,7 @@ // Kinect Header files #include +#include // Safe release for interfaces template inline void SafeRelease(Interface *& pInterfaceToRelease) { @@ -58,6 +59,11 @@ public: virtual void saveSettings() const override; virtual void loadSettings() override; +private: + // add variables for moving average + ThreadSafeMovingAverage _LeftHandOrientationAverage; + ThreadSafeMovingAverage _RightHandOrientationAverage; + protected: struct KinectJoint { diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index d97207384a..4e2e0223a0 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -21,6 +21,7 @@ function addImage(data) { img = document.createElement("IMG"), div2 = document.createElement("DIV"), id = "p" + idCounter++; + img.id = id + "img"; function toggle() { data.share = input.checked; } div.style.height = "" + Math.floor(100 / imageCount) + "%"; if (imageCount > 1) { @@ -33,7 +34,7 @@ function addImage(data) { label.setAttribute('for', id); // cannot do label.for = input.id = id; input.type = "checkbox"; - input.checked = (id === "p0"); + input.checked = false; data.share = input.checked; input.addEventListener('change', toggle); div2.setAttribute("class", "property checkbox"); @@ -46,9 +47,9 @@ function addImage(data) { document.getElementById("snapshot-images").appendChild(div); paths.push(data); } -function handleShareButtons(shareMsg) { +function handleShareButtons(messageOptions) { var openFeed = document.getElementById('openFeed'); - openFeed.checked = shareMsg.openFeedAfterShare; + openFeed.checked = messageOptions.openFeedAfterShare; openFeed.onchange = function () { EventBridge.emitWebEvent(JSON.stringify({ type: "snapshot", @@ -56,7 +57,7 @@ function handleShareButtons(shareMsg) { })); }; - if (!shareMsg.canShare) { + if (!messageOptions.canShare) { // this means you may or may not be logged in, but can't share // because you are not in a public place. document.getElementById("sharing").innerHTML = "

Snapshots can be shared when they're taken in shareable places."; @@ -74,13 +75,26 @@ window.onload = function () { return; } - // last element of list contains a bool for whether or not we can share stuff - var shareMsg = message.action.pop(); - handleShareButtons(shareMsg); - - // rest are image paths which we add - imageCount = message.action.length; - message.action.forEach(addImage); + // The last element of the message contents list contains a bunch of options, + // including whether or not we can share stuff + // The other elements of the list contain image paths. + var messageOptions = message.action.pop(); + handleShareButtons(messageOptions); + + if (messageOptions.containsGif) { + if (messageOptions.processingGif) { + imageCount = message.action.length + 1; // "+1" for the GIF that'll finish processing soon + message.action.unshift({ localPath: '../../../resources/icons/loadingDark.gif' }); + message.action.forEach(addImage); + document.getElementById('p0').disabled = true; + } else { + document.getElementById('p0').disabled = false; + document.getElementById('p0img').src = message.action[0].localPath; + } + } else { + imageCount = message.action.length; + message.action.forEach(addImage); + } }); EventBridge.emitWebEvent(JSON.stringify({ type: "snapshot", diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index 25a5edf3a3..006ef3f90f 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -532,7 +532,7 @@ function onNotify(msg) { createNotification(wordWrap(msg), NotificationType.UNKNOWN); // Needs a generic notification system for user feedback, thus using this } -function onSnapshotTaken(pathStillSnapshot, pathAnimatedSnapshot, notify) { +function onSnapshotTaken(pathStillSnapshot, notify) { if (notify) { var imageProperties = { path: "file:///" + pathStillSnapshot, @@ -656,8 +656,8 @@ Script.update.connect(update); Script.scriptEnding.connect(scriptEnding); Menu.menuItemEvent.connect(menuItemEvent); Window.domainConnectionRefused.connect(onDomainConnectionRefused); -Window.snapshotTaken.connect(onSnapshotTaken); -Window.processingGif.connect(processingGif); +Window.stillSnapshotTaken.connect(onSnapshotTaken); +Window.processingGifStarted.connect(processingGif); Window.connectionAdded.connect(connectionAdded); Window.connectionError.connect(connectionError); Window.notifyEditError = onEditError; diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 76f72d97e1..ee441278aa 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -157,7 +157,9 @@ function onClicked() { resetOverlays = Menu.isOptionChecked("Overlays"); // For completness. Certainly true if the button is visible to be clicke. reticleVisible = Reticle.visible; Reticle.visible = false; - Window.snapshotTaken.connect(resetButtons); + Window.stillSnapshotTaken.connect(stillSnapshotTaken); + Window.processingGifStarted.connect(processingGifStarted); + Window.processingGifCompleted.connect(processingGifCompleted); // hide overlays if they are on if (resetOverlays) { @@ -193,25 +195,14 @@ function isDomainOpen(id) { response.total_entries; } -function resetButtons(pathStillSnapshot, pathAnimatedSnapshot, notify) { - // If we're not taking an animated snapshot, we have to show the HUD. - // If we ARE taking an animated snapshot, we've already re-enabled the HUD by this point. - if (pathAnimatedSnapshot === "") { - // show hud - - Reticle.visible = reticleVisible; - // show overlays if they were on - if (resetOverlays) { - Menu.setIsOptionChecked("Overlays", true); - } - } else { - // Allow the user to click the snapshot HUD button again - if (!buttonConnected) { - button.clicked.connect(onClicked); - buttonConnected = true; - } +function stillSnapshotTaken(pathStillSnapshot, notify) { + // show hud + Reticle.visible = reticleVisible; + // show overlays if they were on + if (resetOverlays) { + Menu.setIsOptionChecked("Overlays", true); } - Window.snapshotTaken.disconnect(resetButtons); + Window.stillSnapshotTaken.disconnect(stillSnapshotTaken); // A Snapshot Review dialog might be left open indefinitely after taking the picture, // during which time the user may have moved. So stash that info in the dialog so that @@ -220,12 +211,11 @@ function resetButtons(pathStillSnapshot, pathAnimatedSnapshot, notify) { var confirmShareContents = [ { localPath: pathStillSnapshot, href: href }, { + containsGif: false, + processingGif: false, canShare: !!isDomainOpen(domainId), openFeedAfterShare: shouldOpenFeedAfterShare() }]; - if (pathAnimatedSnapshot !== "") { - confirmShareContents.unshift({ localPath: pathAnimatedSnapshot, href: href }); - } confirmShare(confirmShareContents); if (clearOverlayWhenMoving) { MyAvatar.setClearOverlayWhenMoving(true); // not until after the share dialog @@ -233,15 +223,51 @@ function resetButtons(pathStillSnapshot, pathAnimatedSnapshot, notify) { HMD.openTablet(); } -function processingGif() { - // show hud - Reticle.visible = reticleVisible; +function processingGifStarted(pathStillSnapshot) { + Window.processingGifStarted.disconnect(processingGifStarted); button.clicked.disconnect(onClicked); buttonConnected = false; + // show hud + Reticle.visible = reticleVisible; // show overlays if they were on if (resetOverlays) { Menu.setIsOptionChecked("Overlays", true); } + + var confirmShareContents = [ + { localPath: pathStillSnapshot, href: href }, + { + containsGif: true, + processingGif: true, + canShare: !!isDomainOpen(domainId), + openFeedAfterShare: shouldOpenFeedAfterShare() + }]; + confirmShare(confirmShareContents); + if (clearOverlayWhenMoving) { + MyAvatar.setClearOverlayWhenMoving(true); // not until after the share dialog + } + HMD.openTablet(); +} + +function processingGifCompleted(pathAnimatedSnapshot) { + Window.processingGifCompleted.disconnect(processingGifCompleted); + button.clicked.connect(onClicked); + buttonConnected = true; + + var confirmShareContents = [ + { localPath: pathAnimatedSnapshot, href: href }, + { + containsGif: true, + processingGif: false, + canShare: !!isDomainOpen(domainId), + openFeedAfterShare: shouldOpenFeedAfterShare() + }]; + readyData = confirmShareContents; + + tablet.emitScriptEvent(JSON.stringify({ + type: "snapshot", + action: readyData + })); } function onTabletScreenChanged(type, url) { @@ -265,7 +291,6 @@ function onConnected() { button.clicked.connect(onClicked); buttonConnected = true; Window.snapshotShared.connect(snapshotShared); -Window.processingGif.connect(processingGif); tablet.screenChanged.connect(onTabletScreenChanged); Account.usernameChanged.connect(onConnected); Script.scriptEnding.connect(function () { @@ -277,7 +302,6 @@ Script.scriptEnding.connect(function () { tablet.removeButton(button); } Window.snapshotShared.disconnect(snapshotShared); - Window.processingGif.disconnect(processingGif); tablet.screenChanged.disconnect(onTabletScreenChanged); });