From 9ad19a2eaf031d95e9be1a6059aafb4790bed4ff Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Tue, 31 May 2016 14:57:31 -0700 Subject: [PATCH 001/171] Draw grab balls in front of entities (not just the lines as before). Don't intersect with grab balls (not just lasers as before). Don't run grab lasers through (2d or 3d) overlays. Don't try to manage reticle in handControllerGrab because other scripts do. --- .../system/controllers/handControllerGrab.js | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 06549a38b5..be4bffe58c 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -249,6 +249,18 @@ function entityIsGrabbedByOther(entityID) { return false; } +// If another script is managing the reticle (as is done by HandControllerPointer), we should not be setting it here, +// and we should not be showing lasers when someone else is using the Reticle to indicate a 2D minor mode. +var EXTERNALLY_MANAGED_2D_MINOR_MODE = true; +function isIn2DMode() { + return EXTERNALLY_MANAGED_2D_MINOR_MODE && Reticle.visible; +} +function restore2DMode() { + if (!EXTERNALLY_MANAGED_2D_MINOR_MODE) { + Reticle.setVisible(true); + } +} + function MyController(hand) { this.hand = hand; if (this.hand === RIGHT_HAND) { @@ -302,7 +314,10 @@ function MyController(hand) { this.update = function() { this.updateSmoothedTrigger(); - + if (isIn2DMode()) { + _this.turnOffVisualizations(); + return; + } switch (this.state) { case STATE_OFF: this.off(); @@ -425,6 +440,8 @@ function MyController(hand) { color: color, alpha: SEARCH_SPHERE_ALPHA, solid: true, + ignoreRayIntersection: true, + drawInFront: true, // Even when burried inside of something, show it. visible: true } this.searchSphere = Overlays.addOverlay("sphere", sphereProperties); @@ -447,6 +464,8 @@ function MyController(hand) { color: color, alpha: 0.1, solid: true, + ignoreRayIntersection: true, + drawInFront: true, // Even when burried inside of something, show it. visible: true } this.grabSphere = Overlays.addOverlay("sphere", sphereProperties); @@ -477,6 +496,7 @@ function MyController(hand) { end: farPoint, color: color, ignoreRayIntersection: true, // always ignore this + drawInFront: true, // Even when burried inside of something, show it. visible: true, alpha: 1 }; @@ -490,6 +510,7 @@ function MyController(hand) { color: color, visible: true, ignoreRayIntersection: true, // always ignore this + drawInFront: true, // Even when burried inside of something, show it. alpha: 1 }); } @@ -759,8 +780,7 @@ function MyController(hand) { this.particleBeamOff(); } this.searchSphereOff(); - - Reticle.setVisible(true); + restore2DMode(); }; @@ -901,9 +921,16 @@ function MyController(hand) { } else { intersection = Entities.findRayIntersection(pickRayBacked, true); } + var overlayIntersection = Overlays.findRayIntersection(pickRayBacked); + if (!intersection.intersects || (overlayIntersection.intersects && (intersection.distance > overlayIntersection.distance))) { + intersection = overlayIntersection; + } + // If we want to share results with other scripts, this is where we would do it. if (intersection.intersects) { - rayPickedCandidateEntities.push(intersection.entityID); + if (intersection.entityID) { + rayPickedCandidateEntities.push(intersection.entityID); + } this.intersectionDistance = Vec3.distance(pickRay.origin, intersection.intersection); } } From c2858f847baea7f9fe8e8f5e161772f98e659ef0 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Tue, 31 May 2016 16:57:23 -0700 Subject: [PATCH 002/171] Partition vive trackpad by center, x, and y (not just center v outer). --- interface/resources/controllers/vive.json | 8 ++++---- .../src/controllers/StandardControls.h | 6 ++++-- plugins/openvr/src/ViveControllerManager.cpp | 20 ++++++++++++------- plugins/openvr/src/ViveControllerManager.h | 2 +- 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 60a46ba3ce..df78ce7e0d 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -1,16 +1,16 @@ { "name": "Vive to Standard", "channels": [ - { "from": "Vive.LY", "when": "Vive.LSOuter", "filters": ["invert"], "to": "Standard.LY" }, - { "from": "Vive.LX", "when": "Vive.LSOuter", "to": "Standard.LX" }, + { "from": "Vive.LY", "when": "Vive.LSY", "filters": ["invert"], "to": "Standard.LY" }, + { "from": "Vive.LX", "when": "Vive.LSX", "to": "Standard.LX" }, { "from": "Vive.LT", "to": "Standard.LT" }, { "from": "Vive.LeftGrip", "to": "Standard.LB" }, { "from": "Vive.LS", "to": "Standard.LS" }, { "from": "Vive.LSTouch", "to": "Standard.LSTouch" }, - { "from": "Vive.RY", "when": "Vive.RSOuter", "filters": ["invert"], "to": "Standard.RY" }, - { "from": "Vive.RX", "when": "Vive.RSOuter", "to": "Standard.RX" }, + { "from": "Vive.RY", "when": "Vive.RSY", "filters": ["invert"], "to": "Standard.RY" }, + { "from": "Vive.RX", "when": "Vive.RSX", "to": "Standard.RX" }, { "from": "Vive.RT", "to": "Standard.RT" }, { "from": "Vive.RightGrip", "to": "Standard.RB" }, diff --git a/libraries/controllers/src/controllers/StandardControls.h b/libraries/controllers/src/controllers/StandardControls.h index 79c23bc6ee..4e0269bef1 100644 --- a/libraries/controllers/src/controllers/StandardControls.h +++ b/libraries/controllers/src/controllers/StandardControls.h @@ -44,7 +44,8 @@ namespace controller { LS_TOUCH, LEFT_THUMB_UP, LS_CENTER, - LS_OUTER, + LS_X, + LS_Y, RIGHT_PRIMARY_THUMB, RIGHT_SECONDARY_THUMB, @@ -53,7 +54,8 @@ namespace controller { RS_TOUCH, RIGHT_THUMB_UP, RS_CENTER, - RS_OUTER, + RS_X, + RS_Y, LEFT_PRIMARY_INDEX, LEFT_SECONDARY_INDEX, diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 6e75454b5f..f006c436e1 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -284,20 +284,24 @@ void ViveControllerManager::InputDevice::handleHandController(float deltaTime, u } // pseudo buttons the depend on both of the above for-loops - partitionTouchpad(controller::LS, controller::LX, controller::LY, controller::LS_CENTER, controller::LS_OUTER); - partitionTouchpad(controller::RS, controller::RX, controller::RY, controller::RS_CENTER, controller::RS_OUTER); + partitionTouchpad(controller::LS, controller::LX, controller::LY, controller::LS_CENTER, controller::LS_X, controller::LS_Y); + partitionTouchpad(controller::RS, controller::RX, controller::RY, controller::RS_CENTER, controller::RS_X, controller::RS_Y); } } } -void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPseudoButton, int outerPseudoButton) { +void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPseudoButton, int xPseudoButton, int yPesudoButton) { // Populate the L/RS_CENTER/OUTER pseudo buttons, corresponding to a partition of the L/RS space based on the X/Y values. const float CENTER_DEADBAND = 0.6f; + const float DIAGONAL_DIVIDE_IN_RADIANS = PI / 4.0f; if (_buttonPressedMap.find(sButton) != _buttonPressedMap.end()) { float absX = abs(_axisStateMap[xAxis]); float absY = abs(_axisStateMap[yAxis]); - bool isCenter = (absX < CENTER_DEADBAND) && (absY < CENTER_DEADBAND); // square deadband - _buttonPressedMap.insert(isCenter ? centerPseudoButton : outerPseudoButton); + glm::vec2 cartesianQuadrantI(absX, absY); + float angle = glm::atan(cartesianQuadrantI.y / cartesianQuadrantI.x); + float radius = glm::length(cartesianQuadrantI); + bool isCenter = radius < CENTER_DEADBAND; + _buttonPressedMap.insert(isCenter ? centerPseudoButton : ((angle < DIAGONAL_DIVIDE_IN_RADIANS) ? xPseudoButton :yPesudoButton)); } } @@ -460,9 +464,11 @@ controller::Input::NamedVector ViveControllerManager::InputDevice::getAvailableI makePair(RS, "RS"), // Differentiate where we are in the touch pad click makePair(LS_CENTER, "LSCenter"), - makePair(LS_OUTER, "LSOuter"), + makePair(LS_X, "LSX"), + makePair(LS_Y, "LSY"), makePair(RS_CENTER, "RSCenter"), - makePair(RS_OUTER, "RSOuter"), + makePair(RS_X, "RSX"), + makePair(RS_Y, "RSY"), // triggers makePair(LT, "LT"), diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index bd5d4a39f4..e14a10a94e 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -61,7 +61,7 @@ private: void handleAxisEvent(float deltaTime, uint32_t axis, float x, float y, bool isLeftHand); void handlePoseEvent(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, const mat4& mat, const vec3& linearVelocity, const vec3& angularVelocity, bool isLeftHand); - void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPsuedoButton, int outerPseudoButton); + void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPsuedoButton, int xPseudoButton, int yPseudoButton); class FilteredStick { public: From 922468ce1c3777b9cd89fac03da8d94d7a86fa55 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Tue, 31 May 2016 20:47:58 -0700 Subject: [PATCH 003/171] fix cut and paste error with extraneous namespace qualifiers. --- plugins/openvr/src/ViveControllerManager.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index e14a10a94e..f788e3f37c 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -61,7 +61,7 @@ private: void handleAxisEvent(float deltaTime, uint32_t axis, float x, float y, bool isLeftHand); void handlePoseEvent(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, const mat4& mat, const vec3& linearVelocity, const vec3& angularVelocity, bool isLeftHand); - void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPsuedoButton, int xPseudoButton, int yPseudoButton); + void partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPsuedoButton, int xPseudoButton, int yPseudoButton); class FilteredStick { public: From f0133013c267ae319c660e8083546c16880faf00 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Wed, 1 Jun 2016 07:30:26 -0700 Subject: [PATCH 004/171] typo --- plugins/openvr/src/ViveControllerManager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index f006c436e1..ba0ac88dfb 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -290,7 +290,7 @@ void ViveControllerManager::InputDevice::handleHandController(float deltaTime, u } } -void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPseudoButton, int xPseudoButton, int yPesudoButton) { +void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPseudoButton, int xPseudoButton, int yPseudoButton) { // Populate the L/RS_CENTER/OUTER pseudo buttons, corresponding to a partition of the L/RS space based on the X/Y values. const float CENTER_DEADBAND = 0.6f; const float DIAGONAL_DIVIDE_IN_RADIANS = PI / 4.0f; @@ -301,7 +301,7 @@ void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxi float angle = glm::atan(cartesianQuadrantI.y / cartesianQuadrantI.x); float radius = glm::length(cartesianQuadrantI); bool isCenter = radius < CENTER_DEADBAND; - _buttonPressedMap.insert(isCenter ? centerPseudoButton : ((angle < DIAGONAL_DIVIDE_IN_RADIANS) ? xPseudoButton :yPesudoButton)); + _buttonPressedMap.insert(isCenter ? centerPseudoButton : ((angle < DIAGONAL_DIVIDE_IN_RADIANS) ? xPseudoButton :yPseudoButton)); } } From 84fa4402fd62772f247486b70018f75eb25f38c5 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 1 Jun 2016 14:03:49 -0700 Subject: [PATCH 005/171] Click on full trigger, activate on partial trigger. --- .../controllers/handControllerPointer.js | 94 +++++++++++++++---- 1 file changed, 78 insertions(+), 16 deletions(-) diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index ca3b5e8cf2..5cbde82654 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -47,22 +47,66 @@ function TimeLock(expiration) { } var handControllerLockOut = new TimeLock(2000); -// Calls onFunction() or offFunction() when swtich(on), but only if it is to a new value. -function LatchedToggle(onFunction, offFunction, state) { - this.getState = function () { - return state; +function Trigger() { + // This part is copied and adapted from handControllerGrab.js. Maybe we should refactor this. + var that = this; + that.TRIGGER_SMOOTH_RATIO = 0.1; // Time averaging of trigger - 0.0 disables smoothing + that.TRIGGER_ON_VALUE = 0.4; // Squeezed just enough to activate search or near grab + that.TRIGGER_GRAB_VALUE = 0.85; // Squeezed far enough to complete distant grab + that.TRIGGER_OFF_VALUE = 0.15; + that.rawTriggerValue = 0; + that.triggerValue = 0; // rolling average of trigger value + that.triggerPress = function (value) { + that.rawTriggerValue = value; }; - this.setState = function (on) { - if (state === on) { - return; - } - state = on; - if (on) { - onFunction(); - } else { - offFunction(); - } + that.updateSmoothedTrigger = function () { // e.g., call once/update for effect + var triggerValue = that.rawTriggerValue; + // smooth out trigger value + that.triggerValue = (that.triggerValue * that.TRIGGER_SMOOTH_RATIO) + + (triggerValue * (1.0 - that.TRIGGER_SMOOTH_RATIO)); }; + // Current smoothed state, without hysteresis. Answering booleans. + that.triggerSmoothedGrab = function () { + return that.triggerValue > that.TRIGGER_GRAB_VALUE; + }; + that.triggerSmoothedSqueezed = function () { + return that.triggerValue > that.TRIGGER_ON_VALUE; + }; + that.triggerSmoothedReleased = function () { + return that.triggerValue < that.TRIGGER_OFF_VALUE; + }; + + // This part is not from handControllerGrab.js + that.state = null; // tri-state: falsey, 'partial', 'full' + that.update = function () { // update state, called from an update function + var state = that.state; + that.updateSmoothedTrigger(); + + // The first two are independent of previous state: + if (that.triggerSmoothedGrab()) { + state = 'full'; + } else if (that.triggerSmoothedReleased()) { + state = null; + // These depend on previous state: + // null -> squeezed ==> partial + // full -> !squeezed ==> partial + // Otherwise no change. + } else if (that.triggerSmoothedSqueezed()) { + if (!state) { + state = 'partial'; + } + } else if (state === 'full') { + state = 'partial'; + } + that.state = state; + }; + // Answer a controller source function (answering either 0.0 or 1.0), with hysteresis. + that.partial = function () { + return that.state ? 1.0 : 0.0; // either 'partial' or 'full' + }; + that.full = function () { + return (that.state === 'full') ? 1.0 : 0.0; + } } // VERTICAL FIELD OF VIEW --------- @@ -257,28 +301,41 @@ setupHandler(Controller.mouseDoublePressEvent, onMouseClick); // CONTROLLER MAPPING --------- // +var leftTrigger = new Trigger(); +var rightTrigger = new Trigger(); +var activeTrigger = rightTrigger; var activeHand = Controller.Standard.RightHand; function toggleHand() { if (activeHand === Controller.Standard.RightHand) { activeHand = Controller.Standard.LeftHand; + activeTrigger = leftTrigger; } else { activeHand = Controller.Standard.RightHand; + activeTrigger = rightTrigger; } } var clickMapping = Controller.newMapping(Script.resolvePath('') + '-click'); Script.scriptEnding.connect(clickMapping.disable); +// Gather the trigger data for smoothing. +clickMapping.from(Controller.Standard.RT).peek().to(rightTrigger.triggerPress); +clickMapping.from(Controller.Standard.LT).peek().to(leftTrigger.triggerPress); +// The next two lines will be removed soon. Right now I want both trigger and button so we can compare. clickMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(Controller.Actions.ReticleClick); clickMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(Controller.Actions.ReticleClick); +// Full somoothed trigger is a click. +clickMapping.from(rightTrigger.full).to(Controller.Actions.ReticleClick); +clickMapping.from(leftTrigger.full).to(Controller.Actions.ReticleClick); clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(Controller.Actions.ContextMenu); clickMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(Controller.Actions.ContextMenu); -clickMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(function (on) { +// Partial smoothed trigger is activation. +clickMapping.from(rightTrigger.partial).to(function (on) { if (on && (activeHand !== Controller.Standard.RightHand)) { toggleHand(); } }); -clickMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(function (on) { +clickMapping.from(leftTrigger.partial).to(function (on) { if (on && (activeHand !== Controller.Standard.LeftHand)) { toggleHand(); } @@ -359,6 +416,8 @@ function update() { if (!Window.hasFocus()) { // Don't mess with other apps return turnOffVisualization(); } + leftTrigger.update(); + rightTrigger.update(); var controllerPose = Controller.getPoseValue(activeHand); // Valid if any plugged-in hand controller is "on". (uncradled Hydra, green-lighted Vive...) if (!controllerPose.valid) { @@ -390,6 +449,9 @@ function update() { return turnOffVisualization(true); } // We are not pointing at a HUD element (but it could be a 3d overlay). + if (!activeTrigger.state) { + return turnOffVisualization(); // No trigger (with hysteresis). + } updateVisualization(controllerPosition, controllerDirection, hudPoint3d, hudPoint2d); } From e6cc1fabe0d97006024e38de4a498b1f364fa860 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 2 Jun 2016 12:38:54 -0700 Subject: [PATCH 006/171] Do not send stale vive data as valid after deactivating. --- plugins/openvr/src/OpenVrDisplayPlugin.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index dba8fca208..a74e5458af 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -96,6 +96,11 @@ void OpenVrDisplayPlugin::internalDeactivate() { Parent::internalDeactivate(); _container->setIsOptionChecked(StandingHMDSensorMode, false); if (_system) { + // Invalidate poses. It's fine if someone else sets these shared values, but we're about to stop updating them, and + // we don't want ViveControllerManager to consider old values to be valid. + for (int i = 0; i < vr::k_unMaxTrackedDeviceCount; i++) { + _trackedDevicePose[i].bPoseIsValid = false; + } releaseOpenVrSystem(); _system = nullptr; } From 8c13ff4ee15ba0ab7b2c8d8b9de3aef2fff0da66 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 31 May 2016 20:23:58 -0700 Subject: [PATCH 007/171] Pinned UI support --- interface/resources/qml/AddressBarDialog.qml | 9 +- interface/resources/qml/AssetServer.qml | 4 +- interface/resources/qml/Browser.qml | 4 +- interface/resources/qml/InfoView.qml | 2 +- interface/resources/qml/LoginDialog.qml | 11 +- interface/resources/qml/QmlWebWindow.qml | 4 +- interface/resources/qml/QmlWindow.qml | 2 +- interface/resources/qml/ToolWindow.qml | 10 +- .../qml/controls-uit/AttachmentsTable.qml | 2 +- interface/resources/qml/desktop/Desktop.qml | 74 ++- .../resources/qml/dialogs/FileDialog.qml | 4 +- .../resources/qml/dialogs/MessageDialog.qml | 4 +- .../qml/dialogs/PreferencesDialog.qml | 4 +- .../resources/qml/dialogs/QueryDialog.qml | 2 +- .../qml/dialogs/preferences/AvatarBrowser.qml | 2 +- .../qml/hifi/dialogs/AttachmentsDialog.qml | 4 +- .../qml/hifi/dialogs/ModelBrowserDialog.qml | 2 +- .../qml/hifi/dialogs/RunningScripts.qml | 7 +- .../hifi/dialogs/attachments/Attachment.qml | 2 +- .../qml/hifi/dialogs/attachments/Vector3.qml | 2 +- .../qml/windows-uit/DefaultFrame.qml | 119 ---- .../resources/qml/windows-uit/Fadable.qml | 60 -- interface/resources/qml/windows-uit/Frame.qml | 133 ----- .../resources/qml/windows-uit/ModalFrame.qml | 98 ---- .../resources/qml/windows-uit/ModalWindow.qml | 28 - .../resources/qml/windows-uit/Window.qml | 343 ------------ .../resources/qml/windows/DefaultFrame.qml | 118 ++-- interface/resources/qml/windows/Fadable.qml | 33 +- interface/resources/qml/windows/Frame.qml | 102 +++- .../resources/qml/windows/HiddenFrame.qml | 2 +- .../resources/qml/windows/ModalFrame.qml | 110 +++- .../resources/qml/windows/ModalWindow.qml | 24 +- interface/resources/qml/windows/Window.qml | 308 ++++++++-- interface/src/Application.cpp | 3 +- interface/src/ui/OverlayConductor.cpp | 61 +- interface/src/ui/OverlayConductor.h | 2 +- .../src/display-plugins/CompositorHelper.cpp | 56 +- .../src/display-plugins/CompositorHelper.h | 23 +- .../display-plugins/OpenGLDisplayPlugin.cpp | 49 +- .../display-plugins/hmd/HmdDisplayPlugin.cpp | 54 +- libraries/ui/src/ErrorDialog.cpp | 4 - libraries/ui/src/ErrorDialog.h | 1 - libraries/ui/src/OffscreenQmlDialog.cpp | 2 +- libraries/ui/src/OffscreenUi.cpp | 34 +- libraries/ui/src/OffscreenUi.h | 9 + libraries/ui/src/QmlWindowClass.cpp | 3 +- libraries/ui/src/Tooltip.cpp | 4 - libraries/ui/src/Tooltip.h | 2 - tests/ui/qml/main.qml | 529 +++++++++--------- tests/ui/qmlscratch.pro | 3 +- 50 files changed, 1003 insertions(+), 1469 deletions(-) delete mode 100644 interface/resources/qml/windows-uit/DefaultFrame.qml delete mode 100644 interface/resources/qml/windows-uit/Fadable.qml delete mode 100644 interface/resources/qml/windows-uit/Frame.qml delete mode 100644 interface/resources/qml/windows-uit/ModalFrame.qml delete mode 100644 interface/resources/qml/windows-uit/ModalWindow.qml delete mode 100644 interface/resources/qml/windows-uit/Window.qml diff --git a/interface/resources/qml/AddressBarDialog.qml b/interface/resources/qml/AddressBarDialog.qml index 7f107e44e9..a48804faba 100644 --- a/interface/resources/qml/AddressBarDialog.qml +++ b/interface/resources/qml/AddressBarDialog.qml @@ -20,9 +20,10 @@ Window { objectName: "AddressBarDialog" frame: HiddenFrame {} + hideBackground: true - visible: false - destroyOnInvisible: false + shown: false + destroyOnHidden: false resizable: false scale: 1.25 // Make this dialog a little larger than normal @@ -145,14 +146,14 @@ Window { if (addressLine.text !== "") { addressBarDialog.loadAddress(addressLine.text) } - root.visible = false; + root.shown = false; } Keys.onPressed: { switch (event.key) { case Qt.Key_Escape: case Qt.Key_Back: - root.visible = false + root.shown = false event.accepted = true break case Qt.Key_Enter: diff --git a/interface/resources/qml/AssetServer.qml b/interface/resources/qml/AssetServer.qml index 370bc92d81..e975037b5f 100644 --- a/interface/resources/qml/AssetServer.qml +++ b/interface/resources/qml/AssetServer.qml @@ -15,7 +15,7 @@ import Qt.labs.settings 1.0 import "styles-uit" import "controls-uit" as HifiControls -import "windows-uit" +import "windows" import "dialogs" Window { @@ -23,7 +23,7 @@ Window { objectName: "AssetServer" title: "Asset Browser" resizable: true - destroyOnInvisible: true + destroyOnHidden: true implicitWidth: 384; implicitHeight: 640 minSize: Qt.vector2d(200, 300) diff --git a/interface/resources/qml/Browser.qml b/interface/resources/qml/Browser.qml index 89ab333a0d..efa2063fe8 100644 --- a/interface/resources/qml/Browser.qml +++ b/interface/resources/qml/Browser.qml @@ -11,13 +11,13 @@ Window { HifiConstants { id: hifi } title: "Browser" resizable: true - destroyOnInvisible: true + destroyOnHidden: true width: 800 height: 600 property alias webView: webview Component.onCompleted: { - visible = true + shown = true addressBar.text = webview.url } diff --git a/interface/resources/qml/InfoView.qml b/interface/resources/qml/InfoView.qml index c5dba7e1f3..0f17a88614 100644 --- a/interface/resources/qml/InfoView.qml +++ b/interface/resources/qml/InfoView.qml @@ -12,7 +12,7 @@ import QtQuick 2.5 import Hifi 1.0 as Hifi import "controls-uit" -import "windows-uit" as Windows +import "windows" as Windows Windows.Window { id: root diff --git a/interface/resources/qml/LoginDialog.qml b/interface/resources/qml/LoginDialog.qml index 1b25b75608..f5030cb88d 100644 --- a/interface/resources/qml/LoginDialog.qml +++ b/interface/resources/qml/LoginDialog.qml @@ -22,8 +22,9 @@ Window { width: loginDialog.implicitWidth // FIXME make movable anchors.centerIn: parent - destroyOnInvisible: false - visible: false + destroyOnHidden: false + hideBackground: true + shown: false LoginDialog { id: loginDialog @@ -268,8 +269,8 @@ Window { } } - onVisibleChanged: { - if (!visible) { + onShownChanged: { + if (!shown) { username.text = "" password.text = "" loginDialog.statusText = "" @@ -282,7 +283,7 @@ Window { switch (event.key) { case Qt.Key_Escape: case Qt.Key_Back: - root.visible = false; + root.shown = false; event.accepted = true; break; diff --git a/interface/resources/qml/QmlWebWindow.qml b/interface/resources/qml/QmlWebWindow.qml index ae052879db..09c3bd7f28 100644 --- a/interface/resources/qml/QmlWebWindow.qml +++ b/interface/resources/qml/QmlWebWindow.qml @@ -13,7 +13,7 @@ import QtQuick.Controls 1.4 import QtWebEngine 1.1 import QtWebChannel 1.0 -import "windows-uit" as Windows +import "windows" as Windows import "controls-uit" as Controls import "styles-uit" @@ -22,7 +22,7 @@ Windows.Window { HifiConstants { id: hifi } title: "WebWindow" resizable: true - visible: false + shown: false // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer destroyOnCloseButton: false property alias source: webview.url diff --git a/interface/resources/qml/QmlWindow.qml b/interface/resources/qml/QmlWindow.qml index 0420cd2e88..7be747a3ad 100644 --- a/interface/resources/qml/QmlWindow.qml +++ b/interface/resources/qml/QmlWindow.qml @@ -14,7 +14,7 @@ Windows.Window { HifiConstants { id: hifi } title: "QmlWindow" resizable: true - visible: false + shown: false focus: true property var channel; // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer diff --git a/interface/resources/qml/ToolWindow.qml b/interface/resources/qml/ToolWindow.qml index aaff43b146..faa96fac5a 100644 --- a/interface/resources/qml/ToolWindow.qml +++ b/interface/resources/qml/ToolWindow.qml @@ -15,7 +15,7 @@ import QtWebEngine 1.1 import QtWebChannel 1.0 import Qt.labs.settings 1.0 -import "windows-uit" +import "windows" import "controls-uit" import "styles-uit" @@ -24,9 +24,9 @@ Window { resizable: true objectName: "ToolWindow" destroyOnCloseButton: false - destroyOnInvisible: false + destroyOnHidden: false closable: true - visible: false + shown: false title: "Edit" property alias tabView: tabView implicitWidth: 520; implicitHeight: 695 @@ -142,7 +142,7 @@ Window { return; } } - visible = false; + shown = false; } function findIndexForUrl(source) { @@ -172,7 +172,7 @@ Window { var tab = tabView.getTab(index); if (newVisible) { - toolWindow.visible = true + toolWindow.shown = true tab.enabled = true } else { tab.enabled = false; diff --git a/interface/resources/qml/controls-uit/AttachmentsTable.qml b/interface/resources/qml/controls-uit/AttachmentsTable.qml index ce93b8f4df..7d0280b72d 100644 --- a/interface/resources/qml/controls-uit/AttachmentsTable.qml +++ b/interface/resources/qml/controls-uit/AttachmentsTable.qml @@ -15,7 +15,7 @@ import QtQuick.XmlListModel 2.0 import "../styles-uit" import "../controls-uit" as HifiControls -import "../windows-uit" +import "../windows" import "../hifi/models" TableView { diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index 62a72e3d8c..73f8a17bb0 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -52,6 +52,7 @@ FocusScope { readonly property real menu: 8000 } + QtObject { id: d @@ -93,6 +94,17 @@ FocusScope { return item; } + function findMatchingChildren(item, predicate) { + var results = []; + for (var i in item.children) { + var child = item.children[i]; + if (predicate(child)) { + results.push(child); + } + } + return results; + } + function isTopLevelWindow(item) { return item.topLevelWindow; } @@ -106,19 +118,9 @@ FocusScope { } function getTopLevelWindows(predicate) { - var currentWindows = []; - if (!desktop) { - console.log("Could not find desktop for " + item) - return currentWindows; - } - - for (var i = 0; i < desktop.children.length; ++i) { - var child = desktop.children[i]; - if (isTopLevelWindow(child) && (!predicate || predicate(child))) { - currentWindows.push(child) - } - } - return currentWindows; + return findMatchingChildren(desktop, function(child) { + return (isTopLevelWindow(child) && (!predicate || predicate(child))); + }); } function getDesktopWindow(item) { @@ -227,19 +229,9 @@ FocusScope { } function getRepositionChildren(predicate) { - var currentWindows = []; - if (!desktop) { - console.log("Could not find desktop"); - return currentWindows; - } - - for (var i = 0; i < desktop.children.length; ++i) { - var child = desktop.children[i]; - if (child.shouldReposition === true && (!predicate || predicate(child))) { - currentWindows.push(child) - } - } - return currentWindows; + return findMatchingChildren(desktop, function(child) { + return (child.shouldReposition === true && (!predicate || predicate(child))); + }); } function repositionAll() { @@ -265,6 +257,35 @@ FocusScope { } } + property bool pinned: false + property var hiddenChildren: [] + + function togglePinned() { + pinned = !pinned + } + + onPinnedChanged: { + + if (pinned) { + hiddenChildren = d.findMatchingChildren(desktop, function(child){ + return !d.isTopLevelWindow(child) && child.visible; + }); + + hiddenChildren.forEach(function(child){ + child.visible = false; + }); + } else { + hiddenChildren.forEach(function(child){ + if (child) { + child.visible = true; + } + }); + hiddenChildren = []; + } + } + + onShowDesktop: pinned = false + function raise(item) { var targetWindow = d.getDesktopWindow(item); if (!targetWindow) { @@ -422,7 +443,6 @@ FocusScope { event.accepted = false; } - function unfocusWindows() { var windows = d.getTopLevelWindows(); for (var i = 0; i < windows.length; ++i) { diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 015a192185..96676e986a 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -18,7 +18,7 @@ import QtQuick.Dialogs 1.2 as OriginalDialogs import ".." import "../controls-uit" import "../styles-uit" -import "../windows-uit" +import "../windows" import "fileDialog" @@ -724,7 +724,7 @@ ModalWindow { Action { id: cancelAction text: "Cancel" - onTriggered: { canceled(); root.visible = false; } + onTriggered: { canceled(); root.shown = false; } } } diff --git a/interface/resources/qml/dialogs/MessageDialog.qml b/interface/resources/qml/dialogs/MessageDialog.qml index 30f492e36a..d390ea08bf 100644 --- a/interface/resources/qml/dialogs/MessageDialog.qml +++ b/interface/resources/qml/dialogs/MessageDialog.qml @@ -14,7 +14,7 @@ import QtQuick.Dialogs 1.2 as OriginalDialogs import "../controls-uit" import "../styles-uit" -import "../windows-uit" +import "../windows" import "messageDialog" @@ -24,7 +24,7 @@ ModalWindow { implicitWidth: 640 implicitHeight: 320 destroyOnCloseButton: true - destroyOnInvisible: true + destroyOnHidden: true visible: true signal selected(int button); diff --git a/interface/resources/qml/dialogs/PreferencesDialog.qml b/interface/resources/qml/dialogs/PreferencesDialog.qml index 40cc713397..398e0abd8e 100644 --- a/interface/resources/qml/dialogs/PreferencesDialog.qml +++ b/interface/resources/qml/dialogs/PreferencesDialog.qml @@ -13,14 +13,14 @@ import QtQuick.Controls 1.4 import "../controls-uit" as HifiControls import "../styles-uit" -import "../windows-uit" +import "../windows" import "preferences" Window { id: root title: "Preferences" resizable: true - destroyOnInvisible: true + destroyOnHidden: true width: 500 height: 577 property var sections: [] diff --git a/interface/resources/qml/dialogs/QueryDialog.qml b/interface/resources/qml/dialogs/QueryDialog.qml index 0c7772dc94..05cb347169 100644 --- a/interface/resources/qml/dialogs/QueryDialog.qml +++ b/interface/resources/qml/dialogs/QueryDialog.qml @@ -14,7 +14,7 @@ import QtQuick.Dialogs 1.2 as OriginalDialogs import "../controls-uit" import "../styles-uit" -import "../windows-uit" +import "../windows" ModalWindow { id: root diff --git a/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml b/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml index e5bc9b80ef..90d2dc5284 100644 --- a/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml +++ b/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml @@ -12,7 +12,7 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 import QtWebEngine 1.1 -import "../../windows-uit" as Windows +import "../../windows" as Windows import "../../controls-uit" as Controls import "../../styles-uit" diff --git a/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml b/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml index 437e02e149..7b3a368bb0 100755 --- a/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml +++ b/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml @@ -6,7 +6,7 @@ import QtQuick.Controls.Styles 1.4 import "../../styles-uit" import "../../controls-uit" as HifiControls -import "../../windows-uit" +import "../../windows" import "attachments" Window { @@ -16,7 +16,7 @@ Window { width: 600 height: 600 resizable: true - destroyOnInvisible: true + destroyOnHidden: true minSize: Qt.vector2d(400, 500) HifiConstants { id: hifi } diff --git a/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml b/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml index b2de108545..29f0498f59 100644 --- a/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml +++ b/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml @@ -9,7 +9,7 @@ import "../models" import "../../styles-uit" import "../../controls-uit" as HifiControls -import "../../windows-uit" +import "../../windows" Window { id: root diff --git a/interface/resources/qml/hifi/dialogs/RunningScripts.qml b/interface/resources/qml/hifi/dialogs/RunningScripts.qml index 31bb553809..7dc7654d9a 100644 --- a/interface/resources/qml/hifi/dialogs/RunningScripts.qml +++ b/interface/resources/qml/hifi/dialogs/RunningScripts.qml @@ -15,14 +15,14 @@ import Qt.labs.settings 1.0 import "../../styles-uit" import "../../controls-uit" as HifiControls -import "../../windows-uit" +import "../../windows" Window { id: root objectName: "RunningScripts" title: "Running Scripts" resizable: true - destroyOnInvisible: true + destroyOnHidden: true implicitWidth: 400 implicitHeight: isHMD ? 695 : 728 minSize: Qt.vector2d(200, 300) @@ -34,6 +34,9 @@ Window { property var runningScriptsModel: ListModel { } property bool isHMD: false + onVisibleChanged: console.log("Running scripts visible changed to " + visible) + onShownChanged: console.log("Running scripts visible changed to " + visible) + Settings { category: "Overlay.RunningScripts" property alias x: root.x diff --git a/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml b/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml index 1277c459ce..04e3934535 100755 --- a/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml +++ b/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml @@ -8,7 +8,7 @@ import "." import ".." import "../../../styles-uit" import "../../../controls-uit" as HifiControls -import "../../../windows-uit" +import "../../../windows" Item { height: column.height + 2 * 8 diff --git a/interface/resources/qml/hifi/dialogs/attachments/Vector3.qml b/interface/resources/qml/hifi/dialogs/attachments/Vector3.qml index e1d7b6d4a3..3d109cc2a5 100644 --- a/interface/resources/qml/hifi/dialogs/attachments/Vector3.qml +++ b/interface/resources/qml/hifi/dialogs/attachments/Vector3.qml @@ -3,7 +3,7 @@ import QtQuick.Controls 1.4 import "../../../styles-uit" import "../../../controls-uit" as HifiControls -import "../../../windows-uit" +import "../../../windows" Item { id: root diff --git a/interface/resources/qml/windows-uit/DefaultFrame.qml b/interface/resources/qml/windows-uit/DefaultFrame.qml deleted file mode 100644 index 84f435480b..0000000000 --- a/interface/resources/qml/windows-uit/DefaultFrame.qml +++ /dev/null @@ -1,119 +0,0 @@ -// -// DefaultFrame.qml -// -// Created by Bradley Austin Davis on 12 Jan 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.5 -import QtGraphicalEffects 1.0 - -import "." -import "../styles-uit" - -Frame { - HifiConstants { id: hifi } - - Rectangle { - // Dialog frame - id: frameContent - - readonly property int iconSize: hifi.dimensions.frameIconSize - readonly property int frameMargin: 9 - readonly property int frameMarginLeft: frameMargin - readonly property int frameMarginRight: frameMargin - readonly property int frameMarginTop: 2 * frameMargin + iconSize - readonly property int frameMarginBottom: iconSize + 11 - - anchors { - topMargin: -frameMarginTop - leftMargin: -frameMarginLeft - rightMargin: -frameMarginRight - bottomMargin: -frameMarginBottom - } - anchors.fill: parent - color: hifi.colors.baseGrayHighlight40 - border { - width: hifi.dimensions.borderWidth - color: hifi.colors.faintGray50 - } - radius: hifi.dimensions.borderRadius - - // Enable dragging of the window - MouseArea { - anchors.fill: parent - drag.target: window - } - - Row { - id: controlsRow - anchors { - right: parent.right; - top: parent.top; - topMargin: frameContent.frameMargin + 1 // Move down a little to visually align with the title - rightMargin: frameContent.frameMarginRight; - } - spacing: frameContent.iconSize / 4 - - HiFiGlyphs { - // "Pin" button - visible: false - text: (frame.pinned && !pinClickArea.containsMouse) || (!frame.pinned && pinClickArea.containsMouse) ? hifi.glyphs.pinInverted : hifi.glyphs.pin - color: pinClickArea.containsMouse && !pinClickArea.pressed ? hifi.colors.redHighlight : hifi.colors.white - size: frameContent.iconSize - MouseArea { - id: pinClickArea - anchors.fill: parent - hoverEnabled: true - propagateComposedEvents: true - onClicked: { frame.pin(); mouse.accepted = false; } - } - } - - HiFiGlyphs { - // "Close" button - visible: window ? window.closable : false - text: closeClickArea.containsPress ? hifi.glyphs.closeInverted : hifi.glyphs.close - color: closeClickArea.containsMouse ? hifi.colors.redHighlight : hifi.colors.white - size: frameContent.iconSize - MouseArea { - id: closeClickArea - anchors.fill: parent - hoverEnabled: true - onClicked: window.visible = false; - } - } - } - - RalewayRegular { - // Title - id: titleText - anchors { - left: parent.left - leftMargin: frameContent.frameMarginLeft + hifi.dimensions.contentMargin.x - right: controlsRow.left - rightMargin: frameContent.iconSize - top: parent.top - topMargin: frameContent.frameMargin - } - text: window ? window.title : "" - color: hifi.colors.white - size: hifi.fontSizes.overlayTitle - } - - DropShadow { - source: titleText - anchors.fill: titleText - horizontalOffset: 2 - verticalOffset: 2 - samples: 2 - color: hifi.colors.baseGrayShadow60 - visible: (window && window.focus) - cached: true - } - } -} - diff --git a/interface/resources/qml/windows-uit/Fadable.qml b/interface/resources/qml/windows-uit/Fadable.qml deleted file mode 100644 index 34990c2147..0000000000 --- a/interface/resources/qml/windows-uit/Fadable.qml +++ /dev/null @@ -1,60 +0,0 @@ -// -// Fadable.qml -// -// Created by Bradley Austin Davis on 15 Jan 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtGraphicalEffects 1.0 - -import "../styles-uit" - -// Enable window visibility transitions -FocusScope { - id: root - HifiConstants { id: hifi } - - Component.onCompleted: { - fadeTargetProperty = visible ? 1.0 : 0.0 - } - - // The target property to animate, usually scale or opacity - property alias fadeTargetProperty: root.opacity - // always start the property at 0 to enable fade in on creation - fadeTargetProperty: 0 - // DO NOT set visible to false or when derived types override it it - // will short circuit the fade in on initial visibility - // visible: false <--- NO - - // Some dialogs should be destroyed when they become - // invisible, so handle that - onVisibleChanged: { - // If someone directly set the visibility to false - // toggle it back on and use the targetVisible flag to transition - // via fading. - if ((!visible && fadeTargetProperty != 0.0) || (visible && fadeTargetProperty == 0.0)) { - var target = visible; - visible = !visible; - fadeTargetProperty = target ? 1.0 : 0.0; - return; - } - } - - // The actual animator - Behavior on fadeTargetProperty { - NumberAnimation { - duration: hifi.effects.fadeInDuration - easing.type: Easing.InOutCubic - } - } - - // Once we're transparent, disable the dialog's visibility - onFadeTargetPropertyChanged: { - visible = (fadeTargetProperty != 0.0); - } -} diff --git a/interface/resources/qml/windows-uit/Frame.qml b/interface/resources/qml/windows-uit/Frame.qml deleted file mode 100644 index 9519a44cf0..0000000000 --- a/interface/resources/qml/windows-uit/Frame.qml +++ /dev/null @@ -1,133 +0,0 @@ -// -// Frame.qml -// -// Created by Bradley Austin Davis on 12 Jan 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.5 -import QtGraphicalEffects 1.0 - -import "../styles-uit" -import "../js/Utils.js" as Utils - -Item { - id: frame - HifiConstants { id: hifi } - - default property var decoration - - property bool gradientsSupported: desktop.gradientsSupported - - readonly property int frameMarginLeft: frameContent.frameMarginLeft - readonly property int frameMarginRight: frameContent.frameMarginRight - readonly property int frameMarginTop: frameContent.frameMarginTop - readonly property int frameMarginBottom: frameContent.frameMarginBottom - - // Frames always fill their parents, but their decorations may extend - // beyond the window via negative margin sizes - anchors.fill: parent - - children: [ - focusShadow, - decoration, - sizeOutline, - debugZ, - sizeDrag - ] - - Text { - id: debugZ - visible: DebugQML - text: window ? "Z: " + window.z : "" - y: window ? window.height + 4 : 0 - } - - function deltaSize(dx, dy) { - var newSize = Qt.vector2d(window.width + dx, window.height + dy); - newSize = Utils.clampVector(newSize, window.minSize, window.maxSize); - window.width = newSize.x - window.height = newSize.y - } - - RadialGradient { - id: focusShadow - width: 1.66 * window.width - height: 1.66 * window.height - x: (window.width - width) / 2 - y: window.height / 2 - 0.375 * height - visible: gradientsSupported && window && window.focus && pane.visible - gradient: Gradient { - // GradientStop position 0.5 is at full circumference of circle that fits inside the square. - GradientStop { position: 0.0; color: "#ff000000" } // black, 100% opacity - GradientStop { position: 0.333; color: "#1f000000" } // black, 12% opacity - GradientStop { position: 0.5; color: "#00000000" } // black, 0% opacity - GradientStop { position: 1.0; color: "#00000000" } - } - cached: true - } - - Rectangle { - id: sizeOutline - x: -frameMarginLeft - y: -frameMarginTop - width: window ? window.width + frameMarginLeft + frameMarginRight + 2 : 0 - height: window ? window.height + frameMarginTop + frameMarginBottom + 2 : 0 - color: hifi.colors.baseGrayHighlight15 - border.width: 3 - border.color: hifi.colors.white50 - radius: hifi.dimensions.borderRadius - visible: window ? !pane.visible : false - } - - MouseArea { - // Resize handle - id: sizeDrag - width: hifi.dimensions.frameIconSize - height: hifi.dimensions.frameIconSize - enabled: window ? window.resizable : false - hoverEnabled: true - x: window ? window.width + frameMarginRight - hifi.dimensions.frameIconSize : 0 - y: window ? window.height + 4 : 0 - property vector2d pressOrigin - property vector2d sizeOrigin - property bool hid: false - onPressed: { - //console.log("Pressed on size") - pressOrigin = Qt.vector2d(mouseX, mouseY) - sizeOrigin = Qt.vector2d(window.content.width, window.content.height) - hid = false; - } - onReleased: { - if (hid) { - pane.visible = true - frameContent.visible = true - hid = false; - } - } - onPositionChanged: { - if (pressed) { - if (pane.visible) { - pane.visible = false; - frameContent.visible = false - hid = true; - } - var delta = Qt.vector2d(mouseX, mouseY).minus(pressOrigin); - frame.deltaSize(delta.x, delta.y) - } - } - HiFiGlyphs { - visible: sizeDrag.enabled - x: -11 // Move a little to visually align - y: window.modality == Qt.ApplicationModal ? -6 : -4 - text: hifi.glyphs.resizeHandle - size: hifi.dimensions.frameIconSize + 10 - color: sizeDrag.containsMouse || sizeDrag.pressed - ? hifi.colors.white - : (window.colorScheme == hifi.colorSchemes.dark ? hifi.colors.white50 : hifi.colors.lightGrayText80) - } - } -} diff --git a/interface/resources/qml/windows-uit/ModalFrame.qml b/interface/resources/qml/windows-uit/ModalFrame.qml deleted file mode 100644 index 211353b5f3..0000000000 --- a/interface/resources/qml/windows-uit/ModalFrame.qml +++ /dev/null @@ -1,98 +0,0 @@ -// -// ModalFrame.qml -// -// Created by Bradley Austin Davis on 15 Jan 2016 -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.5 - -import "." -import "../controls-uit" -import "../styles-uit" - -Frame { - HifiConstants { id: hifi } - - Rectangle { - id: frameContent - - readonly property bool hasTitle: window.title != "" - - readonly property int frameMarginLeft: hifi.dimensions.modalDialogMargin.x - readonly property int frameMarginRight: hifi.dimensions.modalDialogMargin.x - readonly property int frameMarginTop: hifi.dimensions.modalDialogMargin.y + (frameContent.hasTitle ? hifi.dimensions.modalDialogTitleHeight + 10 : 0) - readonly property int frameMarginBottom: hifi.dimensions.modalDialogMargin.y - - signal frameClicked(); - - anchors { - fill: parent - topMargin: -frameMarginTop - leftMargin: -frameMarginLeft - rightMargin: -frameMarginRight - bottomMargin: -frameMarginBottom - } - - border { - width: hifi.dimensions.borderWidth - color: hifi.colors.lightGrayText80 - } - radius: hifi.dimensions.borderRadius - color: hifi.colors.faintGray - - // Enable dragging of the window - MouseArea { - anchors.fill: parent - drag.target: window - enabled: window.draggable - onClicked: window.frameClicked(); - } - - Item { - visible: frameContent.hasTitle - anchors.fill: parent - anchors { - topMargin: -parent.anchors.topMargin - leftMargin: -parent.anchors.leftMargin - rightMargin: -parent.anchors.rightMargin - } - - Item { - width: title.width + (icon.text !== "" ? icon.width + hifi.dimensions.contentSpacing.x : 0) - x: (parent.width - width) / 2 - - onWidthChanged: window.titleWidth = width - - HiFiGlyphs { - id: icon - text: window.iconText ? window.iconText : "" - size: window.iconSize ? window.iconSize : 30 - color: hifi.colors.lightGray - visible: text != "" - anchors.verticalCenter: title.verticalCenter - anchors.left: parent.left - } - RalewayRegular { - id: title - text: window.title - elide: Text.ElideRight - color: hifi.colors.baseGrayHighlight - size: hifi.fontSizes.overlayTitle - y: -hifi.dimensions.modalDialogTitleHeight - anchors.right: parent.right - } - } - - Rectangle { - anchors.left: parent.left - anchors.right: parent.right - height: 1 - color: hifi.colors.lightGray - } - } - } -} diff --git a/interface/resources/qml/windows-uit/ModalWindow.qml b/interface/resources/qml/windows-uit/ModalWindow.qml deleted file mode 100644 index 144165e4e1..0000000000 --- a/interface/resources/qml/windows-uit/ModalWindow.qml +++ /dev/null @@ -1,28 +0,0 @@ -// -// ModalWindow.qml -// -// Created by Bradley Austin Davis on 22 Jan 2016 -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.5 - -import "." - -Window { - id: window - modality: Qt.ApplicationModal - destroyOnCloseButton: true - destroyOnInvisible: true - frame: ModalFrame { } - - property int colorScheme: hifi.colorSchemes.light - property bool draggable: false - - signal frameClicked(); - - anchors.centerIn: draggable ? undefined : parent -} diff --git a/interface/resources/qml/windows-uit/Window.qml b/interface/resources/qml/windows-uit/Window.qml deleted file mode 100644 index d614b21ce2..0000000000 --- a/interface/resources/qml/windows-uit/Window.qml +++ /dev/null @@ -1,343 +0,0 @@ -// -// Window.qml -// -// Created by Bradley Austin Davis on 12 Jan 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 -import QtGraphicalEffects 1.0 - -import "." -import "../styles-uit" - -// FIXME how do I set the initial position of a window without -// overriding places where the a individual client of the window -// might be setting the position with a Settings{} element? - -// FIXME how to I enable dragging without allowing the window to lay outside -// of the desktop? How do I ensure when the desktop resizes all the windows -// are still at least partially visible? -Fadable { - id: window - HifiConstants { id: hifi } - - // The Window size is the size of the content, while the frame - // decorations can extend outside it. - implicitHeight: content ? content.height : 0 - implicitWidth: content ? content.width : 0 - x: desktop.invalid_position; y: desktop.invalid_position; - enabled: visible - - signal windowDestroyed(); - - property int modality: Qt.NonModal - readonly property bool topLevelWindow: true - property string title - // Should the window be closable control? - property bool closable: true - // Should the window try to remain on top of other windows? - property bool alwaysOnTop: false - // Should hitting the close button hide or destroy the window? - property bool destroyOnCloseButton: true - // Should hiding the window destroy it or just hide it? - property bool destroyOnInvisible: false - // FIXME support for pinned / unpinned pending full design - // property bool pinnable: false - // property bool pinned: false - property bool resizable: false - property bool gradientsSupported: desktop.gradientsSupported - property int colorScheme: hifi.colorSchemes.dark - - property vector2d minSize: Qt.vector2d(100, 100) - property vector2d maxSize: Qt.vector2d(1280, 800) - - // The content to place inside the window, determined by the client - default property var content - - property var footer: Item { } // Optional static footer at the bottom of the dialog. - - function setDefaultFocus() {} // Default function; can be overridden by dialogs. - - property var rectifier: Timer { - property bool executing: false; - interval: 100 - repeat: false - running: false - - onTriggered: { - executing = true; - x = Math.floor(x); - y = Math.floor(y); - executing = false; - } - - function begin() { - if (!executing) { - restart(); - } - } - } - onXChanged: rectifier.begin(); - onYChanged: rectifier.begin(); - - // This mouse area serves to raise the window. To function, it must live - // in the window and have a higher Z-order than the content, but follow - // the position and size of frame decoration - property var activator: MouseArea { - width: frame.decoration.width - height: frame.decoration.height - x: frame.decoration.anchors.leftMargin - y: frame.decoration.anchors.topMargin - propagateComposedEvents: true - acceptedButtons: Qt.AllButtons - enabled: window.visible - onPressed: { - //console.log("Pressed on activator area"); - window.raise(); - mouse.accepted = false; - } - } - - // This mouse area serves to swallow mouse events while the mouse is over the window - // to prevent things like mouse wheel events from reaching the application and changing - // the camera if the user is scrolling through a list and gets to the end. - property var swallower: MouseArea { - width: frame.decoration.width - height: frame.decoration.height - x: frame.decoration.anchors.leftMargin - y: frame.decoration.anchors.topMargin - hoverEnabled: true - acceptedButtons: Qt.AllButtons - enabled: window.visible - onClicked: {} - onDoubleClicked: {} - onPressAndHold: {} - onReleased: {} - onWheel: {} - } - - // Default to a standard frame. Can be overriden to provide custom - // frame styles, like a full desktop frame to simulate a modal window - property var frame: DefaultFrame { } - - // Scrollable window content. - property var pane: Item { - property bool isScrolling: scrollView.height < scrollView.contentItem.height - property int contentWidth: scrollView.width - (isScrolling ? 10 : 0) - property int scrollHeight: scrollView.height - - anchors.fill: parent - anchors.rightMargin: isScrolling ? 11 : 0 - - Rectangle { - id: contentBackground - anchors.fill: parent - anchors.rightMargin: parent.isScrolling ? 11 : 0 - color: hifi.colors.baseGray - visible: modality != Qt.ApplicationModal - } - - LinearGradient { - visible: gradientsSupported && modality != Qt.ApplicationModal - anchors.top: contentBackground.bottom - anchors.left: contentBackground.left - width: contentBackground.width - 1 - height: 4 - start: Qt.point(0, 0) - end: Qt.point(0, 4) - gradient: Gradient { - GradientStop { position: 0.0; color: hifi.colors.darkGray } - GradientStop { position: 1.0; color: hifi.colors.darkGray0 } - } - cached: true - } - - ScrollView { - id: scrollView - contentItem: content - horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff - verticalScrollBarPolicy: Qt.ScrollBarAsNeeded - anchors.fill: parent - anchors.rightMargin: parent.isScrolling ? 1 : 0 - anchors.bottomMargin: footer.height > 0 ? footerPane.height : 0 - - style: ScrollViewStyle { - - padding.right: -7 // Move to right away from content. - - handle: Item { - implicitWidth: 8 - Rectangle { - radius: 4 - color: hifi.colors.white30 - anchors { - fill: parent - leftMargin: 2 // Finesse size and position. - topMargin: 1 - bottomMargin: 1 - } - } - } - - scrollBarBackground: Item { - implicitWidth: 10 - Rectangle { - color: hifi.colors.darkGray30 - radius: 4 - anchors { - fill: parent - topMargin: -1 // Finesse size - bottomMargin: -2 - } - } - } - - incrementControl: Item { - visible: false - } - - decrementControl: Item { - visible: false - } - } - } - - Rectangle { - // Optional non-scrolling footer. - id: footerPane - anchors { - left: parent.left - bottom: parent.bottom - } - width: parent.contentWidth - height: footer.height + 2 * hifi.dimensions.contentSpacing.y + 3 - color: hifi.colors.baseGray - visible: footer.height > 0 - - Item { - // Horizontal rule. - anchors.fill: parent - - Rectangle { - width: parent.width - height: 1 - y: 1 // Stop displaying content just above horizontal rule/=. - color: hifi.colors.baseGrayShadow - } - - Rectangle { - width: parent.width - height: 1 - y: 2 - color: hifi.colors.baseGrayHighlight - } - } - - Item { - anchors.fill: parent - anchors.topMargin: 3 // Horizontal rule. - children: [ footer ] - } - } - } - - children: [ swallower, frame, pane, activator ] - - Component.onCompleted: { - window.parentChanged.connect(raise); - raise(); - setDefaultFocus(); - centerOrReposition(); - } - Component.onDestruction: { - window.parentChanged.disconnect(raise); // Prevent warning on shutdown - windowDestroyed(); - } - - onVisibleChanged: { - if (!visible && destroyOnInvisible) { - destroy(); - return; - } - if (visible) { - raise(); - } - enabled = visible - - if (visible && parent) { - centerOrReposition(); - } - } - - function centerOrReposition() { - if (x == desktop.invalid_position && y == desktop.invalid_position) { - desktop.centerOnVisible(window); - } else { - desktop.repositionOnVisible(window); - } - } - - function raise() { - if (visible && parent) { - desktop.raise(window) - } - } - - function pin() { -// pinned = ! pinned - } - - // our close function performs the same way as the OffscreenUI class: - // don't do anything but manipulate the targetVisible flag and let the other - // mechanisms decide if the window should be destroyed after the close - // animation completes - // FIXME using this close function messes up the visibility signals received by the - // type and it's derived types -// function close() { -// console.log("Closing " + window) -// if (destroyOnCloseButton) { -// destroyOnInvisible = true -// } -// visible = false; -// } - - function framedRect() { - if (!frame || !frame.decoration) { - return Qt.rect(0, 0, window.width, window.height) - } - return Qt.rect(frame.decoration.anchors.leftMargin, frame.decoration.anchors.topMargin, - window.width - frame.decoration.anchors.leftMargin - frame.decoration.anchors.rightMargin, - window.height - frame.decoration.anchors.topMargin - frame.decoration.anchors.bottomMargin) - } - - Keys.onPressed: { - switch(event.key) { - case Qt.Key_Control: - case Qt.Key_Shift: - case Qt.Key_Meta: - case Qt.Key_Alt: - break; - - case Qt.Key_W: - if (window.closable && (event.modifiers === Qt.ControlModifier)) { - visible = false - event.accepted = true - } - // fall through - - default: - // Consume unmodified keyboard entries while the window is focused, to prevent them - // from propagating to the application - if (event.modifiers === Qt.NoModifier) { - event.accepted = true; - } - break; - } - } -} diff --git a/interface/resources/qml/windows/DefaultFrame.qml b/interface/resources/qml/windows/DefaultFrame.qml index c58f9ca545..242209dbe0 100644 --- a/interface/resources/qml/windows/DefaultFrame.qml +++ b/interface/resources/qml/windows/DefaultFrame.qml @@ -1,20 +1,48 @@ +// +// DefaultFrame.qml +// +// Created by Bradley Austin Davis on 12 Jan 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + import QtQuick 2.5 +import QtGraphicalEffects 1.0 import "." -import "../controls" +import "../styles-uit" Frame { - id: frame - - property bool wideTopMargin: (window && (window.closable || window.title)); + HifiConstants { id: hifi } Rectangle { - anchors { margins: -iconSize; topMargin: -iconSize * (wideTopMargin ? 2 : 1); } - anchors.fill: parent; - color: "#7f7f7f7f"; - radius: 3; + // Dialog frame + id: frameContent - // Allow dragging of the window + readonly property int iconSize: hifi.dimensions.frameIconSize + readonly property int frameMargin: 9 + readonly property int frameMarginLeft: frameMargin + readonly property int frameMarginRight: frameMargin + readonly property int frameMarginTop: 2 * frameMargin + iconSize + readonly property int frameMarginBottom: iconSize + 11 + + anchors { + topMargin: -frameMarginTop + leftMargin: -frameMarginLeft + rightMargin: -frameMarginRight + bottomMargin: -frameMarginBottom + } + anchors.fill: parent + color: hifi.colors.baseGrayHighlight40 + border { + width: hifi.dimensions.borderWidth + color: hifi.colors.faintGray50 + } + radius: hifi.dimensions.borderRadius + + // Enable dragging of the window MouseArea { anchors.fill: parent drag.target: window @@ -22,48 +50,70 @@ Frame { Row { id: controlsRow - anchors { right: parent.right; top: parent.top; rightMargin: iconSize; topMargin: iconSize / 2; } - spacing: iconSize / 4 - FontAwesome { - visible: false - text: "\uf08d" - style: Text.Outline; styleColor: "white" - size: frame.iconSize - rotation: !frame.parent ? 90 : frame.parent.pinned ? 0 : 90 - color: frame.pinned ? "red" : "black" + anchors { + right: parent.right; + top: parent.top; + topMargin: frameContent.frameMargin + 1 // Move down a little to visually align with the title + rightMargin: frameContent.frameMarginRight; + } + spacing: frameContent.iconSize / 4 + + HiFiGlyphs { + // "Pin" button + visible: window.pinnable + text: window.pinned ? hifi.glyphs.pinInverted : hifi.glyphs.pin + color: pinClickArea.pressed ? hifi.colors.redHighlight : hifi.colors.white + size: frameContent.iconSize MouseArea { + id: pinClickArea anchors.fill: parent + hoverEnabled: true propagateComposedEvents: true - onClicked: { frame.pin(); mouse.accepted = false; } + onClicked: window.pinned = !window.pinned; } } - FontAwesome { + + HiFiGlyphs { + // "Close" button visible: window ? window.closable : false - text: closeClickArea.containsMouse ? "\uf057" : "\uf05c" - style: Text.Outline; - styleColor: "white" - color: closeClickArea.containsMouse ? "red" : "black" - size: frame.iconSize + text: closeClickArea.containsPress ? hifi.glyphs.closeInverted : hifi.glyphs.close + color: closeClickArea.containsMouse ? hifi.colors.redHighlight : hifi.colors.white + size: frameContent.iconSize MouseArea { id: closeClickArea anchors.fill: parent hoverEnabled: true - onClicked: window.visible = false; + onClicked: window.shown = false; } } } - Text { + RalewayRegular { + // Title id: titleText - anchors { left: parent.left; leftMargin: iconSize; right: controlsRow.left; rightMargin: iconSize; top: parent.top; topMargin: iconSize / 2; } + anchors { + left: parent.left + leftMargin: frameContent.frameMarginLeft + hifi.dimensions.contentMargin.x + right: controlsRow.left + rightMargin: frameContent.iconSize + top: parent.top + topMargin: frameContent.frameMargin + } text: window ? window.title : "" - elide: Text.ElideRight - font.bold: true - color: (window && window.focus) ? "white" : "gray" - style: Text.Outline; - styleColor: "black" + color: hifi.colors.white + size: hifi.fontSizes.overlayTitle + } + + DropShadow { + source: titleText + anchors.fill: titleText + horizontalOffset: 2 + verticalOffset: 2 + samples: 2 + color: hifi.colors.baseGrayShadow60 + visible: (window && window.focus) + cached: true } } - } diff --git a/interface/resources/qml/windows/Fadable.qml b/interface/resources/qml/windows/Fadable.qml index 0352966bd0..38cd4bf1f9 100644 --- a/interface/resources/qml/windows/Fadable.qml +++ b/interface/resources/qml/windows/Fadable.qml @@ -1,8 +1,18 @@ +// +// Fadable.qml +// +// Created by Bradley Austin Davis on 15 Jan 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + import QtQuick 2.5 import QtQuick.Controls 1.4 import QtGraphicalEffects 1.0 -import "." -import "../styles" + +import "../styles-uit" // Enable window visibility transitions FocusScope { @@ -13,6 +23,7 @@ FocusScope { fadeTargetProperty = visible ? 1.0 : 0.0 } + property var completionCallback; // The target property to animate, usually scale or opacity property alias fadeTargetProperty: root.opacity // always start the property at 0 to enable fade in on creation @@ -33,6 +44,13 @@ FocusScope { fadeTargetProperty = target ? 1.0 : 0.0; return; } + + // Now handle completions + if (completionCallback) { + completionCallback(); + completionCallback = undefined; + } + } // The actual animator @@ -43,8 +61,17 @@ FocusScope { } } - // Once we're transparent, disable the dialog's visibility onFadeTargetPropertyChanged: { visible = (fadeTargetProperty != 0.0); } + + function fadeIn(callback) { + completionCallback = callback; + fadeTargetProperty = 1.0; + } + + function fadeOut(callback) { + completionCallback = callback; + fadeTargetProperty = 0.0; + } } diff --git a/interface/resources/qml/windows/Frame.qml b/interface/resources/qml/windows/Frame.qml index 20bf669b9a..9519a44cf0 100644 --- a/interface/resources/qml/windows/Frame.qml +++ b/interface/resources/qml/windows/Frame.qml @@ -1,24 +1,42 @@ -import QtQuick 2.5 +// +// Frame.qml +// +// Created by Bradley Austin Davis on 12 Jan 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// -import "../controls" +import QtQuick 2.5 +import QtGraphicalEffects 1.0 + +import "../styles-uit" import "../js/Utils.js" as Utils Item { id: frame + HifiConstants { id: hifi } + + default property var decoration + + property bool gradientsSupported: desktop.gradientsSupported + + readonly property int frameMarginLeft: frameContent.frameMarginLeft + readonly property int frameMarginRight: frameContent.frameMarginRight + readonly property int frameMarginTop: frameContent.frameMarginTop + readonly property int frameMarginBottom: frameContent.frameMarginBottom + // Frames always fill their parents, but their decorations may extend // beyond the window via negative margin sizes anchors.fill: parent - // Convenience accessor for the window - property alias window: frame.parent - readonly property int iconSize: 24 - default property var decoration; - children: [ + focusShadow, decoration, sizeOutline, debugZ, - sizeDrag, + sizeDrag ] Text { @@ -35,57 +53,81 @@ Item { window.height = newSize.y } + RadialGradient { + id: focusShadow + width: 1.66 * window.width + height: 1.66 * window.height + x: (window.width - width) / 2 + y: window.height / 2 - 0.375 * height + visible: gradientsSupported && window && window.focus && pane.visible + gradient: Gradient { + // GradientStop position 0.5 is at full circumference of circle that fits inside the square. + GradientStop { position: 0.0; color: "#ff000000" } // black, 100% opacity + GradientStop { position: 0.333; color: "#1f000000" } // black, 12% opacity + GradientStop { position: 0.5; color: "#00000000" } // black, 0% opacity + GradientStop { position: 1.0; color: "#00000000" } + } + cached: true + } + Rectangle { id: sizeOutline - width: window ? window.width : 0 - height: window ? window.height : 0 - color: "#00000000" - border.width: 4 - radius: 10 - visible: window ? !window.content.visible : false + x: -frameMarginLeft + y: -frameMarginTop + width: window ? window.width + frameMarginLeft + frameMarginRight + 2 : 0 + height: window ? window.height + frameMarginTop + frameMarginBottom + 2 : 0 + color: hifi.colors.baseGrayHighlight15 + border.width: 3 + border.color: hifi.colors.white50 + radius: hifi.dimensions.borderRadius + visible: window ? !pane.visible : false } MouseArea { + // Resize handle id: sizeDrag - width: iconSize - height: iconSize + width: hifi.dimensions.frameIconSize + height: hifi.dimensions.frameIconSize enabled: window ? window.resizable : false - x: window ? window.width : 0 - y: window ? window.height : 0 + hoverEnabled: true + x: window ? window.width + frameMarginRight - hifi.dimensions.frameIconSize : 0 + y: window ? window.height + 4 : 0 property vector2d pressOrigin property vector2d sizeOrigin property bool hid: false onPressed: { - console.log("Pressed on size") + //console.log("Pressed on size") pressOrigin = Qt.vector2d(mouseX, mouseY) sizeOrigin = Qt.vector2d(window.content.width, window.content.height) hid = false; } onReleased: { if (hid) { - window.content.visible = true + pane.visible = true + frameContent.visible = true hid = false; } } onPositionChanged: { if (pressed) { - if (window.content.visible) { - window.content.visible = false; + if (pane.visible) { + pane.visible = false; + frameContent.visible = false hid = true; } var delta = Qt.vector2d(mouseX, mouseY).minus(pressOrigin); frame.deltaSize(delta.x, delta.y) } } - FontAwesome { + HiFiGlyphs { visible: sizeDrag.enabled - rotation: -45 - anchors { centerIn: parent } - horizontalAlignment: Text.AlignHCenter - text: "\uf07d" - size: iconSize / 3 * 2 - style: Text.Outline; styleColor: "white" + x: -11 // Move a little to visually align + y: window.modality == Qt.ApplicationModal ? -6 : -4 + text: hifi.glyphs.resizeHandle + size: hifi.dimensions.frameIconSize + 10 + color: sizeDrag.containsMouse || sizeDrag.pressed + ? hifi.colors.white + : (window.colorScheme == hifi.colorSchemes.dark ? hifi.colors.white50 : hifi.colors.lightGrayText80) } } - } diff --git a/interface/resources/qml/windows/HiddenFrame.qml b/interface/resources/qml/windows/HiddenFrame.qml index 2621b71eed..3d3fd047e2 100644 --- a/interface/resources/qml/windows/HiddenFrame.qml +++ b/interface/resources/qml/windows/HiddenFrame.qml @@ -2,7 +2,7 @@ import QtQuick 2.5 import "." -Frame { +Item { id: frame Item { anchors.fill: parent } diff --git a/interface/resources/qml/windows/ModalFrame.qml b/interface/resources/qml/windows/ModalFrame.qml index eb4641bc75..211353b5f3 100644 --- a/interface/resources/qml/windows/ModalFrame.qml +++ b/interface/resources/qml/windows/ModalFrame.qml @@ -1,36 +1,98 @@ +// +// ModalFrame.qml +// +// Created by Bradley Austin Davis on 15 Jan 2016 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + import QtQuick 2.5 import "." -import "../controls" +import "../controls-uit" +import "../styles-uit" Frame { - id: frame + HifiConstants { id: hifi } - Item { - anchors.fill: parent + Rectangle { + id: frameContent - Rectangle { - id: background - anchors.fill: parent - anchors.margins: -4096 - visible: window.visible - color: "#7f7f7f7f"; - radius: 3; + readonly property bool hasTitle: window.title != "" + + readonly property int frameMarginLeft: hifi.dimensions.modalDialogMargin.x + readonly property int frameMarginRight: hifi.dimensions.modalDialogMargin.x + readonly property int frameMarginTop: hifi.dimensions.modalDialogMargin.y + (frameContent.hasTitle ? hifi.dimensions.modalDialogTitleHeight + 10 : 0) + readonly property int frameMarginBottom: hifi.dimensions.modalDialogMargin.y + + signal frameClicked(); + + anchors { + fill: parent + topMargin: -frameMarginTop + leftMargin: -frameMarginLeft + rightMargin: -frameMarginRight + bottomMargin: -frameMarginBottom } - Text { - y: -implicitHeight - iconSize / 2 - text: window.title - elide: Text.ElideRight - font.bold: true - color: window.focus ? "white" : "gray" - style: Text.Outline; - styleColor: "black" + border { + width: hifi.dimensions.borderWidth + color: hifi.colors.lightGrayText80 + } + radius: hifi.dimensions.borderRadius + color: hifi.colors.faintGray + + // Enable dragging of the window + MouseArea { + anchors.fill: parent + drag.target: window + enabled: window.draggable + onClicked: window.frameClicked(); + } + + Item { + visible: frameContent.hasTitle + anchors.fill: parent + anchors { + topMargin: -parent.anchors.topMargin + leftMargin: -parent.anchors.leftMargin + rightMargin: -parent.anchors.rightMargin + } + + Item { + width: title.width + (icon.text !== "" ? icon.width + hifi.dimensions.contentSpacing.x : 0) + x: (parent.width - width) / 2 + + onWidthChanged: window.titleWidth = width + + HiFiGlyphs { + id: icon + text: window.iconText ? window.iconText : "" + size: window.iconSize ? window.iconSize : 30 + color: hifi.colors.lightGray + visible: text != "" + anchors.verticalCenter: title.verticalCenter + anchors.left: parent.left + } + RalewayRegular { + id: title + text: window.title + elide: Text.ElideRight + color: hifi.colors.baseGrayHighlight + size: hifi.fontSizes.overlayTitle + y: -hifi.dimensions.modalDialogTitleHeight + anchors.right: parent.right + } + } + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + height: 1 + color: hifi.colors.lightGray + } } } - - - - } - diff --git a/interface/resources/qml/windows/ModalWindow.qml b/interface/resources/qml/windows/ModalWindow.qml index 32443e70e3..144165e4e1 100644 --- a/interface/resources/qml/windows/ModalWindow.qml +++ b/interface/resources/qml/windows/ModalWindow.qml @@ -1,14 +1,28 @@ +// +// ModalWindow.qml +// +// Created by Bradley Austin Davis on 22 Jan 2016 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + import QtQuick 2.5 import "." Window { - id: root - anchors.centerIn: parent + id: window modality: Qt.ApplicationModal destroyOnCloseButton: true destroyOnInvisible: true - frame: ModalFrame{} + frame: ModalFrame { } + + property int colorScheme: hifi.colorSchemes.light + property bool draggable: false + + signal frameClicked(); + + anchors.centerIn: draggable ? undefined : parent } - - diff --git a/interface/resources/qml/windows/Window.qml b/interface/resources/qml/windows/Window.qml index 3abdbacc64..ed1b820fc8 100644 --- a/interface/resources/qml/windows/Window.qml +++ b/interface/resources/qml/windows/Window.qml @@ -1,9 +1,20 @@ +// +// Window.qml +// +// Created by Bradley Austin Davis on 12 Jan 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + import QtQuick 2.5 import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 import QtGraphicalEffects 1.0 import "." -import "../styles" +import "../styles-uit" // FIXME how do I set the initial position of a window without // overriding places where the a individual client of the window @@ -15,16 +26,36 @@ import "../styles" Fadable { id: window HifiConstants { id: hifi } + + // + // Signals + // + signal windowDestroyed(); + + // + // Native properties + // + // The Window size is the size of the content, while the frame // decorations can extend outside it. implicitHeight: content ? content.height : 0 implicitWidth: content ? content.width : 0 x: desktop.invalid_position; y: desktop.invalid_position; - enabled: visible + children: [ swallower, frame, pane, activator ] - signal windowDestroyed(); + // + // Custom properties + // property int modality: Qt.NonModal + // Corresponds to the window shown / hidden state AS DISTINCT from window visibility. + // Window visibility should NOT be used as a proxy for any other behavior. + property bool shown: true + // FIXME workaround to deal with the face that some visual items are defined here, + // when they should be moved to a frame derived type + property bool hideBackground: false + visible: shown + enabled: visible readonly property bool topLevelWindow: true property string title // Should the window be closable control? @@ -34,17 +65,23 @@ Fadable { // Should hitting the close button hide or destroy the window? property bool destroyOnCloseButton: true // Should hiding the window destroy it or just hide it? - property bool destroyOnInvisible: false - // FIXME support for pinned / unpinned pending full design - // property bool pinnable: false - // property bool pinned: false + property bool destroyOnHidden: false + property bool pinnable: true + property bool pinned: false property bool resizable: false + property bool gradientsSupported: desktop.gradientsSupported + property int colorScheme: hifi.colorSchemes.dark + property vector2d minSize: Qt.vector2d(100, 100) - property vector2d maxSize: Qt.vector2d(1280, 720) + property vector2d maxSize: Qt.vector2d(1280, 800) // The content to place inside the window, determined by the client default property var content + property var footer: Item { } // Optional static footer at the bottom of the dialog. + + function setDefaultFocus() {} // Default function; can be overridden by dialogs. + property var rectifier: Timer { property bool executing: false; interval: 100 @@ -65,20 +102,15 @@ Fadable { } } - - onXChanged: rectifier.begin(); - onYChanged: rectifier.begin(); - // This mouse area serves to raise the window. To function, it must live // in the window and have a higher Z-order than the content, but follow // the position and size of frame decoration property var activator: MouseArea { width: frame.decoration.width height: frame.decoration.height - x: frame.decoration.anchors.margins + x: frame.decoration.anchors.leftMargin y: frame.decoration.anchors.topMargin propagateComposedEvents: true - hoverEnabled: true acceptedButtons: Qt.AllButtons enabled: window.visible onPressed: { @@ -94,7 +126,7 @@ Fadable { property var swallower: MouseArea { width: frame.decoration.width height: frame.decoration.height - x: frame.decoration.anchors.margins + x: frame.decoration.anchors.leftMargin y: frame.decoration.anchors.topMargin hoverEnabled: true acceptedButtons: Qt.AllButtons @@ -106,71 +138,241 @@ Fadable { onWheel: {} } - // Default to a standard frame. Can be overriden to provide custom // frame styles, like a full desktop frame to simulate a modal window property var frame: DefaultFrame { } + // Scrollable window content. + // FIXME this should not define any visual content in this type. The base window + // type should only consist of logic sized areas, with nothing drawn (although the + // default value for the frame property does include visual decorations) + property var pane: Item { + property bool isScrolling: scrollView.height < scrollView.contentItem.height + property int contentWidth: scrollView.width - (isScrolling ? 10 : 0) + property int scrollHeight: scrollView.height - children: [ swallower, frame, content, activator ] + anchors.fill: parent + anchors.rightMargin: isScrolling ? 11 : 0 + Rectangle { + id: contentBackground + anchors.fill: parent + anchors.rightMargin: parent.isScrolling ? 11 : 0 + color: hifi.colors.baseGray + visible: !window.hideBackground && modality != Qt.ApplicationModal + } + + + LinearGradient { + visible: !window.hideBackground && gradientsSupported && modality != Qt.ApplicationModal + anchors.top: contentBackground.bottom + anchors.left: contentBackground.left + width: contentBackground.width - 1 + height: 4 + start: Qt.point(0, 0) + end: Qt.point(0, 4) + gradient: Gradient { + GradientStop { position: 0.0; color: hifi.colors.darkGray } + GradientStop { position: 1.0; color: hifi.colors.darkGray0 } + } + cached: true + } + + ScrollView { + id: scrollView + contentItem: content + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff + verticalScrollBarPolicy: Qt.ScrollBarAsNeeded + anchors.fill: parent + anchors.rightMargin: parent.isScrolling ? 1 : 0 + anchors.bottomMargin: footer.height > 0 ? footerPane.height : 0 + + style: ScrollViewStyle { + + padding.right: -7 // Move to right away from content. + + handle: Item { + implicitWidth: 8 + Rectangle { + radius: 4 + color: hifi.colors.white30 + anchors { + fill: parent + leftMargin: 2 // Finesse size and position. + topMargin: 1 + bottomMargin: 1 + } + } + } + + scrollBarBackground: Item { + implicitWidth: 10 + Rectangle { + color: hifi.colors.darkGray30 + radius: 4 + anchors { + fill: parent + topMargin: -1 // Finesse size + bottomMargin: -2 + } + } + } + + incrementControl: Item { + visible: false + } + + decrementControl: Item { + visible: false + } + } + } + + Rectangle { + // Optional non-scrolling footer. + id: footerPane + anchors { + left: parent.left + bottom: parent.bottom + } + width: parent.contentWidth + height: footer.height + 2 * hifi.dimensions.contentSpacing.y + 3 + color: hifi.colors.baseGray + visible: footer.height > 0 + + Item { + // Horizontal rule. + anchors.fill: parent + + Rectangle { + width: parent.width + height: 1 + y: 1 // Stop displaying content just above horizontal rule/=. + color: hifi.colors.baseGrayShadow + } + + Rectangle { + width: parent.width + height: 1 + y: 2 + color: hifi.colors.baseGrayHighlight + } + } + + Item { + anchors.fill: parent + anchors.topMargin: 3 // Horizontal rule. + children: [ footer ] + } + } + } + + // + // Handlers + // Component.onCompleted: { window.parentChanged.connect(raise); - raise(); - centerOrReposition(); + setDefaultFocus(); + d.centerOrReposition(); + d.updateVisibility(shown); } Component.onDestruction: { window.parentChanged.disconnect(raise); // Prevent warning on shutdown windowDestroyed(); } - function centerOrReposition() { - if (x == desktop.invalid_position && y == desktop.invalid_position) { - desktop.centerOnVisible(window); - } else { - desktop.repositionOnVisible(window); - } - } + onXChanged: rectifier.begin(); + onYChanged: rectifier.begin(); + + onShownChanged: d.updateVisibility(shown) onVisibleChanged: { - if (!visible && destroyOnInvisible) { - destroy(); - return; - } - if (visible) { - raise(); - } enabled = visible - if (visible && parent) { - centerOrReposition(); + d.centerOrReposition(); } } + QtObject { + id: d + + readonly property alias pinned: window.pinned + readonly property alias shown: window.shown + readonly property alias modality: window.modality; + + function getTargetVisibility() { + if (!window.shown) { + return false; + } + + if (modality !== Qt.NonModal) { + return true; + } + + if (pinned) { + return true; + } + + if (desktop && !desktop.pinned) { + return true; + } + + return false; + } + + // The force flag causes all windows to fade back in, because a window was shown + readonly property alias visible: window.visible + function updateVisibility(force) { + if (force && !pinned && desktop.pinned) { + // Change the pinned state (which in turn will call us again) + desktop.pinned = false; + return; + } + + var targetVisibility = getTargetVisibility(); + if (targetVisibility === visible) { + return; + } + + if (targetVisibility) { + fadeIn(function() { + if (force) { + window.raise(); + } + }); + } else { + fadeOut(function() { + if (!window.shown && window.destroyOnHidden) { + window.destroy(); + } + }); + } + } + + function centerOrReposition() { + if (x == desktop.invalid_position && y == desktop.invalid_position) { + desktop.centerOnVisible(window); + } else { + desktop.repositionOnVisible(window); + } + } + + } + + // When the desktop pinned state changes, automatically handle the current windows + Connections { target: desktop; onPinnedChanged: d.updateVisibility() } + + function raise() { if (visible && parent) { desktop.raise(window) } } - function pin() { -// pinned = ! pinned + function setPinned() { + pinned = !pinned } - // our close function performs the same way as the OffscreenUI class: - // don't do anything but manipulate the targetVisible flag and let the other - // mechanisms decide if the window should be destroyed after the close - // animation completes - // FIXME using this close function messes up the visibility signals received by the - // type and it's derived types -// function close() { -// console.log("Closing " + window) -// if (destroyOnCloseButton) { -// destroyOnInvisible = true -// } -// visible = false; -// } - function framedRect() { if (!frame || !frame.decoration) { return Qt.rect(0, 0, window.width, window.height) @@ -180,7 +382,6 @@ Fadable { window.height - frame.decoration.anchors.topMargin - frame.decoration.anchors.bottomMargin) } - Keys.onPressed: { switch(event.key) { case Qt.Key_Control: @@ -189,10 +390,9 @@ Fadable { case Qt.Key_Alt: break; - case Qt.Key_W: if (window.closable && (event.modifiers === Qt.ControlModifier)) { - visible = false + shown = false event.accepted = true } // fall through diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 48b418b93c..c63973985a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2057,7 +2057,8 @@ void Application::keyPressEvent(QKeyEvent* event) { case Qt::Key_X: if (isShifted && isMeta) { auto offscreenUi = DependencyManager::get(); - offscreenUi->getRootContext()->engine()->clearComponentCache(); + offscreenUi->togglePinned(); + //offscreenUi->getRootContext()->engine()->clearComponentCache(); //OffscreenUi::information("Debugging", "Component cache cleared"); // placeholder for dialogs being converted to QML. } diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index 83d729779c..54dba229e3 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -96,7 +96,7 @@ void OverlayConductor::updateMode() { myAvatar->reset(true, false, false); } if (_wantsOverlays) { - setEnabled(!nowDriving, false); + setEnabled(!nowDriving); } _driving = nowDriving; } // Else haven't accumulated enough time in new mode, but keep timing. @@ -114,18 +114,14 @@ void OverlayConductor::updateMode() { case SITTING: { // enter the SITTING state // place the overlay at origin - Transform identity; - qApp->getApplicationCompositor().setModelTransform(identity); + qApp->getApplicationCompositor().setModelTransform(Transform()); break; } case STANDING: { // STANDING mode is not currently used. // enter the STANDING state // place the overlay at the current hmd position in world space auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose()); - Transform t; - t.setTranslation(extractTranslation(camMat)); - t.setRotation(glm::quat_cast(camMat)); - qApp->getApplicationCompositor().setModelTransform(t); + qApp->getApplicationCompositor().setModelTransform(Transform(camMat)); break; } @@ -139,54 +135,21 @@ void OverlayConductor::updateMode() { } -void OverlayConductor::setEnabled(bool enabled, bool toggleQmlEvents) { - +void OverlayConductor::setEnabled(bool enabled) { if (enabled == _enabled) { return; } - if (toggleQmlEvents) { // Could recurse on us with the wrong toggleQmlEvents flag, and not need in the !toggleQmlEvent case anyway. - Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, enabled); - } - _enabled = enabled; // set the new value - + auto offscreenUi = DependencyManager::get(); + offscreenUi->setPinned(!_enabled); // if the new state is visible/enabled... - if (_enabled) { - // alpha fadeIn the overlay mesh. - qApp->getApplicationCompositor().fadeIn(); - - // enable mouse clicks from script - qApp->getOverlays().enable(); - - // enable QML events - if (toggleQmlEvents) { - auto offscreenUi = DependencyManager::get(); - offscreenUi->getRootItem()->setEnabled(true); - } - - if (_mode == STANDING) { - // place the overlay at the current hmd position in world space - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose()); - Transform t; - t.setTranslation(extractTranslation(camMat)); - t.setRotation(glm::quat_cast(camMat)); - qApp->getApplicationCompositor().setModelTransform(t); - } - } else { // other wise, if the new state is hidden/not enabled - // alpha fadeOut the overlay mesh. - qApp->getApplicationCompositor().fadeOut(); - - // disable mouse clicks from script - qApp->getOverlays().disable(); - - // disable QML events - if (toggleQmlEvents) { // I'd really rather always do this, but it looses drive state. bugzid:501 - auto offscreenUi = DependencyManager::get(); - offscreenUi->getRootItem()->setEnabled(false); - } - } + if (_enabled && _mode == STANDING) { + // place the overlay at the current hmd position in world space + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose()); + qApp->getApplicationCompositor().setModelTransform(Transform(camMat)); + } } bool OverlayConductor::getEnabled() const { diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h index 99f4b56584..1ec66663a4 100644 --- a/interface/src/ui/OverlayConductor.h +++ b/interface/src/ui/OverlayConductor.h @@ -17,7 +17,7 @@ public: ~OverlayConductor(); void update(float dt); - void setEnabled(bool enable, bool toggleQmlEvents = true); + void setEnabled(bool enable); bool getEnabled() const; private: diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp index f9d527de8f..d4fff1b976 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp @@ -346,7 +346,7 @@ bool CompositorHelper::calculateRayUICollisionPoint(const glm::vec3& position, c auto relativePosition = vec3(relativePosition4) / relativePosition4.w; auto relativeDirection = glm::inverse(glm::quat_cast(UITransform)) * direction; - float uiRadius = _oculusUIRadius; // * myAvatar->getUniformScale(); // FIXME - how do we want to handle avatar scale + float uiRadius = _hmdUIRadius; // * myAvatar->getUniformScale(); // FIXME - how do we want to handle avatar scale float instersectionDistance; if (raySphereIntersect(relativeDirection, relativePosition, uiRadius, &instersectionDistance)){ @@ -407,60 +407,6 @@ void CompositorHelper::updateTooltips() { //} } -static const float FADE_DURATION = 500.0f; -static const float FADE_IN_ALPHA = 1.0f; -static const float FADE_OUT_ALPHA = 0.0f; - -void CompositorHelper::startFadeFailsafe(float endValue) { - _fadeStarted = usecTimestampNow(); - _fadeFailsafeEndValue = endValue; - - const int SLIGHT_DELAY = 10; - QTimer::singleShot(FADE_DURATION + SLIGHT_DELAY, [this]{ - checkFadeFailsafe(); - }); -} - -void CompositorHelper::checkFadeFailsafe() { - auto elapsedInFade = usecTimestampNow() - _fadeStarted; - if (elapsedInFade > FADE_DURATION) { - setAlpha(_fadeFailsafeEndValue); - } -} - -void CompositorHelper::fadeIn() { - _fadeInAlpha = true; - - _alphaPropertyAnimation->setDuration(FADE_DURATION); - _alphaPropertyAnimation->setStartValue(_alpha); - _alphaPropertyAnimation->setEndValue(FADE_IN_ALPHA); - _alphaPropertyAnimation->start(); - - // Sometimes, this "QPropertyAnimation" fails to complete the animation, and we end up with a partially faded - // state. So we will also have this fail-safe, where we record the timestamp of the fadeRequest, and the target - // value of the fade, and if after that time we still haven't faded all the way, we will kick it to the final - // fade value - startFadeFailsafe(FADE_IN_ALPHA); -} - -void CompositorHelper::fadeOut() { - _fadeInAlpha = false; - - _alphaPropertyAnimation->setDuration(FADE_DURATION); - _alphaPropertyAnimation->setStartValue(_alpha); - _alphaPropertyAnimation->setEndValue(FADE_OUT_ALPHA); - _alphaPropertyAnimation->start(); - startFadeFailsafe(FADE_OUT_ALPHA); -} - -void CompositorHelper::toggle() { - if (_fadeInAlpha) { - fadeOut(); - } else { - fadeIn(); - } -} - glm::mat4 CompositorHelper::getReticleTransform(const glm::mat4& eyePose, const glm::vec3& headPosition) const { glm::mat4 result; if (isHMD()) { diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.h b/libraries/display-plugins/src/display-plugins/CompositorHelper.h index c0b53b329e..868beec53f 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.h +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.h @@ -38,7 +38,6 @@ const float MAGNIFY_MULT = 2.0f; class CompositorHelper : public QObject, public Dependency { Q_OBJECT - Q_PROPERTY(float alpha READ getAlpha WRITE setAlpha) Q_PROPERTY(bool reticleOverDesktop READ getReticleOverDesktop WRITE setReticleOverDesktop) public: static const uvec2 VIRTUAL_SCREEN_SIZE; @@ -75,13 +74,6 @@ public: void setModelTransform(const Transform& transform) { _modelTransform = transform; } const Transform& getModelTransform() const { return _modelTransform; } - void fadeIn(); - void fadeOut(); - void toggle(); - - float getAlpha() const { return _alpha; } - void setAlpha(float alpha) { _alpha = alpha; } - bool getReticleVisible() const { return _reticleVisible; } void setReticleVisible(bool visible) { _reticleVisible = visible; } @@ -113,7 +105,7 @@ public: void setReticleOverDesktop(bool value) { _isOverDesktop = value; } void setDisplayPlugin(const DisplayPluginPointer& displayPlugin) { _currentDisplayPlugin = displayPlugin; } - void setFrameInfo(uint32_t frame, const glm::mat4& camera) { _currentCamera = camera; _currentFrame = frame; } + void setFrameInfo(uint32_t frame, const glm::mat4& camera) { _currentCamera = camera; } signals: void allowMouseCaptureChanged(); @@ -127,7 +119,6 @@ private: DisplayPluginPointer _currentDisplayPlugin; glm::mat4 _currentCamera; - uint32_t _currentFrame { 0 }; QWidget* _renderingWidget{ nullptr }; //// Support for hovering and tooltips @@ -143,17 +134,7 @@ private: float _textureFov { VIRTUAL_UI_TARGET_FOV.y }; float _textureAspectRatio { VIRTUAL_UI_ASPECT_RATIO }; - float _alpha { 1.0f }; - float _prevAlpha { 1.0f }; - float _fadeInAlpha { true }; - float _oculusUIRadius { 1.0f }; - - quint64 _fadeStarted { 0 }; - float _fadeFailsafeEndValue { 1.0f }; - void checkFadeFailsafe(); - void startFadeFailsafe(float endValue); - - int _reticleQuad; + float _hmdUIRadius { 1.0f }; int _previousBorderWidth { -1 }; int _previousBorderHeight { -1 }; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index d34b698410..d9ee979777 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -445,25 +445,18 @@ void OpenGLDisplayPlugin::compositeOverlay() { useProgram(_program); // check the alpha - auto overlayAlpha = compositorHelper->getAlpha(); - if (overlayAlpha > 0.0f) { - // set the alpha - Uniform(*_program, _alphaUniform).Set(overlayAlpha); - - // Overlay draw - if (isStereo()) { - Uniform(*_program, _mvpUniform).Set(mat4()); - for_each_eye([&](Eye eye) { - eyeViewport(eye); - drawUnitQuad(); - }); - } else { - // Overlay draw - Uniform(*_program, _mvpUniform).Set(mat4()); + // Overlay draw + if (isStereo()) { + Uniform(*_program, _mvpUniform).Set(mat4()); + for_each_eye([&](Eye eye) { + eyeViewport(eye); drawUnitQuad(); - } + }); + } else { + // Overlay draw + Uniform(*_program, _mvpUniform).Set(mat4()); + drawUnitQuad(); } - Uniform(*_program, _alphaUniform).Set(1.0); } void OpenGLDisplayPlugin::compositePointer() { @@ -471,24 +464,16 @@ void OpenGLDisplayPlugin::compositePointer() { auto compositorHelper = DependencyManager::get(); useProgram(_program); - // check the alpha - auto overlayAlpha = compositorHelper->getAlpha(); - if (overlayAlpha > 0.0f) { - // set the alpha - Uniform(*_program, _alphaUniform).Set(overlayAlpha); - - Uniform(*_program, _mvpUniform).Set(compositorHelper->getReticleTransform(glm::mat4())); - if (isStereo()) { - for_each_eye([&](Eye eye) { - eyeViewport(eye); - drawUnitQuad(); - }); - } else { + Uniform(*_program, _mvpUniform).Set(compositorHelper->getReticleTransform(glm::mat4())); + if (isStereo()) { + for_each_eye([&](Eye eye) { + eyeViewport(eye); drawUnitQuad(); - } + }); + } else { + drawUnitQuad(); } Uniform(*_program, _mvpUniform).Set(mat4()); - Uniform(*_program, _alphaUniform).Set(1.0); } void OpenGLDisplayPlugin::compositeScene() { diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 4e594d89ed..1616dcdb77 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -254,23 +254,15 @@ void HmdDisplayPlugin::compositeOverlay() { using namespace oglplus; auto compositorHelper = DependencyManager::get(); - // check the alpha useProgram(_program); - auto overlayAlpha = compositorHelper->getAlpha(); - if (overlayAlpha > 0.0f) { - // set the alpha - Uniform(*_program, _alphaUniform).Set(overlayAlpha); - - _sphereSection->Use(); - for_each_eye([&](Eye eye) { - eyeViewport(eye); - auto modelView = glm::inverse(_currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye)); - auto mvp = _eyeProjections[eye] * modelView; - Uniform(*_program, _mvpUniform).Set(mvp); - _sphereSection->Draw(); - }); - } - Uniform(*_program, _alphaUniform).Set(1.0); + _sphereSection->Use(); + for_each_eye([&](Eye eye) { + eyeViewport(eye); + auto modelView = glm::inverse(_currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye)); + auto mvp = _eyeProjections[eye] * modelView; + Uniform(*_program, _mvpUniform).Set(mvp); + _sphereSection->Draw(); + }); } void HmdDisplayPlugin::compositePointer() { @@ -280,25 +272,19 @@ void HmdDisplayPlugin::compositePointer() { // check the alpha useProgram(_program); - auto overlayAlpha = compositorHelper->getAlpha(); - if (overlayAlpha > 0.0f) { - // set the alpha - Uniform(*_program, _alphaUniform).Set(overlayAlpha); - // Mouse pointer - _plane->Use(); - // Reconstruct the headpose from the eye poses - auto headPosition = vec3(_currentPresentFrameInfo.presentPose[3]); - for_each_eye([&](Eye eye) { - eyeViewport(eye); - auto eyePose = _currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye); - auto reticleTransform = compositorHelper->getReticleTransform(eyePose, headPosition); - auto mvp = _eyeProjections[eye] * reticleTransform; - Uniform(*_program, _mvpUniform).Set(mvp); - _plane->Draw(); - }); - } - Uniform(*_program, _alphaUniform).Set(1.0); + // Mouse pointer + _plane->Use(); + // Reconstruct the headpose from the eye poses + auto headPosition = vec3(_currentPresentFrameInfo.presentPose[3]); + for_each_eye([&](Eye eye) { + eyeViewport(eye); + auto eyePose = _currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye); + auto reticleTransform = compositorHelper->getReticleTransform(eyePose, headPosition); + auto mvp = _eyeProjections[eye] * reticleTransform; + Uniform(*_program, _mvpUniform).Set(mvp); + _plane->Draw(); + }); } void HmdDisplayPlugin::internalPresent() { diff --git a/libraries/ui/src/ErrorDialog.cpp b/libraries/ui/src/ErrorDialog.cpp index ab36ef8d36..fcd73b4cc0 100644 --- a/libraries/ui/src/ErrorDialog.cpp +++ b/libraries/ui/src/ErrorDialog.cpp @@ -22,10 +22,6 @@ QString ErrorDialog::text() const { return _text; } -void ErrorDialog::setVisible(bool v) { - OffscreenQmlDialog::setVisible(v); -} - void ErrorDialog::setText(const QString& arg) { if (arg != _text) { _text = arg; diff --git a/libraries/ui/src/ErrorDialog.h b/libraries/ui/src/ErrorDialog.h index 665090da1a..38954714a7 100644 --- a/libraries/ui/src/ErrorDialog.h +++ b/libraries/ui/src/ErrorDialog.h @@ -30,7 +30,6 @@ public: QString text() const; public slots: - virtual void setVisible(bool v); void setText(const QString& arg); signals: diff --git a/libraries/ui/src/OffscreenQmlDialog.cpp b/libraries/ui/src/OffscreenQmlDialog.cpp index 43514c4761..2d1ca20876 100644 --- a/libraries/ui/src/OffscreenQmlDialog.cpp +++ b/libraries/ui/src/OffscreenQmlDialog.cpp @@ -17,7 +17,7 @@ OffscreenQmlDialog::~OffscreenQmlDialog() { } void OffscreenQmlDialog::hide() { - static_cast(parent())->setVisible(false); + parent()->setProperty(OFFSCREEN_VISIBILITY_PROPERTY, false); } QString OffscreenQmlDialog::title() const { diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index dfd9056703..fa1a31d196 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -121,32 +121,28 @@ void OffscreenUi::show(const QUrl& url, const QString& name, std::functionfindChild(name); } + if (item) { - item->setVisible(true); + QQmlProperty(item, OFFSCREEN_VISIBILITY_PROPERTY).write(true); } } void OffscreenUi::toggle(const QUrl& url, const QString& name, std::function f) { QQuickItem* item = getRootItem()->findChild(name); - // Already loaded? - if (item) { - emit showDesktop(); - item->setVisible(!item->isVisible()); + if (!item) { + show(url, name, f); return; } - load(url, f); - item = getRootItem()->findChild(name); - if (item && !item->isVisible()) { - emit showDesktop(); - item->setVisible(true); - } + // Already loaded, so just flip the bit + QQmlProperty shownProperty(item, OFFSCREEN_VISIBILITY_PROPERTY); + shownProperty.write(!shownProperty.read().toBool()); } void OffscreenUi::hide(const QString& name) { QQuickItem* item = getRootItem()->findChild(name); if (item) { - item->setVisible(false); + QQmlProperty(item, OFFSCREEN_VISIBILITY_PROPERTY).write(false); } } @@ -345,6 +341,20 @@ QVariant OffscreenUi::inputDialog(const Icon icon, const QString& title, const Q return waitForInputDialogResult(createInputDialog(icon, title, label, current)); } +void OffscreenUi::togglePinned() { + bool invokeResult = QMetaObject::invokeMethod(_desktop, "togglePinned"); + if (!invokeResult) { + qWarning() << "Failed to toggle window visibility"; + } +} + +void OffscreenUi::setPinned(bool pinned) { + bool invokeResult = QMetaObject::invokeMethod(_desktop, "setPinned", Q_ARG(QVariant, pinned)); + if (!invokeResult) { + qWarning() << "Failed to set window visibility"; + } +} + void OffscreenUi::addMenuInitializer(std::function f) { if (!_vrMenu) { _queuedMenuInitializers.push_back(f); diff --git a/libraries/ui/src/OffscreenUi.h b/libraries/ui/src/OffscreenUi.h index 5a16b49491..e1d552c978 100644 --- a/libraries/ui/src/OffscreenUi.h +++ b/libraries/ui/src/OffscreenUi.h @@ -28,6 +28,8 @@ class VrMenu; +#define OFFSCREEN_VISIBILITY_PROPERTY "shown" + class OffscreenUi : public OffscreenQmlSurface, public Dependency { Q_OBJECT @@ -44,6 +46,13 @@ public: void setNavigationFocused(bool focused); void unfocusWindows(); void toggleMenu(const QPoint& screenCoordinates); + + + // Setting pinned to true will hide all overlay elements on the desktop that don't have a pinned flag + void setPinned(bool pinned = true); + + void togglePinned(); + bool eventFilter(QObject* originalDestination, QEvent* event) override; void addMenuInitializer(std::function f); diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp index 396d716cda..b8834f0549 100644 --- a/libraries/ui/src/QmlWindowClass.cpp +++ b/libraries/ui/src/QmlWindowClass.cpp @@ -163,8 +163,7 @@ void QmlWindowClass::setVisible(bool visible) { QMetaObject::invokeMethod(targetWindow, "showTabForUrl", Qt::QueuedConnection, Q_ARG(QVariant, _source), Q_ARG(QVariant, visible)); } else { DependencyManager::get()->executeOnUiThread([=] { - targetWindow->setVisible(visible); - //emit visibilityChanged(visible); + targetWindow->setProperty(OFFSCREEN_VISIBILITY_PROPERTY, visible); }); } } diff --git a/libraries/ui/src/Tooltip.cpp b/libraries/ui/src/Tooltip.cpp index 3c0902b378..94e04f34b6 100644 --- a/libraries/ui/src/Tooltip.cpp +++ b/libraries/ui/src/Tooltip.cpp @@ -47,10 +47,6 @@ void Tooltip::setImageURL(const QString& imageURL) { } } -void Tooltip::setVisible(bool visible) { - QQuickItem::setVisible(visible); -} - QString Tooltip::showTip(const QString& title, const QString& description) { const QString newTipId = QUuid().createUuid().toString(); diff --git a/libraries/ui/src/Tooltip.h b/libraries/ui/src/Tooltip.h index d1c7330a74..5e884a7aea 100644 --- a/libraries/ui/src/Tooltip.h +++ b/libraries/ui/src/Tooltip.h @@ -39,8 +39,6 @@ public: static void closeTip(const QString& tipId); public slots: - virtual void setVisible(bool v); - void setTitle(const QString& title); void setDescription(const QString& description); void setImageURL(const QString& imageURL); diff --git a/tests/ui/qml/main.qml b/tests/ui/qml/main.qml index 97f0c0a613..3b2cc264c8 100644 --- a/tests/ui/qml/main.qml +++ b/tests/ui/qml/main.qml @@ -5,7 +5,7 @@ import Qt.labs.settings 1.0 import "../../../interface/resources/qml" //import "../../../interface/resources/qml/windows" -import "../../../interface/resources/qml/windows-uit" +import "../../../interface/resources/qml/windows" import "../../../interface/resources/qml/dialogs" import "../../../interface/resources/qml/hifi" import "../../../interface/resources/qml/hifi/dialogs" @@ -17,6 +17,267 @@ ApplicationWindow { width: 1280 height: 800 title: qsTr("Scratch App") + toolBar: Row { + id: testButtons + anchors { margins: 8; left: parent.left; top: parent.top } + spacing: 8 + property int count: 0 + + property var tabs: []; + property var urls: []; + + // Window visibility + + Button { + text: "restore all" + onClicked: { + for (var i = 0; i < desktop.windows.length; ++i) { + desktop.windows[i].shown = true + } + } + } + Button { + text: "toggle blue visible" + onClicked: { + blue.shown = !blue.shown + } + } + Button { + text: "toggle blue enabled" + onClicked: { + blue.enabled = !blue.enabled + } + } + + Button { + text: "toggle desktop" + onClicked: desktop.toggleVisible() + } + + // Error alerts + /* + Button { + // Message without title. + text: "Show Error" + onClicked: { + var messageBox = desktop.messageBox({ + text: "Diagnostic cycle will be complete in 30 seconds", + icon: hifi.icons.critical, + }); + messageBox.selected.connect(function(button) { + console.log("You clicked " + button) + }) + } + } + Button { + // detailedText is not currently used anywhere in Interface but it is easier to leave in and style good enough. + text: "Show Long Error" + onClicked: { + desktop.messageBox({ + informativeText: "Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds ", + text: "Baloney", + icon: hifi.icons.warning, + detailedText: "sakjd;laskj dksa;dl jka;lsd j;lkjas ;dlkaj s;dlakjd ;alkjda; slkjda; lkjda;lksjd ;alksjd; alksjd ;alksjd; alksjd; alksdjas;ldkjas;lkdja ;kj ;lkasjd; lkj as;dlka jsd;lka jsd;laksjd a" + }); + } + } + */ + + // query + /* + // There is no such desktop.queryBox() function; may need to update test to cover QueryDialog.qml? + Button { + text: "Show Query" + onClicked: { + var queryBox = desktop.queryBox({ + text: "Have you stopped beating your wife?", + placeholderText: "Are you sure?", + // icon: hifi.icons.critical, + }); + queryBox.selected.connect(function(result) { + console.log("User responded with " + result); + }); + + queryBox.canceled.connect(function() { + console.log("User cancelled query box "); + }) + } + } + */ + + // file dialog + + Button { + text: "Open Directory" + property var builder: Component { + FileDialog { selectDirectory: true } + } + + onClicked: { + var fileDialog = builder.createObject(desktop); + fileDialog.canceled.connect(function(){ + console.log("Cancelled") + }) + fileDialog.selectedFile.connect(function(file){ + console.log("Selected " + file) + }) + } + } + Button { + text: "Open File" + property var builder: Component { + FileDialog { + title: "Open File" + filter: "All Files (*.*)" + //filter: "HTML files (*.html);;Other(*.png)" + } + } + + onClicked: { + var fileDialog = builder.createObject(desktop); + fileDialog.canceled.connect(function(){ + console.log("Cancelled") + }) + fileDialog.selectedFile.connect(function(file){ + console.log("Selected " + file) + }) + } + } + /* + */ + + // tabs + /* + Button { + text: "Add Tab" + onClicked: { + console.log(desktop.toolWindow); + desktop.toolWindow.addWebTab({ source: "Foo" }); + desktop.toolWindow.showTabForUrl("Foo", true); + } + } + + Button { + text: "Add Tab 2" + onClicked: { + console.log(desktop.toolWindow); + desktop.toolWindow.addWebTab({ source: "Foo 2" }); + desktop.toolWindow.showTabForUrl("Foo 2", true); + } + } + + Button { + text: "Add Tab 3" + onClicked: { + console.log(desktop.toolWindow); + desktop.toolWindow.addWebTab({ source: "Foo 3" }); + desktop.toolWindow.showTabForUrl("Foo 3", true); + } + } + + Button { + text: "Destroy Tab" + onClicked: { + console.log(desktop.toolWindow); + desktop.toolWindow.removeTabForUrl("Foo"); + } + } + */ + + // Hifi specific stuff + /* + Button { + // Shows the dialog with preferences sections but not each section's preference items + // because Preferences.preferencesByCategory() method is not stubbed out. + text: "Settings > General..." + property var builder: Component { + GeneralPreferencesDialog { } + } + onClicked: { + var runningScripts = builder.createObject(desktop); + } + } + + Button { + text: "Running Scripts" + property var builder: Component { + RunningScripts { } + } + onClicked: { + var runningScripts = builder.createObject(desktop); + } + } + + Button { + text: "Attachments" + property var builder: Component { + AttachmentsDialog { } + } + onClicked: { + var attachmentsDialog = builder.createObject(desktop); + } + } + Button { + // Replicates message box that pops up after selecting new avatar. Includes title. + text: "Confirm Avatar" + onClicked: { + var messageBox = desktop.messageBox({ + title: "Set Avatar", + text: "Would you like to use 'Albert' for your avatar?", + icon: hifi.icons.question, // Test question icon + //icon: hifi.icons.information, // Test informaton icon + //icon: hifi.icons.warning, // Test warning icon + //icon: hifi.icons.critical, // Test critical icon + //icon: hifi.icons.none, // Test no icon + buttons: OriginalDialogs.StandardButton.Ok + OriginalDialogs.StandardButton.Cancel, + defaultButton: OriginalDialogs.StandardButton.Ok + }); + messageBox.selected.connect(function(button) { + console.log("You clicked " + button) + }) + } + } + */ + // bookmarks + /* + Button { + text: "Bookmark Location" + onClicked: { + desktop.inputDialog({ + title: "Bookmark Location", + icon: hifi.icons.placemark, + label: "Name" + }); + } + } + Button { + text: "Delete Bookmark" + onClicked: { + desktop.inputDialog({ + title: "Delete Bookmark", + icon: hifi.icons.placemark, + label: "Select the bookmark to delete", + items: ["Bookmark A", "Bookmark B", "Bookmark C"] + }); + } + } + Button { + text: "Duplicate Bookmark" + onClicked: { + desktop.messageBox({ + title: "Duplicate Bookmark", + icon: hifi.icons.warning, + text: "The bookmark name you entered alread exists in yoru list.", + informativeText: "Would you like to overwrite it?", + buttons: OriginalDialogs.StandardButton.Yes + OriginalDialogs.StandardButton.No, + defaultButton: OriginalDialogs.StandardButton.Yes + }); + } + } + */ + + } + HifiConstants { id: hifi } @@ -35,249 +296,12 @@ ApplicationWindow { } */ - Row { - id: testButtons - anchors { margins: 8; left: parent.left; top: parent.top } - spacing: 8 - property int count: 0 - - property var tabs: []; - property var urls: []; - - Button { - // Shows the dialog with preferences sections but not each section's preference items - // because Preferences.preferencesByCategory() method is not stubbed out. - text: "Settings > General..." - property var builder: Component { - GeneralPreferencesDialog { } - } - onClicked: { - var runningScripts = builder.createObject(desktop); - } - } - - Button { - text: "Running Scripts" - property var builder: Component { - RunningScripts { } - } - onClicked: { - var runningScripts = builder.createObject(desktop); - } - } - - Button { - text: "Attachments" - property var builder: Component { - AttachmentsDialog { } - } - onClicked: { - var attachmentsDialog = builder.createObject(desktop); - } - } - - /* - Button { - text: "restore all" - onClicked: { - for (var i = 0; i < desktop.windows.length; ++i) { - desktop.windows[i].visible = true - } - } - } - Button { - text: "toggle blue visible" - onClicked: { - blue.visible = !blue.visible - } - } - Button { - text: "toggle blue enabled" - onClicked: { - blue.enabled = !blue.enabled - } - } - */ - Button { - // Replicates message box that pops up after selecting new avatar. Includes title. - text: "Confirm Avatar" - onClicked: { - var messageBox = desktop.messageBox({ - title: "Set Avatar", - text: "Would you like to use 'Albert' for your avatar?", - icon: hifi.icons.question, // Test question icon - //icon: hifi.icons.information, // Test informaton icon - //icon: hifi.icons.warning, // Test warning icon - //icon: hifi.icons.critical, // Test critical icon - //icon: hifi.icons.none, // Test no icon - buttons: OriginalDialogs.StandardButton.Ok + OriginalDialogs.StandardButton.Cancel, - defaultButton: OriginalDialogs.StandardButton.Ok - }); - messageBox.selected.connect(function(button) { - console.log("You clicked " + button) - }) - } - } - Button { - // Message without title. - text: "Show Error" - onClicked: { - var messageBox = desktop.messageBox({ - text: "Diagnostic cycle will be complete in 30 seconds", - icon: hifi.icons.critical, - }); - messageBox.selected.connect(function(button) { - console.log("You clicked " + button) - }) - } - } - Button { - // detailedText is not currently used anywhere in Interface but it is easier to leave in and style good enough. - text: "Show Long Error" - onClicked: { - desktop.messageBox({ - informativeText: "Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds ", - text: "Baloney", - icon: hifi.icons.warning, - detailedText: "sakjd;laskj dksa;dl jka;lsd j;lkjas ;dlkaj s;dlakjd ;alkjda; slkjda; lkjda;lksjd ;alksjd; alksjd ;alksjd; alksjd; alksdjas;ldkjas;lkdja ;kj ;lkasjd; lkj as;dlka jsd;lka jsd;laksjd a" - }); - } - } - Button { - text: "Bookmark Location" - onClicked: { - desktop.inputDialog({ - title: "Bookmark Location", - icon: hifi.icons.placemark, - label: "Name" - }); - } - } - Button { - text: "Delete Bookmark" - onClicked: { - desktop.inputDialog({ - title: "Delete Bookmark", - icon: hifi.icons.placemark, - label: "Select the bookmark to delete", - items: ["Bookmark A", "Bookmark B", "Bookmark C"] - }); - } - } - Button { - text: "Duplicate Bookmark" - onClicked: { - desktop.messageBox({ - title: "Duplicate Bookmark", - icon: hifi.icons.warning, - text: "The bookmark name you entered alread exists in yoru list.", - informativeText: "Would you like to overwrite it?", - buttons: OriginalDialogs.StandardButton.Yes + OriginalDialogs.StandardButton.No, - defaultButton: OriginalDialogs.StandardButton.Yes - }); - } - } - /* - // There is no such desktop.queryBox() function; may need to update test to cover QueryDialog.qml? - Button { - text: "Show Query" - onClicked: { - var queryBox = desktop.queryBox({ - text: "Have you stopped beating your wife?", - placeholderText: "Are you sure?", - // icon: hifi.icons.critical, - }); - queryBox.selected.connect(function(result) { - console.log("User responded with " + result); - }); - - queryBox.canceled.connect(function() { - console.log("User cancelled query box "); - }) - } - } - */ - Button { - text: "Open Directory" - property var builder: Component { - FileDialog { selectDirectory: true } - } - - onClicked: { - var fileDialog = builder.createObject(desktop); - fileDialog.canceled.connect(function(){ - console.log("Cancelled") - }) - fileDialog.selectedFile.connect(function(file){ - console.log("Selected " + file) - }) - } - } - - Button { - text: "Open File" - property var builder: Component { - FileDialog { - title: "Open File" - filter: "All Files (*.*)" - //filter: "HTML files (*.html);;Other(*.png)" - } - } - - onClicked: { - var fileDialog = builder.createObject(desktop); - fileDialog.canceled.connect(function(){ - console.log("Cancelled") - }) - fileDialog.selectedFile.connect(function(file){ - console.log("Selected " + file) - }) - } - } - - Button { - text: "Add Tab" - onClicked: { - console.log(desktop.toolWindow); - desktop.toolWindow.addWebTab({ source: "Foo" }); - desktop.toolWindow.showTabForUrl("Foo", true); - } - } - - Button { - text: "Add Tab 2" - onClicked: { - console.log(desktop.toolWindow); - desktop.toolWindow.addWebTab({ source: "Foo 2" }); - desktop.toolWindow.showTabForUrl("Foo 2", true); - } - } - - Button { - text: "Add Tab 3" - onClicked: { - console.log(desktop.toolWindow); - desktop.toolWindow.addWebTab({ source: "Foo 3" }); - desktop.toolWindow.showTabForUrl("Foo 3", true); - } - } - - Button { - text: "Destroy Tab" - onClicked: { - console.log(desktop.toolWindow); - desktop.toolWindow.removeTabForUrl("Foo"); - } - } - - } - /* Window { id: blue closable: true visible: true resizable: true - destroyOnInvisible: false + destroyOnHidden: false width: 100; height: 100 x: 1280 / 2; y: 720 / 2 @@ -296,32 +320,33 @@ ApplicationWindow { Component.onDestruction: console.log("Blue destroyed") } } - */ - /* + + + Rectangle { width: 100; height: 100; x: 100; y: 100; color: "#00f" } + Window { id: green alwaysOnTop: true + frame: HiddenFrame{} + hideBackground: true closable: true visible: true resizable: false x: 1280 / 2; y: 720 / 2 - Settings { - category: "TestWindow.Green" - property alias x: green.x - property alias y: green.y - property alias width: green.width - property alias height: green.height - } width: 100; height: 100 - Rectangle { anchors.fill: parent; color: "green" } + Rectangle { + color: "#0f0" + width: green.width; + height: green.height; + } } - +/* Window { id: yellow - objectName: "Yellow" closable: true visible: true resizable: true + x: 100; y: 100 width: 100; height: 100 Rectangle { anchors.fill: parent @@ -329,7 +354,7 @@ ApplicationWindow { color: "yellow" } } - */ +*/ } Action { diff --git a/tests/ui/qmlscratch.pro b/tests/ui/qmlscratch.pro index 417d7dad5b..95be6a480e 100644 --- a/tests/ui/qmlscratch.pro +++ b/tests/ui/qmlscratch.pro @@ -18,6 +18,7 @@ DISTFILES += \ qml/*.qml \ ../../interface/resources/qml/*.qml \ ../../interface/resources/qml/controls/*.qml \ + ../../interface/resources/qml/controls-uit/*.qml \ ../../interface/resources/qml/dialogs/*.qml \ ../../interface/resources/qml/dialogs/fileDialog/*.qml \ ../../interface/resources/qml/dialogs/preferences/*.qml \ @@ -25,9 +26,9 @@ DISTFILES += \ ../../interface/resources/qml/desktop/*.qml \ ../../interface/resources/qml/menus/*.qml \ ../../interface/resources/qml/styles/*.qml \ + ../../interface/resources/qml/styles-uit/*.qml \ ../../interface/resources/qml/windows/*.qml \ ../../interface/resources/qml/hifi/*.qml \ ../../interface/resources/qml/hifi/dialogs/*.qml \ ../../interface/resources/qml/hifi/dialogs/preferences/*.qml \ ../../interface/resources/qml/hifi/overlays/*.qml - From ed5d904b3d87b69c39fa2812dcd5e47623a6b28c Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 2 Jun 2016 09:50:58 -0700 Subject: [PATCH 008/171] Hide overlays when driving --- interface/resources/qml/desktop/Desktop.qml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index 73f8a17bb0..c3b1b0ad6e 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -264,6 +264,10 @@ FocusScope { pinned = !pinned } + function setPinned(newPinned) { + pinned = newPinned + } + onPinnedChanged: { if (pinned) { From e9b359feb1afcb7ecbc27edd15fa1c8859355422 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 2 Jun 2016 19:15:19 -0700 Subject: [PATCH 009/171] Make QML overlays disappear, use fade for non-window types --- interface/resources/qml/desktop/Desktop.qml | 39 ++++++++++++++----- .../resources/qml/windows/ModalWindow.qml | 2 +- scripts/system/libraries/toolBars.js | 2 +- 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index c3b1b0ad6e..241cd91611 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -268,24 +268,45 @@ FocusScope { pinned = newPinned } - onPinnedChanged: { + property real unpinnedAlpha: 1.0; + Behavior on unpinnedAlpha { + NumberAnimation { + easing.type: Easing.Linear; + duration: 300 + } + } + + state: "NORMAL" + states: [ + State { + name: "NORMAL" + PropertyChanges { target: desktop; unpinnedAlpha: 1.0 } + }, + State { + name: "PINNED" + PropertyChanges { target: desktop; unpinnedAlpha: 0.0 } + } + ] + + transitions: [ + Transition { + NumberAnimation { properties: "unpinnedAlpha"; duration: 300 } + } + ] + + onPinnedChanged: { if (pinned) { + // recalculate our non-pinned children hiddenChildren = d.findMatchingChildren(desktop, function(child){ return !d.isTopLevelWindow(child) && child.visible; }); hiddenChildren.forEach(function(child){ - child.visible = false; + child.opacity = Qt.binding(function(){ return desktop.unpinnedAlpha }); }); - } else { - hiddenChildren.forEach(function(child){ - if (child) { - child.visible = true; - } - }); - hiddenChildren = []; } + state = pinned ? "PINNED" : "NORMAL" } onShowDesktop: pinned = false diff --git a/interface/resources/qml/windows/ModalWindow.qml b/interface/resources/qml/windows/ModalWindow.qml index 144165e4e1..f79dc9084f 100644 --- a/interface/resources/qml/windows/ModalWindow.qml +++ b/interface/resources/qml/windows/ModalWindow.qml @@ -16,7 +16,7 @@ Window { id: window modality: Qt.ApplicationModal destroyOnCloseButton: true - destroyOnInvisible: true + destroyOnHidden: true frame: ModalFrame { } property int colorScheme: hifi.colorSchemes.light diff --git a/scripts/system/libraries/toolBars.js b/scripts/system/libraries/toolBars.js index 9efe533457..f021f456bf 100644 --- a/scripts/system/libraries/toolBars.js +++ b/scripts/system/libraries/toolBars.js @@ -256,7 +256,7 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit y: y - ToolBar.SPACING }); } - this.save(); + //this.save(); } this.setAlpha = function(alpha, tool) { From 4e49f3f94297a54d8021cca6cf77325be1cef855 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 2 Jun 2016 19:54:25 -0700 Subject: [PATCH 010/171] Fix browser and update dialogs --- interface/resources/qml/Browser.qml | 67 ++++++++++++++---------- interface/resources/qml/UpdateDialog.qml | 4 +- tests/ui/qml/main.qml | 11 ++++ 3 files changed, 52 insertions(+), 30 deletions(-) diff --git a/interface/resources/qml/Browser.qml b/interface/resources/qml/Browser.qml index efa2063fe8..01e1bb7680 100644 --- a/interface/resources/qml/Browser.qml +++ b/interface/resources/qml/Browser.qml @@ -2,8 +2,8 @@ import QtQuick 2.3 import QtQuick.Controls 1.2 import QtWebEngine 1.1 -import "controls" -import "styles" +import "controls-uit" +import "styles-uit" import "windows" Window { @@ -15,7 +15,9 @@ Window { width: 800 height: 600 property alias webView: webview - + x: 100 + y: 100 + Component.onCompleted: { shown = true addressBar.text = webview.url @@ -30,15 +32,9 @@ Window { Item { id:item - anchors.fill: parent - Rectangle { - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - anchors.bottom: webview.top - color: "white" - } - + width: pane.contentWidth + implicitHeight: pane.scrollHeight + Row { id: buttons spacing: 4 @@ -46,25 +42,37 @@ Window { anchors.topMargin: 8 anchors.left: parent.left anchors.leftMargin: 8 - FontAwesome { - id: back; text: "\uf0a8"; size: 48; enabled: webview.canGoBack; + HiFiGlyphs { + id: back; + enabled: webview.canGoBack; + text: hifi.glyphs.backward color: enabled ? hifi.colors.text : hifi.colors.disabledText + size: 48 MouseArea { anchors.fill: parent; onClicked: webview.goBack() } } - FontAwesome { - id: forward; text: "\uf0a9"; size: 48; enabled: webview.canGoForward; + + HiFiGlyphs { + id: forward; + enabled: webview.canGoForward; + text: hifi.glyphs.forward color: enabled ? hifi.colors.text : hifi.colors.disabledText - MouseArea { anchors.fill: parent; onClicked: webview.goBack() } + size: 48 + MouseArea { anchors.fill: parent; onClicked: webview.goForward() } } - FontAwesome { - id: reload; size: 48; text: webview.loading ? "\uf057" : "\uf021" - MouseArea { anchors.fill: parent; onClicked: webview.loading ? webview.stop() : webview.reload() } + + HiFiGlyphs { + id: reload; + enabled: webview.canGoForward; + text: webview.loading ? hifi.glyphs.close : hifi.glyphs.reload + color: enabled ? hifi.colors.text : hifi.colors.disabledText + size: 48 + MouseArea { anchors.fill: parent; onClicked: webview.goForward() } } } - Border { + Item { + id: border height: 48 - radius: 8 anchors.top: parent.top anchors.topMargin: 8 anchors.right: parent.right @@ -86,15 +94,18 @@ Window { onSourceChanged: console.log("Icon url: " + source) } } - - TextInput { + + TextField { id: addressBar anchors.right: parent.right anchors.rightMargin: 8 anchors.left: barIcon.right anchors.leftMargin: 0 anchors.verticalCenter: parent.verticalCenter - + focus: true + colorScheme: hifi.colorSchemes.dark + placeholderText: "Enter URL" + Component.onCompleted: scriptsModel.filterRegExp = new RegExp("^.*$", "i") Keys.onPressed: { switch(event.key) { case Qt.Key_Enter: @@ -110,7 +121,7 @@ Window { } } - WebView { + WebEngineView { id: webview url: "http://highfidelity.com" anchors.top: buttons.bottom @@ -119,7 +130,7 @@ Window { anchors.left: parent.left anchors.right: parent.right onLoadingChanged: { - if (loadRequest.status == WebEngineView.LoadSucceededStatus) { + if (loadRequest.status === WebEngineView.LoadSucceededStatus) { addressBar.text = loadRequest.url } } @@ -127,7 +138,7 @@ Window { console.log("New icon: " + icon) } - profile: desktop.browserProfile + //profile: desktop.browserProfile } diff --git a/interface/resources/qml/UpdateDialog.qml b/interface/resources/qml/UpdateDialog.qml index 4cb5b206c6..52fdab25f9 100644 --- a/interface/resources/qml/UpdateDialog.qml +++ b/interface/resources/qml/UpdateDialog.qml @@ -3,8 +3,8 @@ import QtQuick 2.3 import QtQuick.Controls 1.3 import QtQuick.Controls.Styles 1.3 import QtGraphicalEffects 1.0 -import "controls" -import "styles" +import "controls-uit" +import "styles-uit" import "windows" Window { diff --git a/tests/ui/qml/main.qml b/tests/ui/qml/main.qml index 3b2cc264c8..d752734de4 100644 --- a/tests/ui/qml/main.qml +++ b/tests/ui/qml/main.qml @@ -105,6 +105,16 @@ ApplicationWindow { } */ + // Browser + + Button { + text: "Open Browser" + onClicked: builder.createObject(desktop); + property var builder: Component { + Browser {} + } + } + // file dialog Button { @@ -358,6 +368,7 @@ ApplicationWindow { } Action { + id: openBrowserAction text: "Open Browser" shortcut: "Ctrl+Shift+X" onTriggered: { From 3e919cb7ac4d78b3661b020e9f577a0978c680eb Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 3 Jun 2016 10:42:02 -0700 Subject: [PATCH 011/171] cleanup --- .../controllers/handControllerPointer.js | 59 +++++++++---------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index 5cbde82654..e70f704ce7 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -27,6 +27,7 @@ // UTILITIES ------------- // +function ignore() { } // Utility to make it easier to setup and disconnect cleanly. function setupHandler(event, handler) { @@ -106,7 +107,7 @@ function Trigger() { }; that.full = function () { return (that.state === 'full') ? 1.0 : 0.0; - } + }; } // VERTICAL FIELD OF VIEW --------- @@ -229,10 +230,9 @@ function updateSeeking() { } averageMouseVelocity = lastIntegration = 0; var lookAt2D = HMD.getHUDLookAtPosition2D(); - if (!lookAt2D) { - // FIXME - determine if this message is useful but make it so it doesn't spam the - // log in the case that it is happening - //print('Cannot seek without lookAt position'); + if (!lookAt2D) { // If this happens, something has gone terribly wrong. + print('Cannot seek without lookAt position'); + isSeeking = false; return; } // E.g., if parallel to location in HUD var copy = Reticle.position; @@ -305,7 +305,7 @@ var leftTrigger = new Trigger(); var rightTrigger = new Trigger(); var activeTrigger = rightTrigger; var activeHand = Controller.Standard.RightHand; -function toggleHand() { +function toggleHand() { // unequivocally switch which hand controls mouse position if (activeHand === Controller.Standard.RightHand) { activeHand = Controller.Standard.LeftHand; activeTrigger = leftTrigger; @@ -314,6 +314,13 @@ function toggleHand() { activeTrigger = rightTrigger; } } +function makeToggleAction(hand) { // return a function(0|1) that makes the specified hand control mouse when 1 + return function (on) { + if (on && (activeHand !== hand)) { + toggleHand(); + } + }; +} var clickMapping = Controller.newMapping(Script.resolvePath('') + '-click'); Script.scriptEnding.connect(clickMapping.disable); @@ -321,25 +328,14 @@ Script.scriptEnding.connect(clickMapping.disable); // Gather the trigger data for smoothing. clickMapping.from(Controller.Standard.RT).peek().to(rightTrigger.triggerPress); clickMapping.from(Controller.Standard.LT).peek().to(leftTrigger.triggerPress); -// The next two lines will be removed soon. Right now I want both trigger and button so we can compare. -clickMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(Controller.Actions.ReticleClick); -clickMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(Controller.Actions.ReticleClick); -// Full somoothed trigger is a click. +// Full smoothed trigger is a click. clickMapping.from(rightTrigger.full).to(Controller.Actions.ReticleClick); clickMapping.from(leftTrigger.full).to(Controller.Actions.ReticleClick); clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(Controller.Actions.ContextMenu); clickMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(Controller.Actions.ContextMenu); // Partial smoothed trigger is activation. -clickMapping.from(rightTrigger.partial).to(function (on) { - if (on && (activeHand !== Controller.Standard.RightHand)) { - toggleHand(); - } -}); -clickMapping.from(leftTrigger.partial).to(function (on) { - if (on && (activeHand !== Controller.Standard.LeftHand)) { - toggleHand(); - } -}); +clickMapping.from(rightTrigger.partial).to(makeToggleAction(Controller.Standard.RightHand)); +clickMapping.from(leftTrigger.partial).to(makeToggleAction(Controller.Standard.LeftHand)); clickMapping.enable(); // VISUAL AID ----------- @@ -374,6 +370,7 @@ function turnOffVisualization(optionalEnableClicks) { // because we're showing c } var MAX_RAY_SCALE = 32000; // Anything large. It's a scale, not a distance. function updateVisualization(controllerPosition, controllerDirection, hudPosition3d, hudPosition2d) { + ignore(controllerPosition, controllerDirection, hudPosition2d); // Show an indication of where the cursor will appear when crossing a HUD element, // and where in-world clicking will occur. // @@ -408,21 +405,21 @@ function updateVisualization(controllerPosition, controllerDirection, hudPositio function update() { var now = Date.now(); if (!handControllerLockOut.expired(now)) { - return turnOffVisualization(); - } // Let them use mouse it in peace. + return turnOffVisualization(); // Let them use mouse it in peace. + } if (!Menu.isOptionChecked("First Person")) { - return turnOffVisualization(); - } // What to do? menus can be behind hand! - if (!Window.hasFocus()) { // Don't mess with other apps - return turnOffVisualization(); + return turnOffVisualization(); // What to do? menus can be behind hand! + } + if (!Window.hasFocus()) { + return turnOffVisualization(); // Don't mess with other apps } leftTrigger.update(); rightTrigger.update(); var controllerPose = Controller.getPoseValue(activeHand); // Valid if any plugged-in hand controller is "on". (uncradled Hydra, green-lighted Vive...) if (!controllerPose.valid) { - return turnOffVisualization(); - } // Controller is cradled. + return turnOffVisualization(); // Controller is cradled. + } var controllerPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, controllerPose.translation), MyAvatar.position); // This gets point direction right, but if you want general quaternion it would be more complicated: @@ -430,9 +427,9 @@ function update() { var hudPoint3d = calculateRayUICollisionPoint(controllerPosition, controllerDirection); if (!hudPoint3d) { - // FIXME - determine if this message is useful but make it so it doesn't spam the - // log in the case that it is happening - //print('Controller is parallel to HUD'); + if (Menu.isOptionChecked("Overlays")) { // With our hud resetting strategy, hudPoint3d should be valid here + print('Controller is parallel to HUD'); // so let us know that our assumptions are wrong. + } return turnOffVisualization(); } var hudPoint2d = overlayFromWorldPoint(hudPoint3d); From 247286c71f41ecc97dccdf57f7111175ad48c36e Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 3 Jun 2016 11:55:33 -0700 Subject: [PATCH 012/171] Basic address bar toggle button. --- scripts/defaultScripts.js | 1 + scripts/system/goto.js | 47 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 scripts/system/goto.js diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 2a050d183e..4f6b5ca0e6 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -13,6 +13,7 @@ Script.load("system/progress.js"); Script.load("system/away.js"); Script.load("system/users.js"); Script.load("system/examples.js"); +Script.load("system/goto.js"); Script.load("system/edit.js"); Script.load("system/selectAudioDevice.js"); Script.load("system/notifications.js"); diff --git a/scripts/system/goto.js b/scripts/system/goto.js new file mode 100644 index 0000000000..75d9829905 --- /dev/null +++ b/scripts/system/goto.js @@ -0,0 +1,47 @@ +// +// goto.js +// scripts/system/ +// +// Created by Howard Stearns on 2 Jun 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +Script.include("libraries/toolBars.js"); + +function initialPosition(windowDimensions, toolbar) { + return { + x: windowDimensions.x / 2 - Tool.IMAGE_WIDTH, + y: windowDimensions.y + }; +} +var toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.goto.toolbar", initialPosition, { + x: -Tool.IMAGE_WIDTH / 2, + y: -Tool.IMAGE_HEIGHT +}); +var button = toolBar.addTool({ + imageURL: Script.resolvePath("assets/images/tools/directory-01.svg"), + subImage: { + x: 0, + y: Tool.IMAGE_WIDTH, + width: Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT + }, + width: Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT, + alpha: 0.9, + visible: true, + showButtonDown: true +}); +function onMousePress (event) { + if (event.isLeftButton && button === toolBar.clicked(Overlays.getOverlayAtPoint(event))) { + DialogsManager.toggleAddressBar(); + } +}; +Controller.mousePressEvent.connect(onMousePress) +Script.scriptEnding.connect(function () { + Controller.mousePressEvent.disconnect(onMousePress) + toolBar.cleanup(); +}); From 68819561175d8495be22975dafd39b784ced661e Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 3 Jun 2016 14:57:46 -0700 Subject: [PATCH 013/171] Basic HMD toggle button. --- scripts/defaultScripts.js | 1 + .../assets/images/tools/hmd-switch-01.svg | 102 ++++++++++++++++++ scripts/system/hmd.js | 57 ++++++++++ 3 files changed, 160 insertions(+) create mode 100644 scripts/system/assets/images/tools/hmd-switch-01.svg create mode 100644 scripts/system/hmd.js diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 2a050d183e..b10d1c6f28 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -13,6 +13,7 @@ Script.load("system/progress.js"); Script.load("system/away.js"); Script.load("system/users.js"); Script.load("system/examples.js"); +Script.load("system/hmd.js"); Script.load("system/edit.js"); Script.load("system/selectAudioDevice.js"); Script.load("system/notifications.js"); diff --git a/scripts/system/assets/images/tools/hmd-switch-01.svg b/scripts/system/assets/images/tools/hmd-switch-01.svg new file mode 100644 index 0000000000..15ecb02b6b --- /dev/null +++ b/scripts/system/assets/images/tools/hmd-switch-01.svg @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/system/hmd.js b/scripts/system/hmd.js new file mode 100644 index 0000000000..277af68315 --- /dev/null +++ b/scripts/system/hmd.js @@ -0,0 +1,57 @@ +// +// hmd.js +// scripts/system/ +// +// Created by Howard Stearns on 2 Jun 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +Script.include("libraries/toolBars.js"); + +var headset; // The preferred headset. Default to the first one found in the following list. +var displayMenuName = "Display"; +var desktopMenuItemName = "Desktop"; +['OpenVR (Vive)', 'Oculus Rift'].forEach(function (name) { + if (!headset && Menu.menuItemExists(displayMenuName, name)) { + headset = name; + } +}); + +function initialPosition(windowDimensions, toolbar) { + return { + x: windowDimensions.x / 2 + (2 * Tool.IMAGE_WIDTH), + y: windowDimensions.y + }; +} +var toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.hmd.toolbar", initialPosition, { + x: -Tool.IMAGE_WIDTH / 2, + y: -Tool.IMAGE_HEIGHT +}); +var button = toolBar.addTool({ + imageURL: Script.resolvePath("assets/images/tools/hmd-switch-01.svg"), + subImage: { + x: 0, + y: Tool.IMAGE_WIDTH, + width: Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT + }, + width: Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT, + alpha: 0.9, + visible: true, + showButtonDown: true +}); +function onMousePress (event) { + if (event.isLeftButton && button === toolBar.clicked(Overlays.getOverlayAtPoint(event))) { + var isDesktop = Menu.isOptionChecked(desktopMenuItemName); + Menu.setIsOptionChecked(isDesktop ? headset : desktopMenuItemName, true); + } +}; +Controller.mousePressEvent.connect(onMousePress) +Script.scriptEnding.connect(function () { + Controller.mousePressEvent.disconnect(onMousePress) + toolBar.cleanup(); +}); From c9f440a49ddc59b357b807af6e81addd1255196a Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 1 Jun 2016 09:53:06 -0700 Subject: [PATCH 014/171] handControllerGrab: initial rendering of equip hot-spots * When trigger is depressed, any entities that are marked with a wearable equip-point will become highlighted by a wireframe box (temporary art). * The grab state machine is now defined by the CONTROLLER_STATE_MACHINE object. this includes each state's name and updateMethod name. * Support was added for entry and exit methods when changing states, this functionality is used to draw and delete the hot spots when entering and exiting the searching state. --- .../system/controllers/handControllerGrab.js | 280 +++++++++++------- 1 file changed, 181 insertions(+), 99 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index be4bffe58c..0561e73e1f 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -92,6 +92,7 @@ var EQUIP_SPRING_TIMEFRAME = 0.4; // how quickly objects move to their new posit // other constants // +var HOTSPOT_DRAW_DISTANCE = 10; var RIGHT_HAND = 1; var LEFT_HAND = 0; @@ -179,47 +180,85 @@ var COLLIDES_WITH_WHILE_MULTI_GRABBED = "dynamic"; var HEART_BEAT_INTERVAL = 5 * MSECS_PER_SEC; var HEART_BEAT_TIMEOUT = 15 * MSECS_PER_SEC; -function stateToName(state) { - switch (state) { - case STATE_OFF: - return "off"; - case STATE_SEARCHING: - return "searching"; - case STATE_HOLD_SEARCHING: - return "hold_searching"; - case STATE_DISTANCE_HOLDING: - return "distance_holding"; - case STATE_CONTINUE_DISTANCE_HOLDING: - return "continue_distance_holding"; - case STATE_NEAR_GRABBING: - return "near_grabbing"; - case STATE_CONTINUE_NEAR_GRABBING: - return "continue_near_grabbing"; - case STATE_NEAR_TRIGGER: - return "near_trigger"; - case STATE_CONTINUE_NEAR_TRIGGER: - return "continue_near_trigger"; - case STATE_FAR_TRIGGER: - return "far_trigger"; - case STATE_CONTINUE_FAR_TRIGGER: - return "continue_far_trigger"; - case STATE_RELEASE: - return "release"; - case STATE_EQUIP: - return "equip"; - case STATE_HOLD: - return "hold"; - case STATE_CONTINUE_HOLD: - return "continue_hold"; - case STATE_CONTINUE_EQUIP: - return "continue_equip"; - case STATE_WAITING_FOR_EQUIP_THUMB_RELEASE: - return "waiting_for_equip_thumb_release"; - case STATE_WAITING_FOR_RELEASE_THUMB_RELEASE: - return "waiting_for_release_thumb_release"; - } +var CONTROLLER_STATE_MACHINE = {}; - return "unknown"; +CONTROLLER_STATE_MACHINE[STATE_OFF] = { + name: "off", + updateMethod: "off" +}; +CONTROLLER_STATE_MACHINE[STATE_SEARCHING] = { + name: "searching", + updateMethod: "search", + enterMethod: "searchEnter", + exitMethod: "searchExit" +}; +CONTROLLER_STATE_MACHINE[STATE_HOLD_SEARCHING] = { + name: "hold_searching", + updateMethod: "search" +}; +CONTROLLER_STATE_MACHINE[STATE_DISTANCE_HOLDING] = { + name: "distance_holding", + updateMethod: "distanceHolding" +}; +CONTROLLER_STATE_MACHINE[STATE_CONTINUE_DISTANCE_HOLDING] = { + name: "continue_distance_holding", + updateMethod: "continueDistanceHolding" +}; +CONTROLLER_STATE_MACHINE[STATE_NEAR_GRABBING] = { + name: "near_grabbing", + updateMethod: "nearGrabbing" +}; +CONTROLLER_STATE_MACHINE[STATE_EQUIP] = { + name: "equip", + updateMethod: "nearGrabbing" +}; +CONTROLLER_STATE_MACHINE[STATE_HOLD] = { + name: "hold", + updateMethod: "nearGrabbing" +}; +CONTROLLER_STATE_MACHINE[STATE_CONTINUE_NEAR_GRABBING] = { + name: "continue_near_grabbing", + updateMethod: "continueNearGrabbing" +}; +CONTROLLER_STATE_MACHINE[STATE_CONTINUE_HOLD] = { + name: "continue_hold", + updateMethod: "continueNearGrabbing" +}; +CONTROLLER_STATE_MACHINE[STATE_CONTINUE_EQUIP] = { + name: "continue_equip", + updateMethod: "continueNearGrabbing" +}; +CONTROLLER_STATE_MACHINE[STATE_NEAR_TRIGGER] = { + name: "near_trigger", + updateMethod: "nearTrigger" +}; +CONTROLLER_STATE_MACHINE[STATE_CONTINUE_NEAR_TRIGGER] = { + name: "continue_near_trigger", + updateMethod: "continueNearTrigger" +}; +CONTROLLER_STATE_MACHINE[STATE_FAR_TRIGGER] = { + name: "far_trigger", + updateMethod: "farTrigger" +}; +CONTROLLER_STATE_MACHINE[STATE_CONTINUE_FAR_TRIGGER] = { + name: "continue_far_trigger", + updateMethod: "continueFarTrigger" +}; +CONTROLLER_STATE_MACHINE[STATE_RELEASE] = { + name: "release", + updateMethod: "release" +}; +CONTROLLER_STATE_MACHINE[STATE_WAITING_FOR_EQUIP_THUMB_RELEASE] = { + name: "waiting_for_equip_thumb_release", + updateMethod: "waitingForEquipThumbRelease" +}; +CONTROLLER_STATE_MACHINE[STATE_WAITING_FOR_RELEASE_THUMB_RELEASE] = { + name: "waiting_for_release_thumb_release", + updateMethod: "waitingForReleaseThumbRelease" +}; + +function stateToName(state) { + return CONTROLLER_STATE_MACHINE[state] ? CONTROLLER_STATE_MACHINE[state].name : "???"; } function getTag() { @@ -249,6 +288,14 @@ function entityIsGrabbedByOther(entityID) { return false; } +function propsArePhysical(props) { + if (!props.dynamic) { + return false; + } + var isPhysical = (props.shapeType && props.shapeType != 'none'); + return isPhysical; +} + // If another script is managing the reticle (as is done by HandControllerPointer), we should not be setting it here, // and we should not be showing lasers when someone else is using the Reticle to indicate a 2D minor mode. var EXTERNALLY_MANAGED_2D_MINOR_MODE = true; @@ -314,55 +361,22 @@ function MyController(hand) { this.update = function() { this.updateSmoothedTrigger(); + if (isIn2DMode()) { _this.turnOffVisualizations(); return; } - switch (this.state) { - case STATE_OFF: - this.off(); - break; - case STATE_SEARCHING: - case STATE_HOLD_SEARCHING: - this.search(); - break; - case STATE_DISTANCE_HOLDING: - this.distanceHolding(); - break; - case STATE_CONTINUE_DISTANCE_HOLDING: - this.continueDistanceHolding(); - break; - case STATE_NEAR_GRABBING: - case STATE_EQUIP: - case STATE_HOLD: - this.nearGrabbing(); - break; - case STATE_WAITING_FOR_EQUIP_THUMB_RELEASE: - this.waitingForEquipThumbRelease(); - break; - case STATE_WAITING_FOR_RELEASE_THUMB_RELEASE: - this.waitingForReleaseThumbRelease(); - break; - case STATE_CONTINUE_NEAR_GRABBING: - case STATE_CONTINUE_HOLD: - case STATE_CONTINUE_EQUIP: - this.continueNearGrabbing(); - break; - case STATE_NEAR_TRIGGER: - this.nearTrigger(); - break; - case STATE_CONTINUE_NEAR_TRIGGER: - this.continueNearTrigger(); - break; - case STATE_FAR_TRIGGER: - this.farTrigger(); - break; - case STATE_CONTINUE_FAR_TRIGGER: - this.continueFarTrigger(); - break; - case STATE_RELEASE: - this.release(); - break; + + if (CONTROLLER_STATE_MACHINE[this.state]) { + var updateMethodName = CONTROLLER_STATE_MACHINE[this.state].updateMethod; + var updateMethod = this[updateMethodName]; + if (updateMethod) { + updateMethod.call(this); + } else { + print("WARNING: could not find updateMethod for state " + stateToName(this.state)); + } + } else { + print("WARNING: could not find state " + this.state + " " + CONTROLLER_STATE_MACHINE[this.state]); } }; @@ -374,9 +388,33 @@ function MyController(hand) { this.setState = function(newState) { this.grabSphereOff(); if (WANT_DEBUG || WANT_DEBUG_STATE) { - print("STATE (" + this.hand + "): " + stateToName(this.state) + " --> " + - stateToName(newState) + ", hand: " + this.hand); + var oldStateName = stateToName(this.state); + var newStateName = stateToName(newState); + print("STATE (" + this.hand + "): " + oldStateName + " --> " + newStateName); } + + // exit the old state + if (CONTROLLER_STATE_MACHINE[this.state]) { + var exitMethodName = CONTROLLER_STATE_MACHINE[this.state].exitMethod; + var exitMethod = this[exitMethodName]; + if (exitMethod) { + exitMethod.call(this); + } + } else { + print("WARNING: could not find this.state " + this.state); + } + + // enter the new state + if (CONTROLLER_STATE_MACHINE[newState]) { + var enterMethodName = CONTROLLER_STATE_MACHINE[newState].enterMethod; + var enterMethod = this[enterMethodName]; + if (enterMethod) { + enterMethod.call(this); + } + } else { + print("WARNING: could not find newState " + newState); + } + this.state = newState; }; @@ -759,14 +797,6 @@ function MyController(hand) { } }; - this.propsArePhysical = function(props) { - if (!props.dynamic) { - return false; - } - var isPhysical = (props.shapeType && props.shapeType != 'none'); - return isPhysical; - } - this.turnOffVisualizations = function() { if (USE_ENTITY_LINES_FOR_SEARCHING === true || USE_ENTITY_LINES_FOR_MOVING === true) { this.lineOff(); @@ -852,6 +882,58 @@ function MyController(hand) { } }; + this.searchEnter = function() { + this.equipHotspotOverlays = []; + + // find entities near the avatar that might be equipable. + var entities = Entities.findEntities(MyAvatar.position, HOTSPOT_DRAW_DISTANCE); + var i, l = entities.length; + for (i = 0; i < l; i++) { + + // is this entity equipable? + var grabData = getEntityCustomData(GRABBABLE_DATA_KEY, entities[i], undefined); + var grabProps = Entities.getEntityProperties(entities[i], GRABBABLE_PROPERTIES); + if (grabData) { + + var hotspotPos = grabProps.position; + + // does this entity have an equip point? + var wearableData = getEntityCustomData("wearable", entities[i], undefined); + if (wearableData) { + var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"; + if (wearableData[handJointName]) { + // draw the hotspot around the equip point. + hotspotPos = wearableData[handJointName][0]; + } + } + + // draw a hotspot! + this.equipHotspotOverlays.push(Overlays.addOverlay("cube", { + position: hotspotPos, + size: 0.1, + color: { red: 90, green: 255, blue: 90 }, + alpha: 1, + solid: false, + visible: true, + dashed: false, + lineWidth: 2.0, + ignoreRayIntersection: true, // this never ray intersects + drawInFront: true + })); + } + } + }; + + this.searchExit = function() { + + // delete all equip hotspots + var i, l = this.equipHotspotOverlays.length; + for (i = 0; i < l; i++) { + Overlays.deleteOverlay(this.equipHotspotOverlays[i]); + } + this.equipHotspotOverlays = []; + }; + this.search = function() { this.grabbedEntity = null; this.isInitialGrab = false; @@ -951,7 +1033,7 @@ function MyController(hand) { var propsForCandidate = Entities.getEntityProperties(candidateEntities[i], GRABBABLE_PROPERTIES); var near = (nearPickedCandidateEntities.indexOf(candidateEntities[i]) >= 0); - var isPhysical = this.propsArePhysical(propsForCandidate); + var isPhysical = propsArePhysical(propsForCandidate); var grabbable; if (isPhysical) { // physical things default to grabbable @@ -1030,7 +1112,7 @@ function MyController(hand) { if ((this.grabbedEntity !== null) && (this.triggerSmoothedGrab() || this.bumperSqueezed())) { // We are squeezing enough to grab, and we've found an entity that we'll try to do something with. var near = (nearPickedCandidateEntities.indexOf(this.grabbedEntity) >= 0) || minDistance <= NEAR_PICK_MAX_DISTANCE; - var isPhysical = this.propsArePhysical(props); + var isPhysical = propsArePhysical(props); // near or far trigger if (grabbableData.wantsTrigger) { @@ -1462,7 +1544,7 @@ function MyController(hand) { } } - var isPhysical = this.propsArePhysical(grabbedProperties) || entityHasActions(this.grabbedEntity); + var isPhysical = propsArePhysical(grabbedProperties) || entityHasActions(this.grabbedEntity); if (isPhysical && this.state == STATE_NEAR_GRABBING) { // grab entity via action if (!this.setupHoldAction()) { @@ -1879,7 +1961,7 @@ function MyController(hand) { var forceVelocity = false; var doSetVelocity = false; - if (parentID != NULL_UUID && deactiveProps.parentID == NULL_UUID && this.propsArePhysical(props)) { + if (parentID != NULL_UUID && deactiveProps.parentID == NULL_UUID && propsArePhysical(props)) { // TODO: EntityScriptingInterface::convertLocationToScriptSemantics should be setting up // props.velocity to be a world-frame velocity and localVelocity to be vs parent. Until that // is done, we use a measured velocity here so that things held via a bumper-grab / parenting-grab From 6d462a477dd3620ddc81efb6355af7df6dd1522e Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 1 Jun 2016 16:54:14 -0700 Subject: [PATCH 015/171] Change equip-hotspot to a green sphere. --- .../system/controllers/handControllerGrab.js | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 0561e73e1f..4163b07c6d 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -897,28 +897,26 @@ function MyController(hand) { var hotspotPos = grabProps.position; - // does this entity have an equip point? + // does this entity have an attach point? var wearableData = getEntityCustomData("wearable", entities[i], undefined); if (wearableData) { var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"; if (wearableData[handJointName]) { - // draw the hotspot around the equip point. + // draw the hotspot around the attach point. hotspotPos = wearableData[handJointName][0]; } } // draw a hotspot! - this.equipHotspotOverlays.push(Overlays.addOverlay("cube", { + this.equipHotspotOverlays.push(Overlays.addOverlay("sphere", { position: hotspotPos, - size: 0.1, + size: 0.2, color: { red: 90, green: 255, blue: 90 }, - alpha: 1, - solid: false, + alpha: 0.7, + solid: true, visible: true, - dashed: false, - lineWidth: 2.0, - ignoreRayIntersection: true, // this never ray intersects - drawInFront: true + ignoreRayIntersection: false, + drawInFront: false })); } } From b836a580ffda8b2625e64ec404d3520cfcf13ed5 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 6 Jun 2016 14:39:14 -0700 Subject: [PATCH 016/171] Updated some debug print information --- scripts/system/controllers/handControllerGrab.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 4163b07c6d..8a96e9d80c 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -376,7 +376,7 @@ function MyController(hand) { print("WARNING: could not find updateMethod for state " + stateToName(this.state)); } } else { - print("WARNING: could not find state " + this.state + " " + CONTROLLER_STATE_MACHINE[this.state]); + print("WARNING: could not find state " + this.state + " in state machine"); } }; @@ -390,7 +390,7 @@ function MyController(hand) { if (WANT_DEBUG || WANT_DEBUG_STATE) { var oldStateName = stateToName(this.state); var newStateName = stateToName(newState); - print("STATE (" + this.hand + "): " + oldStateName + " --> " + newStateName); + print("STATE (" + this.hand + "): " + newStateName + " <-- " + oldStateName); } // exit the old state @@ -401,7 +401,7 @@ function MyController(hand) { exitMethod.call(this); } } else { - print("WARNING: could not find this.state " + this.state); + print("WARNING: could not find state " + this.state + " in state machine"); } // enter the new state @@ -412,7 +412,7 @@ function MyController(hand) { enterMethod.call(this); } } else { - print("WARNING: could not find newState " + newState); + print("WARNING: could not find newState " + newState + " in state machine"); } this.state = newState; @@ -943,6 +943,7 @@ function MyController(hand) { this.setState(STATE_RELEASE); return; } + if (this.state == STATE_HOLD_SEARCHING && this.bumperReleased()) { this.setState(STATE_RELEASE); return; From 74c5ddd49f751937b1814763a18ddc5b189667d1 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Tue, 7 Jun 2016 09:50:10 -0700 Subject: [PATCH 017/171] fix paused interactions with mouse --- scripts/system/away.js | 8 +++++--- scripts/system/controllers/handControllerPointer.js | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/scripts/system/away.js b/scripts/system/away.js index 2b2ea8a42b..399b3dc96c 100644 --- a/scripts/system/away.js +++ b/scripts/system/away.js @@ -176,9 +176,11 @@ function goAway() { // tell the Reticle, we want to stop capturing the mouse until we come back Reticle.allowMouseCapture = false; - if (HMD.active) { - Reticle.visible = false; - } + // Allow users to find their way to other applications, our menus, etc. + // For desktop, that means we want the reticle visible. + // For HMD, the hmd preview will show the system mouse because of allowMouseCapture, + // but we want to turn off our Reticle so that we don't get two in preview and a stuck one in headset. + Reticle.visible = !HMD.active; wasHmdMounted = safeGetHMDMounted(); // always remember the correct state avatarPosition = MyAvatar.position; diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index e70f704ce7..7b45babf4d 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -410,8 +410,8 @@ function update() { if (!Menu.isOptionChecked("First Person")) { return turnOffVisualization(); // What to do? menus can be behind hand! } - if (!Window.hasFocus()) { - return turnOffVisualization(); // Don't mess with other apps + if (!Window.hasFocus() || !Reticle.allowMouseCapture) { + return turnOffVisualization(); // Don't mess with other apps or paused mouse activity } leftTrigger.update(); rightTrigger.update(); From b9ee0c087e6f79b36850799bf476e80ab93c5938 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 7 Jun 2016 13:45:23 -0700 Subject: [PATCH 018/171] Add a constant hud toggle button to the UI --- interface/resources/icons/hud-01.svg | 105 ++++++++++++++++++++ interface/resources/qml/desktop/Desktop.qml | 2 +- interface/resources/qml/hifi/Desktop.qml | 21 ++++ tests/ui/qml/main.qml | 2 +- 4 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 interface/resources/icons/hud-01.svg diff --git a/interface/resources/icons/hud-01.svg b/interface/resources/icons/hud-01.svg new file mode 100644 index 0000000000..4929389268 --- /dev/null +++ b/interface/resources/icons/hud-01.svg @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index 241cd91611..e269a63cde 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -299,7 +299,7 @@ FocusScope { if (pinned) { // recalculate our non-pinned children hiddenChildren = d.findMatchingChildren(desktop, function(child){ - return !d.isTopLevelWindow(child) && child.visible; + return !d.isTopLevelWindow(child) && child.visible && !child.pinned; }); hiddenChildren.forEach(function(child){ diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index 59278a17b4..f1c1aeb4d1 100644 --- a/interface/resources/qml/hifi/Desktop.qml +++ b/interface/resources/qml/hifi/Desktop.qml @@ -47,6 +47,27 @@ Desktop { } } + Item { + id: hudToggleButton + clip: true + width: 50 + height: 50 + anchors.bottom: parent.bottom + anchors.bottomMargin: 32 + anchors.horizontalCenter: parent.horizontalCenter + property bool pinned: true + Image { + y: desktop.pinned ? -50 : 0 + id: hudToggleImage + source: "../../icons/hud-01.svg" + } + MouseArea { + anchors.fill: parent + onClicked: desktop.togglePinned() + } + } + + } diff --git a/tests/ui/qml/main.qml b/tests/ui/qml/main.qml index d752734de4..33408fb821 100644 --- a/tests/ui/qml/main.qml +++ b/tests/ui/qml/main.qml @@ -51,7 +51,7 @@ ApplicationWindow { Button { text: "toggle desktop" - onClicked: desktop.toggleVisible() + onClicked: desktop.togglePinned() } // Error alerts From ba61491ee605d44fd5c39babb134cf85f07e8331 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 7 Jun 2016 15:33:00 -0700 Subject: [PATCH 019/171] PR feedback --- interface/resources/qml/hifi/Desktop.qml | 22 +++--------- .../resources/qml/hifi/ToggleHudButton.qml | 36 +++++++++++++++++++ 2 files changed, 40 insertions(+), 18 deletions(-) create mode 100644 interface/resources/qml/hifi/ToggleHudButton.qml diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index f1c1aeb4d1..e127a235e6 100644 --- a/interface/resources/qml/hifi/Desktop.qml +++ b/interface/resources/qml/hifi/Desktop.qml @@ -4,11 +4,13 @@ import QtWebEngine 1.1; import "../desktop" import ".." +import "." Desktop { id: desktop MouseArea { + id: hoverWatch anchors.fill: parent hoverEnabled: true propagateComposedEvents: true @@ -47,28 +49,12 @@ Desktop { } } - Item { - id: hudToggleButton - clip: true - width: 50 - height: 50 + + ToggleHudButton { anchors.bottom: parent.bottom anchors.bottomMargin: 32 anchors.horizontalCenter: parent.horizontalCenter - property bool pinned: true - Image { - y: desktop.pinned ? -50 : 0 - id: hudToggleImage - source: "../../icons/hud-01.svg" - } - MouseArea { - anchors.fill: parent - onClicked: desktop.togglePinned() - } } - - } - diff --git a/interface/resources/qml/hifi/ToggleHudButton.qml b/interface/resources/qml/hifi/ToggleHudButton.qml new file mode 100644 index 0000000000..6454f42305 --- /dev/null +++ b/interface/resources/qml/hifi/ToggleHudButton.qml @@ -0,0 +1,36 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +import "../windows" + +Window { + //frame: HiddenFrame {} + hideBackground: true + resizable: false + destroyOnCloseButton: false + destroyOnHidden: false + closable: false + shown: true + pinned: true + width: 50 + height: 50 + clip: true + visible: true + + Item { + width: 50 + height: 50 + Image { + y: desktop.pinned ? -50 : 0 + id: hudToggleImage + source: "../../icons/hud-01.svg" + } + MouseArea { + readonly property string overlayMenuItem: "Overlays" + anchors.fill: parent + onClicked: MenuInterface.setIsOptionChecked(overlayMenuItem, !MenuInterface.isOptionChecked(overlayMenuItem)) + } + } +} + + From 29d9f51b584e205d62bf944af6400cf0beebc1a3 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 7 Jun 2016 16:46:17 -0700 Subject: [PATCH 020/171] Adding missed file --- interface/src/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 8f4d80a86f..c0a7857c95 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1418,7 +1418,7 @@ void Application::initializeUi() { rootContext->setContextProperty("Overlays", &_overlays); rootContext->setContextProperty("Window", DependencyManager::get().data()); - rootContext->setContextProperty("Menu", MenuScriptingInterface::getInstance()); + rootContext->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance()); rootContext->setContextProperty("Stats", Stats::getInstance()); rootContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance()); rootContext->setContextProperty("ScriptDiscoveryService", DependencyManager::get().data()); From eab611acc072d443ab7fccb2dfaed6b4b370046c Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 18 May 2016 21:46:30 -0700 Subject: [PATCH 021/171] Enable Steam VR text input --- libraries/gl/src/gl/OffscreenQmlSurface.cpp | 41 ++++++++++++++++++++ libraries/gl/src/gl/OffscreenQmlSurface.h | 8 +++- plugins/openvr/src/OpenVrDisplayPlugin.cpp | 3 ++ plugins/openvr/src/ViveControllerManager.cpp | 34 +++++++++++++++- 4 files changed, 84 insertions(+), 2 deletions(-) diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.cpp b/libraries/gl/src/gl/OffscreenQmlSurface.cpp index 388ca26482..fa9cb62b7c 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.cpp +++ b/libraries/gl/src/gl/OffscreenQmlSurface.cpp @@ -396,6 +396,8 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) { _renderer->_renderControl->_renderWindow = _proxyWindow; + connect(_renderer->_quickWindow, &QQuickWindow::focusObjectChanged, this, &OffscreenQmlSurface::onFocusObjectChanged); + // Create a QML engine. _qmlEngine = new QQmlEngine; if (!_qmlEngine->incubationController()) { @@ -742,3 +744,42 @@ QVariant OffscreenQmlSurface::returnFromUiThread(std::function funct return function(); } + +void OffscreenQmlSurface::onFocusObjectChanged(QObject* object) { + if (!object) { + setFocusText(false); + return; + } + + QVariant result; +#if 1 + auto invokeResult = QMetaObject::invokeMethod(object, "inputMethodQuery", Q_RETURN_ARG(QVariant, result), + Q_ARG(Qt::InputMethodQuery, Qt::ImEnabled), + Q_ARG(QVariant, QVariant())); +#else + + //static const char* INPUT_METHOD_QUERY_METHOD_NAME = "inputMethodQuery(Qt::InputMethodQuery, QVariant)"; + static const char* INPUT_METHOD_QUERY_METHOD_NAME = "inputMethodQuery"; + auto meta = object->metaObject(); + qDebug() << "new focus " << object; + auto index = meta->indexOfMethod(INPUT_METHOD_QUERY_METHOD_NAME); + if (index < 0 || index >= meta->methodCount()) { + setFocusText(false); + return; + } + + auto method = meta->method(index); + auto invokeResult = method.invoke(object, + Q_RETURN_ARG(QVariant, result), + Q_ARG(Qt::InputMethodQuery, Qt::ImEnabled), + Q_ARG(QVariant, QVariant())); +#endif + setFocusText(invokeResult && result.toBool()); +} + +void OffscreenQmlSurface::setFocusText(bool newFocusText) { + if (newFocusText != _focusText) { + _focusText = newFocusText; + emit focusTextChanged(_focusText); + } +} diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.h b/libraries/gl/src/gl/OffscreenQmlSurface.h index 22a1b99fe6..1ce7276877 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.h +++ b/libraries/gl/src/gl/OffscreenQmlSurface.h @@ -30,7 +30,7 @@ class OffscreenQmlRenderThread; class OffscreenQmlSurface : public QObject { Q_OBJECT - + Q_PROPERTY(bool focusText READ isFocusText NOTIFY focusTextChanged) public: OffscreenQmlSurface(); virtual ~OffscreenQmlSurface(); @@ -55,6 +55,7 @@ public: _mouseTranslator = mouseTranslator; } + bool isFocusText() const { return _focusText; } void pause(); void resume(); bool isPaused() const; @@ -70,6 +71,8 @@ public: signals: void textureUpdated(unsigned int texture); + void focusObjectChanged(QObject* newFocus); + void focusTextChanged(bool focusText); public slots: void requestUpdate(); @@ -78,6 +81,7 @@ public slots: protected: bool filterEnabled(QObject* originalDestination, QEvent* event) const; + void setFocusText(bool newFocusText); private: QObject* finishQmlLoad(std::function f); @@ -85,6 +89,7 @@ private: private slots: void updateQuick(); + void onFocusObjectChanged(QObject* newFocus); private: friend class OffscreenQmlRenderThread; @@ -97,6 +102,7 @@ private: bool _render{ false }; bool _polish{ true }; bool _paused{ true }; + bool _focusText { false }; uint8_t _maxFps{ 60 }; MouseTranslator _mouseTranslator{ [](const QPointF& p) { return p.toPoint(); } }; QWindow* _proxyWindow { nullptr }; diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index fe406cc29a..fbade9fd68 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -36,12 +36,14 @@ vec3 _trackedDeviceLinearVelocities[vr::k_unMaxTrackedDeviceCount]; vec3 _trackedDeviceAngularVelocities[vr::k_unMaxTrackedDeviceCount]; static mat4 _sensorResetMat; static std::array VR_EYES { { vr::Eye_Left, vr::Eye_Right } }; +bool _openVrDisplayActive { false }; bool OpenVrDisplayPlugin::isSupported() const { return openVrSupported(); } bool OpenVrDisplayPlugin::internalActivate() { + _openVrDisplayActive = true; _container->setIsOptionChecked(StandingHMDSensorMode, true); if (!_system) { @@ -94,6 +96,7 @@ bool OpenVrDisplayPlugin::internalActivate() { void OpenVrDisplayPlugin::internalDeactivate() { Parent::internalDeactivate(); + _openVrDisplayActive = false; _container->setIsOptionChecked(StandingHMDSensorMode, false); if (_system) { // Invalidate poses. It's fine if someone else sets these shared values, but we're about to stop updating them, and diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 739d3cde10..8d6c661ae6 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -12,6 +12,7 @@ #include "ViveControllerManager.h" #include +#include #include #include @@ -22,6 +23,7 @@ #include #include #include +#include #include @@ -55,6 +57,9 @@ bool ViveControllerManager::isSupported() const { return openVrSupported(); } +QMetaObject::Connection _focusConnection; +extern bool _openVrDisplayActive; + bool ViveControllerManager::activate() { InputPlugin::activate(); @@ -67,7 +72,20 @@ bool ViveControllerManager::activate() { _system = acquireOpenVrSystem(); } Q_ASSERT(_system); - + auto offscreenUi = DependencyManager::get(); + _focusConnection = connect(offscreenUi.data(), &OffscreenUi::focusTextChanged, [this](bool focusText) { + if (_openVrDisplayActive) { + auto overlay = vr::VROverlay(); + if (overlay) { + if (focusText) { + //virtual EVROverlayError ShowKeyboard( eInputMode, EGamepadTextInputLineMode eLineInputMode, const char *pchDescription, uint32_t unCharMax, const char *pchExistingText, bool bUseMinimalMode, uint64_t uUserValue) = 0; + overlay->ShowKeyboard(vr::EGamepadTextInputMode::k_EGamepadTextInputModeNormal, vr::k_EGamepadTextInputLineModeSingleLine, "Test", 1024, "", false, 0); + } else { + overlay->HideKeyboard(); + } + } + } + }); // OpenVR provides 3d mesh representations of the controllers // Disabled controller rendering code /* @@ -132,6 +150,8 @@ bool ViveControllerManager::activate() { void ViveControllerManager::deactivate() { InputPlugin::deactivate(); + disconnect(_focusConnection); + _container->removeMenuItem(MENU_NAME, RENDER_CONTROLLERS); _container->removeMenu(MENU_PATH); @@ -220,6 +240,18 @@ void ViveControllerManager::pluginUpdate(float deltaTime, const controller::Inpu return; } + vr::VREvent_t vrEvent; + static char textArray[8192]; + while (vr::VRSystem()->PollNextEvent(&vrEvent, sizeof(vrEvent))) { + if (vrEvent.eventType == vr::VREvent_KeyboardDone) { + auto chars = vr::VROverlay()->GetKeyboardText(textArray, 8192); + QInputMethodEvent* event = new QInputMethodEvent(); + event->setCommitString(QString(QByteArray(textArray, chars)), 0, 0); + auto focusObject = DependencyManager::get()->getWindow()->focusObject(); + qApp->postEvent(focusObject, event); + } + } + // because update mutates the internal state we need to lock userInputMapper->withLock([&, this]() { _inputDevice->update(deltaTime, inputCalibrationData); From c2aa9e7f61f71fd3fd53acf3f9ecdec94d34ce06 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 6 Jun 2016 15:14:10 -0700 Subject: [PATCH 022/171] Update SteamVR keyboard behavior --- libraries/gl/src/gl/OffscreenQmlSurface.cpp | 27 +------ plugins/openvr/src/OpenVrHelpers.cpp | 85 ++++++++++++++++++++ plugins/openvr/src/OpenVrHelpers.h | 3 + plugins/openvr/src/ViveControllerManager.cpp | 37 +-------- 4 files changed, 95 insertions(+), 57 deletions(-) diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.cpp b/libraries/gl/src/gl/OffscreenQmlSurface.cpp index fa9cb62b7c..14518ac37a 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.cpp +++ b/libraries/gl/src/gl/OffscreenQmlSurface.cpp @@ -751,30 +751,9 @@ void OffscreenQmlSurface::onFocusObjectChanged(QObject* object) { return; } - QVariant result; -#if 1 - auto invokeResult = QMetaObject::invokeMethod(object, "inputMethodQuery", Q_RETURN_ARG(QVariant, result), - Q_ARG(Qt::InputMethodQuery, Qt::ImEnabled), - Q_ARG(QVariant, QVariant())); -#else - - //static const char* INPUT_METHOD_QUERY_METHOD_NAME = "inputMethodQuery(Qt::InputMethodQuery, QVariant)"; - static const char* INPUT_METHOD_QUERY_METHOD_NAME = "inputMethodQuery"; - auto meta = object->metaObject(); - qDebug() << "new focus " << object; - auto index = meta->indexOfMethod(INPUT_METHOD_QUERY_METHOD_NAME); - if (index < 0 || index >= meta->methodCount()) { - setFocusText(false); - return; - } - - auto method = meta->method(index); - auto invokeResult = method.invoke(object, - Q_RETURN_ARG(QVariant, result), - Q_ARG(Qt::InputMethodQuery, Qt::ImEnabled), - Q_ARG(QVariant, QVariant())); -#endif - setFocusText(invokeResult && result.toBool()); + QInputMethodQueryEvent query(Qt::ImEnabled); + qApp->sendEvent(object, &query); + setFocusText(query.value(Qt::ImEnabled).toBool()); } void OffscreenQmlSurface::setFocusText(bool newFocusText) { diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index 155bc9f079..b13a60d388 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -14,9 +14,13 @@ #include #include #include +#include +#include #include +#include + Q_DECLARE_LOGGING_CATEGORY(displayplugins) Q_LOGGING_CATEGORY(displayplugins, "hifi.plugins.display") @@ -90,6 +94,82 @@ void releaseOpenVrSystem() { } } +static char textArray[8192]; + +static QMetaObject::Connection _focusConnection, _focusTextConnection; +extern bool _openVrDisplayActive; +static vr::IVROverlay* _overlay { nullptr }; +static QObject* _focusObject { nullptr }; +static QString _existingText; +static Qt::InputMethodHints _currentHints; + +void showOpenVrKeyboard(bool show = true) { + if (_overlay) { + if (show) { + auto offscreenUi = DependencyManager::get(); + _focusObject = offscreenUi->getWindow()->focusObject(); + + QInputMethodQueryEvent query(Qt::ImQueryInput | Qt::ImHints); + qApp->sendEvent(_focusObject, &query); + _currentHints = Qt::InputMethodHints(query.value(Qt::ImHints).toUInt()); + vr::EGamepadTextInputMode inputMode = vr::k_EGamepadTextInputModeNormal; + if (_currentHints & Qt::ImhHiddenText) { + inputMode = vr::k_EGamepadTextInputModePassword; + } + vr::EGamepadTextInputLineMode lineMode = vr::k_EGamepadTextInputLineModeSingleLine; + if (_currentHints & Qt::ImhMultiLine) { + lineMode = vr::k_EGamepadTextInputLineModeMultipleLines; + } + _existingText = query.value(Qt::ImSurroundingText).toString(); + _overlay->ShowKeyboard(inputMode, lineMode, "Keyboard", 1024, _existingText.toLocal8Bit().toStdString().c_str(), false, (uint64_t)(void*)_focusObject); + } else { + _focusObject = nullptr; + _overlay->HideKeyboard(); + } + } +} + +void finishOpenVrKeyboardInput() { + auto offscreenUi = DependencyManager::get(); + auto chars = _overlay->GetKeyboardText(textArray, 8192); + auto newText = QString(QByteArray(textArray, chars)); + // TODO modify the new text to match the possible input hints: + // ImhDigitsOnly ImhFormattedNumbersOnly ImhUppercaseOnly ImhLowercaseOnly + // ImhDialableCharactersOnly ImhEmailCharactersOnly ImhUrlCharactersOnly ImhLatinOnly + QInputMethodEvent event(_existingText, QList()); + event.setCommitString(newText, 0, _existingText.size()); + qApp->sendEvent(_focusObject, &event); + // Simulate an enter press on the top level window to trigger the action + if (0 == (_currentHints & Qt::ImhMultiLine)) { + qApp->sendEvent(offscreenUi->getWindow(), &QKeyEvent(QEvent::KeyPress, Qt::Key_Return, Qt::KeyboardModifiers(), QString("\n"))); + qApp->sendEvent(offscreenUi->getWindow(), &QKeyEvent(QEvent::KeyRelease, Qt::Key_Return, Qt::KeyboardModifiers())); + } +} + +void enableOpenVrKeyboard() { + auto offscreenUi = DependencyManager::get(); + _overlay = vr::VROverlay(); + + _focusConnection = QObject::connect(offscreenUi->getWindow(), &QQuickWindow::focusObjectChanged, [](QObject* object) { + if (object != _focusObject && _overlay) { + showOpenVrKeyboard(false); + } + }); + + _focusTextConnection = QObject::connect(offscreenUi.data(), &OffscreenUi::focusTextChanged, [](bool focusText) { + if (_openVrDisplayActive) { + showOpenVrKeyboard(focusText); + } + }); +} + + +void disableOpenVrKeyboard() { + QObject::disconnect(_focusTextConnection); + QObject::disconnect(_focusConnection); +} + + void handleOpenVrEvents() { if (!activeHmd) { return; @@ -107,6 +187,10 @@ void handleOpenVrEvents() { activeHmd->AcknowledgeQuit_Exiting(); break; + case vr::VREvent_KeyboardDone: + finishOpenVrKeyboardInput(); + break; + default: break; } @@ -114,3 +198,4 @@ void handleOpenVrEvents() { } } + diff --git a/plugins/openvr/src/OpenVrHelpers.h b/plugins/openvr/src/OpenVrHelpers.h index 1e5914844c..41e7dcb27d 100644 --- a/plugins/openvr/src/OpenVrHelpers.h +++ b/plugins/openvr/src/OpenVrHelpers.h @@ -18,6 +18,9 @@ vr::IVRSystem* acquireOpenVrSystem(); void releaseOpenVrSystem(); void handleOpenVrEvents(); bool openVrQuitRequested(); +void enableOpenVrKeyboard(); +void disableOpenVrKeyboard(); + template void openvr_for_each_eye(F f) { diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 8d6c661ae6..6d3ce46e82 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -11,9 +11,6 @@ #include "ViveControllerManager.h" -#include -#include - #include #include #include @@ -57,9 +54,6 @@ bool ViveControllerManager::isSupported() const { return openVrSupported(); } -QMetaObject::Connection _focusConnection; -extern bool _openVrDisplayActive; - bool ViveControllerManager::activate() { InputPlugin::activate(); @@ -72,20 +66,9 @@ bool ViveControllerManager::activate() { _system = acquireOpenVrSystem(); } Q_ASSERT(_system); - auto offscreenUi = DependencyManager::get(); - _focusConnection = connect(offscreenUi.data(), &OffscreenUi::focusTextChanged, [this](bool focusText) { - if (_openVrDisplayActive) { - auto overlay = vr::VROverlay(); - if (overlay) { - if (focusText) { - //virtual EVROverlayError ShowKeyboard( eInputMode, EGamepadTextInputLineMode eLineInputMode, const char *pchDescription, uint32_t unCharMax, const char *pchExistingText, bool bUseMinimalMode, uint64_t uUserValue) = 0; - overlay->ShowKeyboard(vr::EGamepadTextInputMode::k_EGamepadTextInputModeNormal, vr::k_EGamepadTextInputLineModeSingleLine, "Test", 1024, "", false, 0); - } else { - overlay->HideKeyboard(); - } - } - } - }); + + enableOpenVrKeyboard(); + // OpenVR provides 3d mesh representations of the controllers // Disabled controller rendering code /* @@ -150,7 +133,7 @@ bool ViveControllerManager::activate() { void ViveControllerManager::deactivate() { InputPlugin::deactivate(); - disconnect(_focusConnection); + disableOpenVrKeyboard(); _container->removeMenuItem(MENU_NAME, RENDER_CONTROLLERS); _container->removeMenu(MENU_PATH); @@ -240,18 +223,6 @@ void ViveControllerManager::pluginUpdate(float deltaTime, const controller::Inpu return; } - vr::VREvent_t vrEvent; - static char textArray[8192]; - while (vr::VRSystem()->PollNextEvent(&vrEvent, sizeof(vrEvent))) { - if (vrEvent.eventType == vr::VREvent_KeyboardDone) { - auto chars = vr::VROverlay()->GetKeyboardText(textArray, 8192); - QInputMethodEvent* event = new QInputMethodEvent(); - event->setCommitString(QString(QByteArray(textArray, chars)), 0, 0); - auto focusObject = DependencyManager::get()->getWindow()->focusObject(); - qApp->postEvent(focusObject, event); - } - } - // because update mutates the internal state we need to lock userInputMapper->withLock([&, this]() { _inputDevice->update(deltaTime, inputCalibrationData); From 79c68b2ecb203cb09453e53179eb22e7689d00df Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 7 Jun 2016 21:49:29 -0700 Subject: [PATCH 023/171] Working on positioning of the keyboard --- plugins/openvr/src/OpenVrHelpers.cpp | 11 ++++++++++- plugins/openvr/src/OpenVrHelpers.h | 10 ++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index b13a60d388..6533371db8 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -102,6 +102,7 @@ static vr::IVROverlay* _overlay { nullptr }; static QObject* _focusObject { nullptr }; static QString _existingText; static Qt::InputMethodHints _currentHints; +extern vr::TrackedDevicePose_t _trackedDevicePose[vr::k_unMaxTrackedDeviceCount]; void showOpenVrKeyboard(bool show = true) { if (_overlay) { @@ -121,7 +122,15 @@ void showOpenVrKeyboard(bool show = true) { lineMode = vr::k_EGamepadTextInputLineModeMultipleLines; } _existingText = query.value(Qt::ImSurroundingText).toString(); - _overlay->ShowKeyboard(inputMode, lineMode, "Keyboard", 1024, _existingText.toLocal8Bit().toStdString().c_str(), false, (uint64_t)(void*)_focusObject); + + auto showKeyboardResult = _overlay->ShowKeyboard(inputMode, lineMode, "Keyboard", 1024, + _existingText.toLocal8Bit().toStdString().c_str(), false, 0); + + mat4 headPose = toGlm(_trackedDevicePose[0].mDeviceToAbsoluteTracking); + mat4 keyboardTransform = glm::translate(headPose, vec3(0, -0.5, -1)); + keyboardTransform = keyboardTransform * glm::rotate(mat4(), 3.14159f / 4.0f, vec3(-1, 0, 0)); + auto keyboardTransformVr = toOpenVr(keyboardTransform); + _overlay->SetKeyboardTransformAbsolute(vr::ETrackingUniverseOrigin::TrackingUniverseStanding, &keyboardTransformVr); } else { _focusObject = nullptr; _overlay->HideKeyboard(); diff --git a/plugins/openvr/src/OpenVrHelpers.h b/plugins/openvr/src/OpenVrHelpers.h index 41e7dcb27d..426178cd65 100644 --- a/plugins/openvr/src/OpenVrHelpers.h +++ b/plugins/openvr/src/OpenVrHelpers.h @@ -44,3 +44,13 @@ inline mat4 toGlm(const vr::HmdMatrix34_t& m) { m.m[0][3], m.m[1][3], m.m[2][3], 1.0f); return result; } + +inline vr::HmdMatrix34_t toOpenVr(const mat4& m) { + vr::HmdMatrix34_t result; + for (uint8_t i = 0; i < 3; ++i) { + for (uint8_t j = 0; j < 4; ++j) { + result.m[i][j] = m[j][i]; + } + } + return result; +} From 40778d7f299777d5c395e0e742532f214ee5c799 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 8 Jun 2016 10:41:19 -0700 Subject: [PATCH 024/171] Allow env disabling of the keyboard. Show keyboard after short delay to avoid flickers --- plugins/openvr/src/OpenVrHelpers.cpp | 64 ++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 17 deletions(-) diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index 6533371db8..1ff1c65ef8 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -99,19 +99,33 @@ static char textArray[8192]; static QMetaObject::Connection _focusConnection, _focusTextConnection; extern bool _openVrDisplayActive; static vr::IVROverlay* _overlay { nullptr }; -static QObject* _focusObject { nullptr }; +static QObject* _keyboardFocusObject { nullptr }; static QString _existingText; static Qt::InputMethodHints _currentHints; extern vr::TrackedDevicePose_t _trackedDevicePose[vr::k_unMaxTrackedDeviceCount]; +static bool _keyboardShown { false }; +static const uint32_t SHOW_KEYBOARD_DELAY_MS = 100; void showOpenVrKeyboard(bool show = true) { - if (_overlay) { - if (show) { - auto offscreenUi = DependencyManager::get(); - _focusObject = offscreenUi->getWindow()->focusObject(); + if (!_overlay) { + return; + } - QInputMethodQueryEvent query(Qt::ImQueryInput | Qt::ImHints); - qApp->sendEvent(_focusObject, &query); + if (show) { + // To avoid flickering the keyboard when a text element is only briefly selected, + // show the keyboard asynchrnously after a very short delay, but only after we check + // that the current focus object is still one that is text enabled + QTimer::singleShot(SHOW_KEYBOARD_DELAY_MS, [] { + auto offscreenUi = DependencyManager::get(); + auto currentFocus = offscreenUi->getWindow()->focusObject(); + QInputMethodQueryEvent query(Qt::ImEnabled | Qt::ImQueryInput | Qt::ImHints); + qApp->sendEvent(currentFocus, &query); + // Current focus isn't text enabled, bail early. + if (!query.value(Qt::ImEnabled).toBool()) { + return; + } + // We're going to show the keyboard now... + _keyboardFocusObject = currentFocus; _currentHints = Qt::InputMethodHints(query.value(Qt::ImHints).toUInt()); vr::EGamepadTextInputMode inputMode = vr::k_EGamepadTextInputModeNormal; if (_currentHints & Qt::ImhHiddenText) { @@ -123,17 +137,24 @@ void showOpenVrKeyboard(bool show = true) { } _existingText = query.value(Qt::ImSurroundingText).toString(); - auto showKeyboardResult = _overlay->ShowKeyboard(inputMode, lineMode, "Keyboard", 1024, + auto showKeyboardResult = _overlay->ShowKeyboard(inputMode, lineMode, "Keyboard", 1024, _existingText.toLocal8Bit().toStdString().c_str(), false, 0); - mat4 headPose = toGlm(_trackedDevicePose[0].mDeviceToAbsoluteTracking); - mat4 keyboardTransform = glm::translate(headPose, vec3(0, -0.5, -1)); - keyboardTransform = keyboardTransform * glm::rotate(mat4(), 3.14159f / 4.0f, vec3(-1, 0, 0)); - auto keyboardTransformVr = toOpenVr(keyboardTransform); - _overlay->SetKeyboardTransformAbsolute(vr::ETrackingUniverseOrigin::TrackingUniverseStanding, &keyboardTransformVr); - } else { - _focusObject = nullptr; + if (vr::VROverlayError_None == showKeyboardResult) { + _keyboardShown = true; + // Try to position the keyboard slightly below where the user is looking. + mat4 headPose = toGlm(_trackedDevicePose[0].mDeviceToAbsoluteTracking); + mat4 keyboardTransform = glm::translate(headPose, vec3(0, -0.5, -1)); + keyboardTransform = keyboardTransform * glm::rotate(mat4(), 3.14159f / 4.0f, vec3(-1, 0, 0)); + auto keyboardTransformVr = toOpenVr(keyboardTransform); + _overlay->SetKeyboardTransformAbsolute(vr::ETrackingUniverseOrigin::TrackingUniverseStanding, &keyboardTransformVr); + } + }); + } else { + _keyboardFocusObject = nullptr; + if (_keyboardShown) { _overlay->HideKeyboard(); + _keyboardShown = false; } } } @@ -147,7 +168,7 @@ void finishOpenVrKeyboardInput() { // ImhDialableCharactersOnly ImhEmailCharactersOnly ImhUrlCharactersOnly ImhLatinOnly QInputMethodEvent event(_existingText, QList()); event.setCommitString(newText, 0, _existingText.size()); - qApp->sendEvent(_focusObject, &event); + qApp->sendEvent(_keyboardFocusObject, &event); // Simulate an enter press on the top level window to trigger the action if (0 == (_currentHints & Qt::ImhMultiLine)) { qApp->sendEvent(offscreenUi->getWindow(), &QKeyEvent(QEvent::KeyPress, Qt::Key_Return, Qt::KeyboardModifiers(), QString("\n"))); @@ -155,12 +176,18 @@ void finishOpenVrKeyboardInput() { } } +static const QString DEBUG_FLAG("HIFI_DISABLE_STEAM_VR_KEYBOARD"); +bool disableSteamVrKeyboard = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); + void enableOpenVrKeyboard() { + if (disableSteamVrKeyboard) { + return; + } auto offscreenUi = DependencyManager::get(); _overlay = vr::VROverlay(); _focusConnection = QObject::connect(offscreenUi->getWindow(), &QQuickWindow::focusObjectChanged, [](QObject* object) { - if (object != _focusObject && _overlay) { + if (object != _keyboardFocusObject) { showOpenVrKeyboard(false); } }); @@ -174,6 +201,9 @@ void enableOpenVrKeyboard() { void disableOpenVrKeyboard() { + if (disableSteamVrKeyboard) { + return; + } QObject::disconnect(_focusTextConnection); QObject::disconnect(_focusConnection); } From f8353fb082c875ec56cb094853cba9730a005610 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 8 Jun 2016 11:11:18 -0700 Subject: [PATCH 025/171] New button content and initial positions. --- .../assets/images/tools/hmd-switch-01.svg | 198 ++++++++++-------- scripts/system/edit.js | 2 +- scripts/system/examples.js | 4 +- scripts/system/goto.js | 2 +- scripts/system/hmd.js | 2 +- 5 files changed, 115 insertions(+), 93 deletions(-) diff --git a/scripts/system/assets/images/tools/hmd-switch-01.svg b/scripts/system/assets/images/tools/hmd-switch-01.svg index 15ecb02b6b..31389d355c 100644 --- a/scripts/system/assets/images/tools/hmd-switch-01.svg +++ b/scripts/system/assets/images/tools/hmd-switch-01.svg @@ -4,99 +4,121 @@ viewBox="0 0 50 150" style="enable-background:new 0 0 50 150;" xml:space="preserve"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + diff --git a/scripts/system/edit.js b/scripts/system/edit.js index afbc679ec4..38d596f83e 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -181,7 +181,7 @@ var toolBar = (function() { function initialize() { toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.edit.toolbar", function(windowDimensions, toolbar) { return { - x: windowDimensions.x / 2, + x: (windowDimensions.x / 2) + (Tool.IMAGE_WIDTH * 2), y: windowDimensions.y }; }, { diff --git a/scripts/system/examples.js b/scripts/system/examples.js index 9d33e473af..6f4268182c 100644 --- a/scripts/system/examples.js +++ b/scripts/system/examples.js @@ -60,7 +60,7 @@ var toolBar = (function() { function initialize() { toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.examples.toolbar", function(windowDimensions, toolbar) { return { - x: windowDimensions.x / 2, + x: (windowDimensions.x / 2) + (Tool.IMAGE_WIDTH * 2), y: windowDimensions.y }; }, { @@ -135,4 +135,4 @@ var toolBar = (function() { }()); Controller.mousePressEvent.connect(toolBar.mousePressEvent) -Script.scriptEnding.connect(toolBar.cleanup); \ No newline at end of file +Script.scriptEnding.connect(toolBar.cleanup); diff --git a/scripts/system/goto.js b/scripts/system/goto.js index 75d9829905..00b5e912c0 100644 --- a/scripts/system/goto.js +++ b/scripts/system/goto.js @@ -13,7 +13,7 @@ Script.include("libraries/toolBars.js"); function initialPosition(windowDimensions, toolbar) { return { - x: windowDimensions.x / 2 - Tool.IMAGE_WIDTH, + x: (windowDimensions.x / 2) - (Tool.IMAGE_WIDTH * 1), y: windowDimensions.y }; } diff --git a/scripts/system/hmd.js b/scripts/system/hmd.js index 277af68315..8b91e45676 100644 --- a/scripts/system/hmd.js +++ b/scripts/system/hmd.js @@ -22,7 +22,7 @@ var desktopMenuItemName = "Desktop"; function initialPosition(windowDimensions, toolbar) { return { - x: windowDimensions.x / 2 + (2 * Tool.IMAGE_WIDTH), + x: (windowDimensions.x / 2) - (Tool.IMAGE_WIDTH * 2.5), y: windowDimensions.y }; } From 42068cf2dd04bb7e4f4b3e8827f4bafbb4b0c82b Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 8 Jun 2016 15:01:53 -0700 Subject: [PATCH 026/171] Only render equip-hotspots for objects with attach points. --- .../system/controllers/handControllerGrab.js | 32 ++++++++----------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 8a96e9d80c..140aa54144 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -894,30 +894,24 @@ function MyController(hand) { var grabData = getEntityCustomData(GRABBABLE_DATA_KEY, entities[i], undefined); var grabProps = Entities.getEntityProperties(entities[i], GRABBABLE_PROPERTIES); if (grabData) { - - var hotspotPos = grabProps.position; - // does this entity have an attach point? var wearableData = getEntityCustomData("wearable", entities[i], undefined); - if (wearableData) { + if (wearableData && wearableData.joints) { var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"; - if (wearableData[handJointName]) { - // draw the hotspot around the attach point. - hotspotPos = wearableData[handJointName][0]; + if (wearableData.joints[handJointName]) { + // draw the hotspot + this.equipHotspotOverlays.push(Overlays.addOverlay("sphere", { + position: grabProps.position, + size: 0.2, + color: { red: 90, green: 255, blue: 90 }, + alpha: 0.7, + solid: true, + visible: true, + ignoreRayIntersection: false, + drawInFront: false + })); } } - - // draw a hotspot! - this.equipHotspotOverlays.push(Overlays.addOverlay("sphere", { - position: hotspotPos, - size: 0.2, - color: { red: 90, green: 255, blue: 90 }, - alpha: 0.7, - solid: true, - visible: true, - ignoreRayIntersection: false, - drawInFront: false - })); } } }; From 5ef6847dc35b047ffbced088d07de9bcc79bda8e Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 8 Jun 2016 18:36:36 -0700 Subject: [PATCH 027/171] HMD re-centering while driving improvements. Previously the HUD fading in/out would also recenter the hmd sensor and the avatar, which caused many problems including: * The user's view could shift vertically. * Your avatar would briefly go into t-pose * other users would see your avatar go into t-pose. Now we now move the UI sphere instead, which results in a much smoother experience. MyAvatar: added hasDriveInput method. OverlayConductor: * removed avatar and sensor reset, instead the overlay's modelTransform is changed. * revived STANDING mode, which is active if myAvatar->getClearOverlayWhenDriving() is true and you are wearing an HMD. * SITTING & FLAT mode should be unchanged. * Instead of using avatar velocity to fade out/fade in the hud, We use the presense or absanse of avatar drive input. * Additionally, we check distance to the UI sphere, and quickly recenter the hud if the users head is too close to the actual hud sphere. CompositorHelper: * Bug fixes for ray picks not using the modelTransform. HmdDisplayPlugin: * Bug fixes for rendering not using the modelTransform. --- interface/src/avatar/MyAvatar.cpp | 7 +- interface/src/avatar/MyAvatar.h | 2 + interface/src/ui/OverlayConductor.cpp | 113 ++++++++---------- interface/src/ui/OverlayConductor.h | 1 + .../src/display-plugins/CompositorHelper.cpp | 4 +- .../display-plugins/hmd/HmdDisplayPlugin.cpp | 3 +- 6 files changed, 66 insertions(+), 64 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 62772c6933..76e48d34d6 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1261,8 +1261,7 @@ void MyAvatar::prepareForPhysicsSimulation() { _characterController.setPositionAndOrientation(getPosition(), getOrientation()); if (qApp->isHMDMode()) { - bool hasDriveInput = fabsf(_driveKeys[TRANSLATE_X]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Z]) > 0.0f; - _follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput); + _follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput()); } else { _follow.deactivate(); } @@ -2135,3 +2134,7 @@ bool MyAvatar::didTeleport() { lastPosition = pos; return (changeInPosition.length() > MAX_AVATAR_MOVEMENT_PER_FRAME); } + +bool MyAvatar::hasDriveInput() const { + return fabsf(_driveKeys[TRANSLATE_X]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Z]) > 0.0f; +} diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index d3da32e0ed..381d658046 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -264,6 +264,8 @@ public: controller::Pose getLeftHandControllerPoseInAvatarFrame() const; controller::Pose getRightHandControllerPoseInAvatarFrame() const; + bool hasDriveInput() const; + public slots: void increaseSize(); void decreaseSize(); diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index 54dba229e3..c4fd938e97 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -22,10 +22,31 @@ OverlayConductor::OverlayConductor() { OverlayConductor::~OverlayConductor() { } +bool OverlayConductor::shouldCenterUI() const { + + glm::mat4 hmdMat = qApp->getHMDSensorPose(); + glm::vec3 hmdPos = extractTranslation(hmdMat); + glm::vec3 hmdForward = transformVectorFast(hmdMat, glm::vec3(0.0f, 0.0f, -1.0f)); + + Transform uiTransform = qApp->getApplicationCompositor().getModelTransform(); + glm::vec3 uiPos = uiTransform.getTranslation(); + glm::vec3 uiForward = uiTransform.getRotation() * glm::vec3(0.0f, 0.0f, -1.0f); + + const float MAX_COMPOSITOR_DISTANCE = 0.6f; + const float MAX_COMPOSITOR_ANGLE = 180.0f; // rotation check is effectively disabled + if (glm::distance(uiPos, hmdPos) > MAX_COMPOSITOR_DISTANCE || + glm::dot(uiForward, hmdForward) < cosf(glm::radians(MAX_COMPOSITOR_ANGLE))) { + return true; + } + return false; +} + void OverlayConductor::update(float dt) { updateMode(); + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + switch (_mode) { case SITTING: { // when sitting, the overlay is at the origin, facing down the -z axis. @@ -36,27 +57,30 @@ void OverlayConductor::update(float dt) { break; } case STANDING: { - // when standing, the overlay is at a reference position, which is set when the overlay is - // enabled. The camera is taken directly from the HMD, but in world space. - // So the sensorToWorldMatrix must be applied. - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - Transform t; - t.evalFromRawMatrix(myAvatar->getSensorToWorldMatrix()); - qApp->getApplicationCompositor().setCameraBaseTransform(t); - // detect when head moves out side of sweet spot, or looks away. - mat4 headMat = myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose(); - vec3 headWorldPos = extractTranslation(headMat); - vec3 headForward = glm::quat_cast(headMat) * glm::vec3(0.0f, 0.0f, -1.0f); - Transform modelXform = qApp->getApplicationCompositor().getModelTransform(); - vec3 compositorWorldPos = modelXform.getTranslation(); - vec3 compositorForward = modelXform.getRotation() * glm::vec3(0.0f, 0.0f, -1.0f); - const float MAX_COMPOSITOR_DISTANCE = 0.6f; - const float MAX_COMPOSITOR_ANGLE = 110.0f; - if (_enabled && (glm::distance(headWorldPos, compositorWorldPos) > MAX_COMPOSITOR_DISTANCE || - glm::dot(headForward, compositorForward) < cosf(glm::radians(MAX_COMPOSITOR_ANGLE)))) { - // fade out the overlay - setEnabled(false); + const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE = 200 * 1000; + const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE = 1000 * 1000; + + // fade in or out the overlay, based on driving. + bool nowDriving = myAvatar->hasDriveInput(); + // Check that we're in this new mode for long enough to really trigger a transition. + if (nowDriving == _driving) { // If there's no change in state, clear any attepted timer. + _timeInPotentialMode = 0; + } else if (_timeInPotentialMode == 0) { // We've just changed with no timer, so start timing now. + _timeInPotentialMode = usecTimestampNow(); + } else if ((usecTimestampNow() - _timeInPotentialMode) > (nowDriving ? REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE : REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE)) { + _timeInPotentialMode = 0; // a real transition + bool wantsOverlays = Menu::getInstance()->isOptionChecked(MenuOption::Overlays); + if (wantsOverlays) { + setEnabled(!nowDriving); + } + _driving = nowDriving; + } + + // center the UI + if (shouldCenterUI()) { + Transform hmdTransform(cancelOutRollAndPitch(qApp->getHMDSensorPose())); + qApp->getApplicationCompositor().setModelTransform(hmdTransform); } break; } @@ -68,43 +92,14 @@ void OverlayConductor::update(float dt) { void OverlayConductor::updateMode() { MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - if (myAvatar->getClearOverlayWhenDriving()) { - float speed = glm::length(myAvatar->getVelocity()); - const float MIN_DRIVING = 0.2f; - const float MAX_NOT_DRIVING = 0.01f; - const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE = 200 * 1000; - const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE = 1000 * 1000; - bool nowDriving = _driving; // Assume current _driving mode unless... - if (speed > MIN_DRIVING) { // ... we're definitely moving... - nowDriving = true; - } else if (speed < MAX_NOT_DRIVING) { // ... or definitely not. - nowDriving = false; - } - // Check that we're in this new mode for long enough to really trigger a transition. - if (nowDriving == _driving) { // If there's no change in state, clear any attepted timer. - _timeInPotentialMode = 0; - } else if (_timeInPotentialMode == 0) { // We've just changed with no timer, so start timing now. - _timeInPotentialMode = usecTimestampNow(); - } else if ((usecTimestampNow() - _timeInPotentialMode) > (nowDriving ? REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE : REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE)) { - _timeInPotentialMode = 0; // a real transition - if (nowDriving) { - _wantsOverlays = Menu::getInstance()->isOptionChecked(MenuOption::Overlays); - } else { // reset when coming out of driving - _mode = FLAT; // Seems appropriate to let things reset, below, after the following. - // All reset of, e.g., room-scale location as though by apostrophe key, without all the other adjustments. - qApp->getActiveDisplayPlugin()->resetSensors(); - myAvatar->reset(true, false, false); - } - if (_wantsOverlays) { - setEnabled(!nowDriving); - } - _driving = nowDriving; - } // Else haven't accumulated enough time in new mode, but keep timing. - } Mode newMode; if (qApp->isHMDMode()) { - newMode = SITTING; + if (myAvatar->getClearOverlayWhenDriving()) { + newMode = STANDING; + } else { + newMode = SITTING; + } } else { newMode = FLAT; } @@ -117,11 +112,10 @@ void OverlayConductor::updateMode() { qApp->getApplicationCompositor().setModelTransform(Transform()); break; } - case STANDING: { // STANDING mode is not currently used. + case STANDING: { // enter the STANDING state - // place the overlay at the current hmd position in world space - auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose()); - qApp->getApplicationCompositor().setModelTransform(Transform(camMat)); + Transform hmdTransform(cancelOutRollAndPitch(qApp->getHMDSensorPose())); + qApp->getApplicationCompositor().setModelTransform(hmdTransform); break; } @@ -132,7 +126,6 @@ void OverlayConductor::updateMode() { } _mode = newMode; - } void OverlayConductor::setEnabled(bool enabled) { @@ -149,7 +142,7 @@ void OverlayConductor::setEnabled(bool enabled) { MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose()); qApp->getApplicationCompositor().setModelTransform(Transform(camMat)); - } + } } bool OverlayConductor::getEnabled() const { diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h index 1ec66663a4..4ecbac5bcf 100644 --- a/interface/src/ui/OverlayConductor.h +++ b/interface/src/ui/OverlayConductor.h @@ -19,6 +19,7 @@ public: void update(float dt); void setEnabled(bool enable); bool getEnabled() const; + bool shouldCenterUI() const; private: void updateMode(); diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp index d4fff1b976..2d3c79071f 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp @@ -336,7 +336,9 @@ void CompositorHelper::computeHmdPickRay(const glm::vec2& cursorPos, glm::vec3& } glm::mat4 CompositorHelper::getUiTransform() const { - return _currentCamera * glm::inverse(_currentDisplayPlugin->getHeadPose()); + glm::mat4 modelMat; + _modelTransform.getMatrix(modelMat); + return _currentCamera * glm::inverse(_currentDisplayPlugin->getHeadPose()) * modelMat; } //Finds the collision point of a world space ray diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 1616dcdb77..b9f6ad1a98 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -253,12 +253,13 @@ void HmdDisplayPlugin::compositeScene() { void HmdDisplayPlugin::compositeOverlay() { using namespace oglplus; auto compositorHelper = DependencyManager::get(); + glm::mat4 modelMat = compositorHelper->getModelTransform().getMatrix(); useProgram(_program); _sphereSection->Use(); for_each_eye([&](Eye eye) { eyeViewport(eye); - auto modelView = glm::inverse(_currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye)); + auto modelView = glm::inverse(_currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye)) * modelMat; auto mvp = _eyeProjections[eye] * modelView; Uniform(*_program, _mvpUniform).Set(mvp); _sphereSection->Draw(); From 6e3057479ffacde155330039d249b242210adb92 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 9 Jun 2016 11:27:02 -0700 Subject: [PATCH 028/171] fixes for warnings Also overlays menu option should work better in conjunction with ui-center/fading --- interface/src/ui/OverlayConductor.cpp | 4 +--- interface/src/ui/OverlayConductor.h | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index c4fd938e97..9e2e8cb0db 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -71,9 +71,7 @@ void OverlayConductor::update(float dt) { } else if ((usecTimestampNow() - _timeInPotentialMode) > (nowDriving ? REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE : REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE)) { _timeInPotentialMode = 0; // a real transition bool wantsOverlays = Menu::getInstance()->isOptionChecked(MenuOption::Overlays); - if (wantsOverlays) { - setEnabled(!nowDriving); - } + setEnabled(!nowDriving && wantsOverlays); _driving = nowDriving; } diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h index 4ecbac5bcf..2e425454c7 100644 --- a/interface/src/ui/OverlayConductor.h +++ b/interface/src/ui/OverlayConductor.h @@ -34,7 +34,6 @@ private: bool _enabled { false }; bool _driving { false }; quint64 _timeInPotentialMode { 0 }; - bool _wantsOverlays { true }; }; #endif From 685710d0ec088b60b13fe7aef0ba3054cb67155e Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 9 Jun 2016 18:09:04 -0700 Subject: [PATCH 029/171] OverlayConductor improvments * the HUD will fade/in when driving, even in desktop mode. * the HUD no longer pops when you lean outside of the UI sphere, instead it should smoothly fade out and fade back in. * the overlay toggle button should override fading while driving, as expected. * removed any notion of SITTING, STANDING or FLAT mode from overlay. --- interface/src/ui/OverlayConductor.cpp | 139 ++++++++++++-------------- interface/src/ui/OverlayConductor.h | 22 ++-- 2 files changed, 76 insertions(+), 85 deletions(-) diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index 9e2e8cb0db..790652aa0f 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -22,7 +22,7 @@ OverlayConductor::OverlayConductor() { OverlayConductor::~OverlayConductor() { } -bool OverlayConductor::shouldCenterUI() const { +bool OverlayConductor::headOutsideOverlay() const { glm::mat4 hmdMat = qApp->getHMDSensorPose(); glm::vec3 hmdPos = extractTranslation(hmdMat); @@ -41,89 +41,82 @@ bool OverlayConductor::shouldCenterUI() const { return false; } -void OverlayConductor::update(float dt) { - - updateMode(); - +bool OverlayConductor::avatarHasDriveInput() const { MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - switch (_mode) { - case SITTING: { - // when sitting, the overlay is at the origin, facing down the -z axis. - // the camera is taken directly from the HMD. - Transform identity; - qApp->getApplicationCompositor().setModelTransform(identity); - qApp->getApplicationCompositor().setCameraBaseTransform(identity); - break; - } - case STANDING: { + const quint64 DRIVE_ENABLE_TIME_USECS = 200 * 1000; // 200 ms + const quint64 DRIVE_DISABLE_TIME_USECS = 1000 * 1000; // 1 s - const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE = 200 * 1000; - const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE = 1000 * 1000; - - // fade in or out the overlay, based on driving. - bool nowDriving = myAvatar->hasDriveInput(); - // Check that we're in this new mode for long enough to really trigger a transition. - if (nowDriving == _driving) { // If there's no change in state, clear any attepted timer. - _timeInPotentialMode = 0; - } else if (_timeInPotentialMode == 0) { // We've just changed with no timer, so start timing now. - _timeInPotentialMode = usecTimestampNow(); - } else if ((usecTimestampNow() - _timeInPotentialMode) > (nowDriving ? REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE : REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE)) { - _timeInPotentialMode = 0; // a real transition - bool wantsOverlays = Menu::getInstance()->isOptionChecked(MenuOption::Overlays); - setEnabled(!nowDriving && wantsOverlays); - _driving = nowDriving; - } - - // center the UI - if (shouldCenterUI()) { - Transform hmdTransform(cancelOutRollAndPitch(qApp->getHMDSensorPose())); - qApp->getApplicationCompositor().setModelTransform(hmdTransform); - } - break; + bool desiredDriving = myAvatar->hasDriveInput(); + if (desiredDriving != _desiredDriving) { + // start timer + _desiredDrivingTimer = usecTimestampNow() + (desiredDriving ? DRIVE_ENABLE_TIME_USECS : DRIVE_DISABLE_TIME_USECS); } - case FLAT: - // do nothing - break; + + _desiredDriving = desiredDriving; + + if (_desiredDrivingTimer != 0 && usecTimestampNow() > _desiredDrivingTimer) { + // timer expired + // change state! + _currentDriving = _desiredDriving; + // disable timer + _desiredDrivingTimer = 0; } + + return _currentDriving; } -void OverlayConductor::updateMode() { +bool OverlayConductor::shouldShowOverlay() const { MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - Mode newMode; - if (qApp->isHMDMode()) { - if (myAvatar->getClearOverlayWhenDriving()) { - newMode = STANDING; - } else { - newMode = SITTING; - } - } else { - newMode = FLAT; +#ifdef WANT_DEBUG + qDebug() << "AJT: wantsOverlays =" << Menu::getInstance()->isOptionChecked(MenuOption::Overlays) << ", clearOverlayWhenDriving =" << myAvatar->getClearOverlayWhenDriving() << + ", headOutsideOverlay =" << headOutsideOverlay() << ", hasDriveInput =" << avatarHasDriveInput(); +#endif + + return Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && (!myAvatar->getClearOverlayWhenDriving() || (!headOutsideOverlay() && !avatarHasDriveInput())); +} + +bool OverlayConductor::shouldRecenterOnFadeOut() const { + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + return Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && myAvatar->getClearOverlayWhenDriving() && headOutsideOverlay(); +} + +void OverlayConductor::centerUI() { + // place the overlay at the current hmd position in sensor space + auto camMat = cancelOutRollAndPitch(qApp->getHMDSensorPose()); + qApp->getApplicationCompositor().setModelTransform(Transform(camMat)); +} + +void OverlayConductor::update(float dt) { + + // centerUI if hmd mode changes + if (qApp->isHMDMode() && !_hmdMode) { + centerUI(); + } + _hmdMode = qApp->isHMDMode(); + + // centerUI if timer expires + if (_fadeOutTime != 0 && usecTimestampNow() > _fadeOutTime) { + // fade out timer expired + _fadeOutTime = 0; + centerUI(); } - if (newMode != _mode) { - switch (newMode) { - case SITTING: { - // enter the SITTING state - // place the overlay at origin - qApp->getApplicationCompositor().setModelTransform(Transform()); - break; - } - case STANDING: { - // enter the STANDING state - Transform hmdTransform(cancelOutRollAndPitch(qApp->getHMDSensorPose())); - qApp->getApplicationCompositor().setModelTransform(hmdTransform); - break; - } + bool showOverlay = shouldShowOverlay(); - case FLAT: - // do nothing - break; + if (showOverlay != getEnabled()) { + if (showOverlay) { + // disable fadeOut timer + _fadeOutTime = 0; + } else if (shouldRecenterOnFadeOut()) { + // start fadeOut timer + const quint64 FADE_OUT_TIME_USECS = 300 * 1000; // 300 ms + _fadeOutTime = usecTimestampNow() + FADE_OUT_TIME_USECS; } } - _mode = newMode; + setEnabled(showOverlay); } void OverlayConductor::setEnabled(bool enabled) { @@ -135,11 +128,9 @@ void OverlayConductor::setEnabled(bool enabled) { auto offscreenUi = DependencyManager::get(); offscreenUi->setPinned(!_enabled); // if the new state is visible/enabled... - if (_enabled && _mode == STANDING) { - // place the overlay at the current hmd position in world space - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose()); - qApp->getApplicationCompositor().setModelTransform(Transform(camMat)); + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + if (_enabled && myAvatar->getClearOverlayWhenDriving() && qApp->isHMDMode()) { + centerUI(); } } diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h index 2e425454c7..40ccb4b91f 100644 --- a/interface/src/ui/OverlayConductor.h +++ b/interface/src/ui/OverlayConductor.h @@ -19,21 +19,21 @@ public: void update(float dt); void setEnabled(bool enable); bool getEnabled() const; - bool shouldCenterUI() const; private: - void updateMode(); + bool headOutsideOverlay() const; + bool avatarHasDriveInput() const; + bool shouldShowOverlay() const; + bool shouldRecenterOnFadeOut() const; + void centerUI(); - enum Mode { - FLAT, - SITTING, - STANDING - }; - - Mode _mode { FLAT }; + quint64 _fadeOutTime { 0 }; bool _enabled { false }; - bool _driving { false }; - quint64 _timeInPotentialMode { 0 }; + bool _hmdMode { false }; + + mutable quint64 _desiredDrivingTimer { 0 }; + mutable bool _desiredDriving { false }; + mutable bool _currentDriving { false }; }; #endif From 4344a35c60f421e99e48df11612ac75c1cd21f28 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 10 Jun 2016 16:00:16 -0700 Subject: [PATCH 030/171] Use the new system pointer functionality. --- .../controllers/handControllerPointer.js | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index 7b45babf4d..1728647e5e 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -305,13 +305,19 @@ var leftTrigger = new Trigger(); var rightTrigger = new Trigger(); var activeTrigger = rightTrigger; var activeHand = Controller.Standard.RightHand; +var LEFT_HUD_LASER = 1; +var RIGHT_HUD_LASER = 2; +var BOTH_HUD_LASERS = LEFT_HUD_LASER + RIGHT_HUD_LASER; +var activeHudLaser = RIGHT_HUD_LASER; function toggleHand() { // unequivocally switch which hand controls mouse position if (activeHand === Controller.Standard.RightHand) { activeHand = Controller.Standard.LeftHand; activeTrigger = leftTrigger; + activeHudLaser = LEFT_HUD_LASER; } else { activeHand = Controller.Standard.RightHand; activeTrigger = rightTrigger; + activeHudLaser = RIGHT_HUD_LASER; } } function makeToggleAction(hand) { // return a function(0|1) that makes the specified hand control mouse when 1 @@ -342,6 +348,7 @@ clickMapping.enable(); // Same properties as handControllerGrab search sphere var BALL_SIZE = 0.011; var BALL_ALPHA = 0.5; +var LASER_COLOR_XYZW = {x: 10 / 255, y: 10 / 255, z: 255 / 255, w: BALL_ALPHA}; var fakeProjectionBall = Overlays.addOverlay("sphere", { size: 5 * BALL_SIZE, color: {red: 255, green: 10, blue: 10}, @@ -356,9 +363,24 @@ Script.scriptEnding.connect(function () { overlays.forEach(Overlays.deleteOverlay); }); var visualizationIsShowing = false; // Not whether it desired, but simply whether it is. Just an optimziation. +var systemLaserOn = false; +var SYSTEM_LASER_DIRECTION = Vec3.normalize({x: 0, y: -1, z: -1}); // Guessing 45 degrees. +function clearSystemLaser() { + if (!systemLaserOn) { + return; + } + print('FIXME remove: disableHandLasers', BOTH_HUD_LASERS); + HMD.disableHandLasers(BOTH_HUD_LASERS); + systemLaserOn = false; +} function turnOffVisualization(optionalEnableClicks) { // because we're showing cursor on HUD if (!optionalEnableClicks) { expireMouseCursor(); + clearSystemLaser(); + } else if (!systemLaserOn) { + print('FIXME remove: setHandLasers', activeHudLaser, true, JSON.stringify(LASER_COLOR_XYZW), JSON.stringify(SYSTEM_LASER_DIRECTION)); + HMD.setHandLasers(activeHudLaser, true, LASER_COLOR_XYZW, SYSTEM_LASER_DIRECTION); + systemLaserOn = true; } if (!visualizationIsShowing) { return; @@ -371,6 +393,7 @@ function turnOffVisualization(optionalEnableClicks) { // because we're showing c var MAX_RAY_SCALE = 32000; // Anything large. It's a scale, not a distance. function updateVisualization(controllerPosition, controllerDirection, hudPosition3d, hudPosition2d) { ignore(controllerPosition, controllerDirection, hudPosition2d); + clearSystemLaser(); // Show an indication of where the cursor will appear when crossing a HUD element, // and where in-world clicking will occur. // From f6ed5a1dae57820a320fa53162960b600bebce47 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 10 Jun 2016 18:33:40 -0700 Subject: [PATCH 031/171] Bugfixes based on feedback * When the overlay is hidden because your head is too close to the sphere, instead of coming back immediately, it waits until the avatar's velocity is near zero for a period of time. * Hooked up jump and fly to MyAvatar::hasDriveInput() * Added an internal state machine to OverlayConductor to manage hiding/showing transitions. * The overlay menu state is now tied directly to the overlay, so it will change state as the overlay is dynamically hidden/shown from code. * Removed slot going directly from MenuOption::Overlays directly to OverlayConductor::setEnable(). --- interface/src/Menu.cpp | 3 +- interface/src/avatar/MyAvatar.cpp | 2 +- interface/src/ui/OverlayConductor.cpp | 139 +++++++++++++++++++------- interface/src/ui/OverlayConductor.h | 35 +++++-- 4 files changed, 131 insertions(+), 48 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 031564fa7a..0c1f2116d9 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -256,8 +256,7 @@ Menu::Menu() { UNSPECIFIED_POSITION, "Advanced"); // View > Overlays - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Overlays, 0, true, - qApp, SLOT(setOverlaysVisible(bool))); + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Overlays, 0, true); // Navigate menu ---------------------------------- MenuWrapper* navigateMenu = addMenu("Navigate"); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 76e48d34d6..12fe7c4ac2 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2136,5 +2136,5 @@ bool MyAvatar::didTeleport() { } bool MyAvatar::hasDriveInput() const { - return fabsf(_driveKeys[TRANSLATE_X]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Z]) > 0.0f; + return fabsf(_driveKeys[TRANSLATE_X]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Y]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Z]) > 0.0f; } diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index 790652aa0f..01649a0b3a 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -17,13 +17,13 @@ #include "OverlayConductor.h" OverlayConductor::OverlayConductor() { + } OverlayConductor::~OverlayConductor() { } bool OverlayConductor::headOutsideOverlay() const { - glm::mat4 hmdMat = qApp->getHMDSensorPose(); glm::vec3 hmdPos = extractTranslation(hmdMat); glm::vec3 hmdForward = transformVectorFast(hmdMat, glm::vec3(0.0f, 0.0f, -1.0f)); @@ -41,7 +41,34 @@ bool OverlayConductor::headOutsideOverlay() const { return false; } -bool OverlayConductor::avatarHasDriveInput() const { +bool OverlayConductor::updateAvatarIsAtRest() { + + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + + const quint64 REST_ENABLE_TIME_USECS = 1000 * 1000; // 1 s + const quint64 REST_DISABLE_TIME_USECS = 200 * 1000; // 200 ms + + const float AT_REST_THRESHOLD = 0.01f; + bool desiredAtRest = glm::length(myAvatar->getVelocity()) < AT_REST_THRESHOLD; + if (desiredAtRest != _desiredAtRest) { + // start timer + _desiredAtRestTimer = usecTimestampNow() + (desiredAtRest ? REST_ENABLE_TIME_USECS : REST_DISABLE_TIME_USECS); + } + + _desiredAtRest = desiredAtRest; + + if (_desiredAtRestTimer != 0 && usecTimestampNow() > _desiredAtRestTimer) { + // timer expired + // change state! + _currentAtRest = _desiredAtRest; + // disable timer + _desiredAtRestTimer = 0; + } + + return _currentAtRest; +} + +bool OverlayConductor::updateAvatarHasDriveInput() { MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); const quint64 DRIVE_ENABLE_TIME_USECS = 200 * 1000; // 200 ms @@ -66,57 +93,88 @@ bool OverlayConductor::avatarHasDriveInput() const { return _currentDriving; } -bool OverlayConductor::shouldShowOverlay() const { - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - -#ifdef WANT_DEBUG - qDebug() << "AJT: wantsOverlays =" << Menu::getInstance()->isOptionChecked(MenuOption::Overlays) << ", clearOverlayWhenDriving =" << myAvatar->getClearOverlayWhenDriving() << - ", headOutsideOverlay =" << headOutsideOverlay() << ", hasDriveInput =" << avatarHasDriveInput(); -#endif - - return Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && (!myAvatar->getClearOverlayWhenDriving() || (!headOutsideOverlay() && !avatarHasDriveInput())); -} - -bool OverlayConductor::shouldRecenterOnFadeOut() const { - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - return Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && myAvatar->getClearOverlayWhenDriving() && headOutsideOverlay(); -} - void OverlayConductor::centerUI() { // place the overlay at the current hmd position in sensor space auto camMat = cancelOutRollAndPitch(qApp->getHMDSensorPose()); qApp->getApplicationCompositor().setModelTransform(Transform(camMat)); } +bool OverlayConductor::userWishesToHide() const { + // user pressed toggle button. + return Menu::getInstance()->isOptionChecked(MenuOption::Overlays) != _prevOverlayMenuChecked && Menu::getInstance()->isOptionChecked(MenuOption::Overlays); +} + +bool OverlayConductor::userWishesToShow() const { + // user pressed toggle button. + return Menu::getInstance()->isOptionChecked(MenuOption::Overlays) != _prevOverlayMenuChecked && !Menu::getInstance()->isOptionChecked(MenuOption::Overlays); +} + +void OverlayConductor::setState(State state) { +#ifdef WANT_DEBUG + static QString stateToString[NumStates] = { "Enabled", "DisabledByDrive", "DisabledByHead", "DisabledByToggle" }; + qDebug() << "OverlayConductor " << stateToString[state] << "<--" << stateToString[_state]; +#endif + _state = state; +} + +OverlayConductor::State OverlayConductor::getState() const { + return _state; +} + void OverlayConductor::update(float dt) { - // centerUI if hmd mode changes + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + + // centerUI when hmd mode is first enabled if (qApp->isHMDMode() && !_hmdMode) { centerUI(); } _hmdMode = qApp->isHMDMode(); - // centerUI if timer expires - if (_fadeOutTime != 0 && usecTimestampNow() > _fadeOutTime) { - // fade out timer expired - _fadeOutTime = 0; - centerUI(); + bool prevDriving = _currentDriving; + bool isDriving = updateAvatarHasDriveInput(); + bool drivingChanged = prevDriving != isDriving; + + bool isAtRest = updateAvatarIsAtRest(); + + switch (getState()) { + case Enabled: + if (qApp->isHMDMode() && headOutsideOverlay()) { + setState(DisabledByHead); + setEnabled(false); + } + if (userWishesToHide()) { + setState(DisabledByToggle); + setEnabled(false); + } + if (drivingChanged && isDriving) { + setState(DisabledByDrive); + setEnabled(false); + } + break; + case DisabledByDrive: + if (!isDriving || userWishesToShow()) { + setState(Enabled); + setEnabled(true); + } + break; + case DisabledByHead: + if (isAtRest || userWishesToShow()) { + setState(Enabled); + setEnabled(true); + } + break; + case DisabledByToggle: + if (userWishesToShow()) { + setState(Enabled); + setEnabled(true); + } + break; + default: + break; } - bool showOverlay = shouldShowOverlay(); - - if (showOverlay != getEnabled()) { - if (showOverlay) { - // disable fadeOut timer - _fadeOutTime = 0; - } else if (shouldRecenterOnFadeOut()) { - // start fadeOut timer - const quint64 FADE_OUT_TIME_USECS = 300 * 1000; // 300 ms - _fadeOutTime = usecTimestampNow() + FADE_OUT_TIME_USECS; - } - } - - setEnabled(showOverlay); + _prevOverlayMenuChecked = Menu::getInstance()->isOptionChecked(MenuOption::Overlays); } void OverlayConductor::setEnabled(bool enabled) { @@ -127,6 +185,11 @@ void OverlayConductor::setEnabled(bool enabled) { _enabled = enabled; // set the new value auto offscreenUi = DependencyManager::get(); offscreenUi->setPinned(!_enabled); + + // ensure that the the state of the menu item reflects the state of the overlay. + Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, _enabled); + _prevOverlayMenuChecked = _enabled; + // if the new state is visible/enabled... MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); if (_enabled && myAvatar->getClearOverlayWhenDriving() && qApp->isHMDMode()) { diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h index 40ccb4b91f..83d6957012 100644 --- a/interface/src/ui/OverlayConductor.h +++ b/interface/src/ui/OverlayConductor.h @@ -22,18 +22,39 @@ public: private: bool headOutsideOverlay() const; - bool avatarHasDriveInput() const; - bool shouldShowOverlay() const; - bool shouldRecenterOnFadeOut() const; + bool updateAvatarHasDriveInput(); + bool updateAvatarIsAtRest(); + bool userWishesToHide() const; + bool userWishesToShow() const; void centerUI(); - quint64 _fadeOutTime { 0 }; + enum State { + Enabled = 0, + DisabledByDrive, + DisabledByHead, + DisabledByToggle, + NumStates + }; + + void setState(State state); + State getState() const; + + State _state { DisabledByDrive }; + + bool _prevOverlayMenuChecked { true }; bool _enabled { false }; bool _hmdMode { false }; + bool _disabledFromHead { false }; - mutable quint64 _desiredDrivingTimer { 0 }; - mutable bool _desiredDriving { false }; - mutable bool _currentDriving { false }; + // used by updateAvatarHasDriveInput + quint64 _desiredDrivingTimer { 0 }; + bool _desiredDriving { false }; + bool _currentDriving { false }; + + // used by updateAvatarIsAtRest + quint64 _desiredAtRestTimer { 0 }; + bool _desiredAtRest { true }; + bool _currentAtRest { true }; }; #endif From ab4bef7d55cbef9063ad0a53e85d5353e5fb66eb Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 13 Jun 2016 11:19:47 -0700 Subject: [PATCH 032/171] Fixes based on PR feedback * The "Clear Overlay When Driving" avatar preference is obeyed. * sensor reset will also center the ui. --- interface/src/Application.cpp | 1 + interface/src/ui/OverlayConductor.cpp | 6 +++--- interface/src/ui/OverlayConductor.h | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1e6f7ba995..e494142302 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4209,6 +4209,7 @@ void Application::resetSensors(bool andReload) { DependencyManager::get()->reset(); DependencyManager::get()->reset(); getActiveDisplayPlugin()->resetSensors(); + _overlayConductor.centerUI(); getMyAvatar()->reset(andReload); QMetaObject::invokeMethod(DependencyManager::get().data(), "reset", Qt::QueuedConnection); } diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index 01649a0b3a..b653d0f445 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -139,7 +139,7 @@ void OverlayConductor::update(float dt) { switch (getState()) { case Enabled: - if (qApp->isHMDMode() && headOutsideOverlay()) { + if (myAvatar->getClearOverlayWhenDriving() && qApp->isHMDMode() && headOutsideOverlay()) { setState(DisabledByHead); setEnabled(false); } @@ -147,7 +147,7 @@ void OverlayConductor::update(float dt) { setState(DisabledByToggle); setEnabled(false); } - if (drivingChanged && isDriving) { + if (myAvatar->getClearOverlayWhenDriving() && drivingChanged && isDriving) { setState(DisabledByDrive); setEnabled(false); } @@ -192,7 +192,7 @@ void OverlayConductor::setEnabled(bool enabled) { // if the new state is visible/enabled... MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - if (_enabled && myAvatar->getClearOverlayWhenDriving() && qApp->isHMDMode()) { + if (_enabled && qApp->isHMDMode()) { centerUI(); } } diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h index 83d6957012..fcfdac72a5 100644 --- a/interface/src/ui/OverlayConductor.h +++ b/interface/src/ui/OverlayConductor.h @@ -20,13 +20,14 @@ public: void setEnabled(bool enable); bool getEnabled() const; + void centerUI(); + private: bool headOutsideOverlay() const; bool updateAvatarHasDriveInput(); bool updateAvatarIsAtRest(); bool userWishesToHide() const; bool userWishesToShow() const; - void centerUI(); enum State { Enabled = 0, From 1ca1f98034731127e00954405b8c8df5e8a36aff Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 13 Jun 2016 12:01:42 -0700 Subject: [PATCH 033/171] Fix unused variable warning --- interface/src/ui/OverlayConductor.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index b653d0f445..a897d85472 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -191,7 +191,6 @@ void OverlayConductor::setEnabled(bool enabled) { _prevOverlayMenuChecked = _enabled; // if the new state is visible/enabled... - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); if (_enabled && qApp->isHMDMode()) { centerUI(); } From 21e67fc4d844c8811231029fbb6fe60e24ac5dfc Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 13 Jun 2016 12:03:42 -0700 Subject: [PATCH 034/171] Another unused variable warning --- interface/src/ui/OverlayConductor.h | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h index fcfdac72a5..375b2652f6 100644 --- a/interface/src/ui/OverlayConductor.h +++ b/interface/src/ui/OverlayConductor.h @@ -45,7 +45,6 @@ private: bool _prevOverlayMenuChecked { true }; bool _enabled { false }; bool _hmdMode { false }; - bool _disabledFromHead { false }; // used by updateAvatarHasDriveInput quint64 _desiredDrivingTimer { 0 }; From 9640727f518e06ad915746848ad3b8ebea030d7e Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 8 Jun 2016 17:09:30 -0700 Subject: [PATCH 035/171] Hand laser rendering support in HMD plugins. --- .../src/scripting/HMDScriptingInterface.cpp | 10 ++ .../src/scripting/HMDScriptingInterface.h | 2 + .../display-plugins/OpenGLDisplayPlugin.cpp | 53 ++++--- .../src/display-plugins/OpenGLDisplayPlugin.h | 21 ++- .../display-plugins/hmd/HmdDisplayPlugin.cpp | 149 +++++++++++++++++- .../display-plugins/hmd/HmdDisplayPlugin.h | 20 ++- libraries/gl/src/gl/OglplusHelpers.cpp | 92 ++++++++++- libraries/gl/src/gl/OglplusHelpers.h | 5 +- libraries/plugins/src/plugins/DisplayPlugin.h | 20 +++ .../oculus/src/OculusBaseDisplayPlugin.cpp | 5 +- .../src/OculusLegacyDisplayPlugin.cpp | 5 +- plugins/openvr/src/OpenVrDisplayPlugin.cpp | 63 ++++++-- plugins/openvr/src/OpenVrDisplayPlugin.h | 1 - plugins/openvr/src/OpenVrHelpers.cpp | 4 + plugins/openvr/src/OpenVrHelpers.h | 1 + 15 files changed, 399 insertions(+), 52 deletions(-) diff --git a/interface/src/scripting/HMDScriptingInterface.cpp b/interface/src/scripting/HMDScriptingInterface.cpp index 7bf1547a3c..02840a9775 100644 --- a/interface/src/scripting/HMDScriptingInterface.cpp +++ b/interface/src/scripting/HMDScriptingInterface.cpp @@ -105,3 +105,13 @@ QString HMDScriptingInterface::preferredAudioInput() const { QString HMDScriptingInterface::preferredAudioOutput() const { return qApp->getActiveDisplayPlugin()->getPreferredAudioOutDevice(); } + +bool HMDScriptingInterface::setHandLasers(int hands, bool enabled, const glm::vec4& color, const glm::vec3& direction) const { + return qApp->getActiveDisplayPlugin()->setHandLaser(hands, + enabled ? DisplayPlugin::HandLaserMode::Overlay : DisplayPlugin::HandLaserMode::None, + color, direction); +} + +void HMDScriptingInterface::disableHandLasers(int hands) const { + qApp->getActiveDisplayPlugin()->setHandLaser(hands, DisplayPlugin::HandLaserMode::None); +} diff --git a/interface/src/scripting/HMDScriptingInterface.h b/interface/src/scripting/HMDScriptingInterface.h index d4c7b7cc0e..c55320ca83 100644 --- a/interface/src/scripting/HMDScriptingInterface.h +++ b/interface/src/scripting/HMDScriptingInterface.h @@ -36,6 +36,8 @@ public: Q_INVOKABLE glm::vec2 overlayToSpherical(const glm::vec2 & overlayPos) const; Q_INVOKABLE QString preferredAudioInput() const; Q_INVOKABLE QString preferredAudioOutput() const; + Q_INVOKABLE bool setHandLasers(int hands, bool enabled, const glm::vec4& color, const glm::vec3& direction) const; + Q_INVOKABLE void disableHandLasers(int hands) const; public: HMDScriptingInterface(); diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index d9ee979777..363bde15e6 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -213,9 +213,10 @@ OpenGLDisplayPlugin::OpenGLDisplayPlugin() { } void OpenGLDisplayPlugin::cleanupForSceneTexture(const gpu::TexturePointer& sceneTexture) { - Lock lock(_mutex); - Q_ASSERT(_sceneTextureToFrameIndexMap.contains(sceneTexture)); - _sceneTextureToFrameIndexMap.remove(sceneTexture); + withRenderThreadLock([&] { + Q_ASSERT(_sceneTextureToFrameIndexMap.contains(sceneTexture)); + _sceneTextureToFrameIndexMap.remove(sceneTexture); + }); } @@ -394,10 +395,9 @@ void OpenGLDisplayPlugin::submitSceneTexture(uint32_t frameIndex, const gpu::Tex return; } - { - Lock lock(_mutex); + withRenderThreadLock([&] { _sceneTextureToFrameIndexMap[sceneTexture] = frameIndex; - } + }); // Submit it to the presentation thread via escrow _sceneTextureEscrow.submit(sceneTexture); @@ -431,11 +431,12 @@ void OpenGLDisplayPlugin::updateTextures() { } void OpenGLDisplayPlugin::updateFrameData() { - Lock lock(_mutex); - auto previousFrameIndex = _currentPresentFrameIndex; - _currentPresentFrameIndex = _sceneTextureToFrameIndexMap[_currentSceneTexture]; - auto skippedCount = (_currentPresentFrameIndex - previousFrameIndex) - 1; - _droppedFrameRate.increment(skippedCount); + withPresentThreadLock([&] { + auto previousFrameIndex = _currentPresentFrameIndex; + _currentPresentFrameIndex = _sceneTextureToFrameIndexMap[_currentSceneTexture]; + auto skippedCount = (_currentPresentFrameIndex - previousFrameIndex) - 1; + _droppedFrameRate.increment(skippedCount); + }); } void OpenGLDisplayPlugin::compositeOverlay() { @@ -492,14 +493,14 @@ void OpenGLDisplayPlugin::compositeLayers() { } _compositeFramebuffer->Bound(Framebuffer::Target::Draw, [&] { Context::Viewport(targetRenderSize.x, targetRenderSize.y); - Context::Clear().DepthBuffer(); - glBindTexture(GL_TEXTURE_2D, getSceneTextureId()); - compositeScene(); + auto sceneTextureId = getSceneTextureId(); auto overlayTextureId = getOverlayTextureId(); + glBindTexture(GL_TEXTURE_2D, sceneTextureId); + compositeScene(); if (overlayTextureId) { - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBindTexture(GL_TEXTURE_2D, overlayTextureId); + Context::Enable(Capability::Blend); + Context::BlendFunc(BlendFunction::SrcAlpha, BlendFunction::OneMinusSrcAlpha); compositeOverlay(); auto compositorHelper = DependencyManager::get(); @@ -507,10 +508,14 @@ void OpenGLDisplayPlugin::compositeLayers() { auto& cursorManager = Cursor::Manager::instance(); const auto& cursorData = _cursorsData[cursorManager.getCursor()->getIcon()]; glBindTexture(GL_TEXTURE_2D, cursorData.texture); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, overlayTextureId); compositePointer(); + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE0); } glBindTexture(GL_TEXTURE_2D, 0); - glDisable(GL_BLEND); + Context::Disable(Capability::Blend); } }); } @@ -549,7 +554,11 @@ float OpenGLDisplayPlugin::newFramePresentRate() const { } float OpenGLDisplayPlugin::droppedFrameRate() const { - return _droppedFrameRate.rate(); + float result; + withRenderThreadLock([&] { + result = _droppedFrameRate.rate(); + }); + return result; } float OpenGLDisplayPlugin::presentRate() const { @@ -664,3 +673,11 @@ void OpenGLDisplayPlugin::useProgram(const ProgramPtr& program) { _activeProgram = program; } } + +void OpenGLDisplayPlugin::assertIsRenderThread() const { + Q_ASSERT(QThread::currentThread() != _presentThread); +} + +void OpenGLDisplayPlugin::assertIsPresentThread() const { + Q_ASSERT(QThread::currentThread() == _presentThread); +} diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index c87ff1bc93..ec8aa45840 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -109,7 +109,6 @@ protected: int32_t _alphaUniform { -1 }; ShapeWrapperPtr _plane; - mutable Mutex _mutex; RateCounter<> _droppedFrameRate; RateCounter<> _newFrameRate; RateCounter<> _presentRate; @@ -135,7 +134,27 @@ protected: BasicFramebufferWrapperPtr _compositeFramebuffer; bool _lockCurrentTexture { false }; + void assertIsRenderThread() const; + void assertIsPresentThread() const; + + template + void withPresentThreadLock(F f) const { + assertIsPresentThread(); + Lock lock(_presentMutex); + f(); + } + + template + void withRenderThreadLock(F f) const { + assertIsRenderThread(); + Lock lock(_presentMutex); + f(); + } + private: + // Any resource shared by the main thread and the presntaion thread must + // be serialized through this mutex + mutable Mutex _presentMutex; ProgramPtr _activeProgram; }; diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 1616dcdb77..ce1a42971e 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -37,7 +38,6 @@ QRect HmdDisplayPlugin::getRecommendedOverlayRect() const { return CompositorHelper::VIRTUAL_SCREEN_RECOMMENDED_OVERLAY_RECT; } - bool HmdDisplayPlugin::internalActivate() { _monoPreview = _container->getBoolSetting("monoPreview", DEFAULT_MONO_VIEW); @@ -197,14 +197,43 @@ static ProgramPtr getReprojectionProgram() { #endif +static const char * LASER_VS = R"VS(#version 410 core +uniform mat4 mvp = mat4(1); + +in vec3 Position; + +out vec3 vPosition; + +void main() { + gl_Position = mvp * vec4(Position, 1); + vPosition = Position; +} + +)VS"; + +static const char * LASER_FS = R"FS(#version 410 core + +uniform vec4 color = vec4(1.0, 1.0, 1.0, 1.0); +in vec3 vPosition; + +out vec4 FragColor; + +void main() { + FragColor = color; +} + +)FS"; + void HmdDisplayPlugin::customizeContext() { Parent::customizeContext(); // Only enable mirroring if we know vsync is disabled enableVsync(false); _enablePreview = !isVsyncEnabled(); _sphereSection = loadSphereSection(_program, CompositorHelper::VIRTUAL_UI_TARGET_FOV.y, CompositorHelper::VIRTUAL_UI_ASPECT_RATIO); + compileProgram(_laserProgram, LASER_VS, LASER_FS); + _laserGeometry = loadLaser(_laserProgram); compileProgram(_reprojectionProgram, REPROJECTION_VS, REPROJECTION_FS); - + using namespace oglplus; REPROJECTION_MATRIX_LOCATION = Uniform(*_reprojectionProgram, "reprojection").Location(); INVERSE_PROJECTION_MATRIX_LOCATION = Uniform(*_reprojectionProgram, "inverseProjections").Location(); @@ -215,6 +244,8 @@ void HmdDisplayPlugin::uncustomizeContext() { _sphereSection.reset(); _compositeFramebuffer.reset(); _reprojectionProgram.reset(); + _laserProgram.reset(); + _laserGeometry.reset(); Parent::uncustomizeContext(); } @@ -285,6 +316,8 @@ void HmdDisplayPlugin::compositePointer() { Uniform(*_program, _mvpUniform).Set(mvp); _plane->Draw(); }); + + compositeLasers(); } void HmdDisplayPlugin::internalPresent() { @@ -343,22 +376,122 @@ void HmdDisplayPlugin::setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm: void HmdDisplayPlugin::updateFrameData() { // Check if we have old frame data to discard - { - Lock lock(_mutex); + withPresentThreadLock([&] { auto itr = _frameInfos.find(_currentPresentFrameIndex); if (itr != _frameInfos.end()) { _frameInfos.erase(itr); } - } + }); Parent::updateFrameData(); - { - Lock lock(_mutex); + withPresentThreadLock([&] { _currentPresentFrameInfo = _frameInfos[_currentPresentFrameIndex]; - } + }); } glm::mat4 HmdDisplayPlugin::getHeadPose() const { return _currentRenderFrameInfo.renderPose; } + +bool HmdDisplayPlugin::setHandLaser(uint32_t hands, HandLaserMode mode, const vec4& color, const vec3& direction) { + HandLaserInfo info; + info.mode = mode; + info.color = color; + info.direction = direction; + withRenderThreadLock([&] { + if (hands & Hand::LeftHand) { + _handLasers[0] = info; + } + if (hands & Hand::RightHand) { + _handLasers[1] = info; + } + }); + // FIXME defer to a child class plugin to determine if hand lasers are actually + return true; +} + +static float calculateRayUiCollisionDistance(const glm::mat4& headPose, const glm::vec3& position, const glm::vec3& direction) { + auto relativePosition4 = glm::inverse(headPose) * vec4(position, 1); + auto relativePosition = vec3(relativePosition4) / relativePosition4.w; + auto relativeDirection = glm::inverse(glm::quat_cast(headPose)) * direction; + if (glm::abs(glm::length2(relativeDirection) - 1.0f) > EPSILON) { + relativeDirection = glm::normalize(relativeDirection); + } + float uiRadius = 1.0f; + float instersectionDistance; + if (!glm::intersectRaySphere(relativePosition, relativeDirection, vec3(0), uiRadius * uiRadius, instersectionDistance)) { + return -1; + } + return instersectionDistance; +} + +void HmdDisplayPlugin::compositeLasers() { + std::array handLasers; + std::array renderHandPoses; + withPresentThreadLock([&] { + handLasers = _handLasers; + renderHandPoses = _handPoses; + }); + + // If neither hand laser is activated, exit + if (!handLasers[0].valid() && !handLasers[1].valid()) { + return; + } + + static const glm::mat4 identity; + if (renderHandPoses[0] == identity && renderHandPoses[1] == identity) { + return; + } + + // Render hand lasers + using namespace oglplus; + useProgram(_laserProgram); + _laserGeometry->Use(); + std::array handLaserModelMatrices; + + for (int i = 0; i < 2; ++i) { + if (renderHandPoses[i] == identity) { + continue; + } + const auto& handLaser = handLasers[i]; + if (!handLaser.valid()) { + continue; + } + + const auto& laserDirection = handLaser.direction; + auto model = renderHandPoses[i]; + auto castDirection = glm::quat_cast(model) * laserDirection; + + // Find the intersection of the laser with he UI and use it to scale the model matrix + float distance = calculateRayUiCollisionDistance(_currentPresentFrameInfo.presentPose, vec3(renderHandPoses[i][3]), castDirection); + if (distance < 0) { + continue; + } + + // Make sure we rotate to match the desired laser direction + if (laserDirection != Vectors::UNIT_NEG_Z) { + auto rotation = glm::rotation(Vectors::UNIT_NEG_Z, laserDirection); + model = model * glm::mat4_cast(rotation); + } + + model = glm::scale(model, vec3(distance)); + handLaserModelMatrices[i] = model; + } + + for_each_eye([&](Eye eye) { + eyeViewport(eye); + auto eyePose = _currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye); + auto view = glm::inverse(eyePose); + const auto& projection = _eyeProjections[eye]; + for (int i = 0; i < 2; ++i) { + if (handLaserModelMatrices[i] == identity) { + continue; + } + Uniform(*_laserProgram, "mvp").Set(projection * view * handLaserModelMatrices[i]); + Uniform(*_laserProgram, "color").Set(handLasers[i].color); + _laserGeometry->Draw(); + // TODO render some kind of visual indicator at the intersection point with the UI. + } + }); +} diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index e6ceb7e376..7cdcf06e9a 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -30,7 +30,7 @@ public: virtual glm::mat4 getHeadPose() const override; - + bool setHandLaser(uint32_t hands, HandLaserMode mode, const vec4& color, const vec3& direction) override; protected: virtual void hmdPresent() = 0; @@ -47,6 +47,22 @@ protected: void uncustomizeContext() override; void updateFrameData() override; + void compositeLasers(); + + + struct HandLaserInfo { + HandLaserMode mode { HandLaserMode::None }; + vec4 color { 1.0f }; + vec3 direction { 0, 0, -1 }; + + // Is this hand laser info suitable for drawing? + bool valid() const { + return (mode != HandLaserMode::None && color.a > 0.0f && direction != vec3()); + } + }; + + std::array _handLasers; + std::array _handPoses; std::array _eyeOffsets; std::array _eyeProjections; std::array _eyeInverseProjections; @@ -75,5 +91,7 @@ private: bool _enableReprojection { true }; ShapeWrapperPtr _sphereSection; ProgramPtr _reprojectionProgram; + ProgramPtr _laserProgram; + ShapeWrapperPtr _laserGeometry; }; diff --git a/libraries/gl/src/gl/OglplusHelpers.cpp b/libraries/gl/src/gl/OglplusHelpers.cpp index 5bf0298593..7a535a806d 100644 --- a/libraries/gl/src/gl/OglplusHelpers.cpp +++ b/libraries/gl/src/gl/OglplusHelpers.cpp @@ -45,9 +45,11 @@ in vec2 vTexCoord; out vec4 FragColor; void main() { - FragColor = texture(sampler, vTexCoord); FragColor.a *= alpha; + if (FragColor.a <= 0.0) { + discard; + } } )FS"; @@ -359,6 +361,94 @@ ShapeWrapperPtr loadSphereSection(ProgramPtr program, float fov, float aspect, i ); } +namespace oglplus { + namespace shapes { + + class Laser : public DrawingInstructionWriter, public DrawMode { + public: + using IndexArray = std::vector; + using PosArray = std::vector; + /// The type of the index container returned by Indices() + // vertex positions + PosArray _pos_data; + IndexArray _idx_data; + unsigned int _prim_count { 0 }; + + public: + Laser() { + int vertices = 2; + _pos_data.resize(vertices * 3); + _pos_data[0] = 0; + _pos_data[1] = 0; + _pos_data[2] = 0; + + _pos_data[3] = 0; + _pos_data[4] = 0; + _pos_data[5] = -1; + + _idx_data.push_back(0); + _idx_data.push_back(1); + _prim_count = 1; + } + + /// Returns the winding direction of faces + FaceOrientation FaceWinding(void) const { + return FaceOrientation::CCW; + } + + /// Queries the bounding sphere coordinates and dimensions + template + void BoundingSphere(Sphere& bounding_sphere) const { + bounding_sphere = Sphere(0, 0, -0.5, 0.5); + } + + typedef GLuint(Laser::*VertexAttribFunc)(std::vector&) const; + + /// Makes the vertex positions and returns the number of values per vertex + template + GLuint Positions(std::vector& dest) const { + dest.clear(); + dest.insert(dest.begin(), _pos_data.begin(), _pos_data.end()); + return 3; + } + + typedef VertexAttribsInfo< + Laser, + std::tuple + > VertexAttribs; + + + /// Returns element indices that are used with the drawing instructions + const IndexArray & Indices(Default = Default()) const { + return _idx_data; + } + + /// Returns the instructions for rendering of faces + DrawingInstructions Instructions(PrimitiveType primitive) const { + DrawingInstructions instr = MakeInstructions(); + DrawOperation operation; + operation.method = DrawOperation::Method::DrawElements; + operation.mode = primitive; + operation.first = 0; + operation.count = _prim_count * 3; + operation.restart_index = DrawOperation::NoRestartIndex(); + operation.phase = 0; + AddInstruction(instr, operation); + return instr; + } + + /// Returns the instructions for rendering of faces + DrawingInstructions Instructions(Default = Default()) const { + return Instructions(PrimitiveType::Lines); + } + }; + } +} + +ShapeWrapperPtr loadLaser(const ProgramPtr& program) { + return std::make_shared(shapes::ShapeWrapper("Position", shapes::Laser(), *program)); +} + void TextureRecycler::setSize(const uvec2& size) { if (size == _size) { return; diff --git a/libraries/gl/src/gl/OglplusHelpers.h b/libraries/gl/src/gl/OglplusHelpers.h index afb06069b8..8940205b21 100644 --- a/libraries/gl/src/gl/OglplusHelpers.h +++ b/libraries/gl/src/gl/OglplusHelpers.h @@ -64,8 +64,9 @@ ProgramPtr loadCubemapShader(); void compileProgram(ProgramPtr & result, const std::string& vs, const std::string& fs); ShapeWrapperPtr loadSkybox(ProgramPtr program); ShapeWrapperPtr loadPlane(ProgramPtr program, float aspect = 1.0f); -ShapeWrapperPtr loadSphereSection(ProgramPtr program, float fov = PI / 3.0f * 2.0f, float aspect = 16.0f / 9.0f, int slices = 32, int stacks = 32); - +ShapeWrapperPtr loadSphereSection(ProgramPtr program, float fov = PI / 3.0f * 2.0f, float aspect = 16.0f / 9.0f, int slices = 128, int stacks = 128); +ShapeWrapperPtr loadLaser(const ProgramPtr& program); + // A basic wrapper for constructing a framebuffer with a renderbuffer // for the depth attachment and an undefined type for the color attachement diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index 1f6a16cd46..9cb4e071f3 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -168,6 +168,26 @@ public: static const QString& MENU_PATH(); + enum Hand { + LeftHand = 0x01, + RightHand = 0x02, + }; + + enum class HandLaserMode { + None, // Render no hand lasers + Overlay, // Render hand lasers only if they intersect with the UI layer, and stop at the UI layer + }; + + virtual bool setHandLaser( + uint32_t hands, // Bits from the Hand enum + HandLaserMode mode, // Mode in which to render + const vec4& color = vec4(1), // The color of the rendered laser + const vec3& direction = vec3(0, 0, -1) // The direction in which to render the hand lasers + ) { + return false; + } + + signals: void recommendedFramebufferSizeChanged(const QSize & size); // Indicates that this display plugin is no longer valid for use. diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp index e9f8545cff..f5fdbd303c 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp @@ -24,8 +24,9 @@ bool OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) { auto trackingState = ovr_GetTrackingState(_session, _currentRenderFrameInfo.predictedDisplayTime, ovrTrue); _currentRenderFrameInfo.renderPose = toGlm(trackingState.HeadPose.ThePose); _currentRenderFrameInfo.presentPose = _currentRenderFrameInfo.renderPose; - Lock lock(_mutex); - _frameInfos[frameIndex] = _currentRenderFrameInfo; + withRenderThreadLock([&] { + _frameInfos[frameIndex] = _currentRenderFrameInfo; + }); return true; } diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp index 8e044fbc16..29a2a4bb1a 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp @@ -45,8 +45,9 @@ bool OculusLegacyDisplayPlugin::beginFrameRender(uint32_t frameIndex) { _currentRenderFrameInfo.predictedDisplayTime = _currentRenderFrameInfo.sensorSampleTime = ovr_GetTimeInSeconds(); _trackingState = ovrHmd_GetTrackingState(_hmd, _currentRenderFrameInfo.predictedDisplayTime); _currentRenderFrameInfo.rawRenderPose = _currentRenderFrameInfo.renderPose = toGlm(_trackingState.HeadPose.ThePose); - Lock lock(_mutex); - _frameInfos[frameIndex] = _currentRenderFrameInfo; + withRenderThreadLock([&]{ + _frameInfos[frameIndex] = _currentRenderFrameInfo; + }) return true; } diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index fbade9fd68..92c01dc0a3 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -29,11 +29,13 @@ Q_DECLARE_LOGGING_CATEGORY(displayplugins) const QString OpenVrDisplayPlugin::NAME("OpenVR (Vive)"); const QString StandingHMDSensorMode = "Standing HMD Sensor Mode"; // this probably shouldn't be hardcoded here -static vr::IVRCompositor* _compositor{ nullptr }; +static vr::IVRCompositor* _compositor { nullptr }; vr::TrackedDevicePose_t _trackedDevicePose[vr::k_unMaxTrackedDeviceCount]; + mat4 _trackedDevicePoseMat4[vr::k_unMaxTrackedDeviceCount]; vec3 _trackedDeviceLinearVelocities[vr::k_unMaxTrackedDeviceCount]; vec3 _trackedDeviceAngularVelocities[vr::k_unMaxTrackedDeviceCount]; + static mat4 _sensorResetMat; static std::array VR_EYES { { vr::Eye_Left, vr::Eye_Right } }; bool _openVrDisplayActive { false }; @@ -59,16 +61,14 @@ bool OpenVrDisplayPlugin::internalActivate() { // left + right eyes _renderTargetSize.x *= 2; - { - Lock lock(_poseMutex); + withRenderThreadLock([&] { openvr_for_each_eye([&](vr::Hmd_Eye eye) { _eyeOffsets[eye] = toGlm(_system->GetEyeToHeadTransform(eye)); _eyeProjections[eye] = toGlm(_system->GetProjectionMatrix(eye, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, vr::API_OpenGL)); }); // FIXME Calculate the proper combined projection by using GetProjectionRaw values from both eyes _cullingProjection = _eyeProjections[0]; - - } + }); _compositor = vr::VRCompositor(); Q_ASSERT(_compositor); @@ -113,7 +113,7 @@ void OpenVrDisplayPlugin::internalDeactivate() { void OpenVrDisplayPlugin::customizeContext() { // Display plugins in DLLs must initialize glew locally static std::once_flag once; - std::call_once(once, []{ + std::call_once(once, [] { glewExperimental = true; GLenum err = glewInit(); glGetError(); // clear the potential error from glewExperimental @@ -123,9 +123,10 @@ void OpenVrDisplayPlugin::customizeContext() { } void OpenVrDisplayPlugin::resetSensors() { - Lock lock(_poseMutex); - glm::mat4 m = toGlm(_trackedDevicePose[0].mDeviceToAbsoluteTracking); - _sensorResetMat = glm::inverse(cancelOutRollAndPitch(m)); + withRenderThreadLock([&] { + glm::mat4 m = toGlm(_trackedDevicePose[0].mDeviceToAbsoluteTracking); + _sensorResetMat = glm::inverse(cancelOutRollAndPitch(m)); + }); } @@ -150,6 +151,24 @@ bool OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { _system->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseStanding, _currentRenderFrameInfo.predictedDisplayTime, _trackedDevicePose, vr::k_unMaxTrackedDeviceCount); + + vr::TrackedDeviceIndex_t handIndices[2] { vr::k_unTrackedDeviceIndexInvalid, vr::k_unTrackedDeviceIndexInvalid }; + { + vr::TrackedDeviceIndex_t controllerIndices[2] ; + auto trackedCount = _system->GetSortedTrackedDeviceIndicesOfClass(vr::TrackedDeviceClass_Controller, controllerIndices, 2); + // Find the left and right hand controllers, if they exist + for (uint32_t i = 0; i < std::min(trackedCount, 2); ++i) { + if (_trackedDevicePose[i].bPoseIsValid) { + auto role = _system->GetControllerRoleForTrackedDeviceIndex(controllerIndices[i]); + if (vr::TrackedControllerRole_LeftHand == role) { + handIndices[0] = controllerIndices[i]; + } else if (vr::TrackedControllerRole_RightHand == role) { + handIndices[1] = controllerIndices[i]; + } + } + } + } + // copy and process predictedTrackedDevicePoses for (int i = 0; i < vr::k_unMaxTrackedDeviceCount; i++) { _trackedDevicePoseMat4[i] = _sensorResetMat * toGlm(_trackedDevicePose[i].mDeviceToAbsoluteTracking); @@ -159,18 +178,27 @@ bool OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { _currentRenderFrameInfo.rawRenderPose = toGlm(_trackedDevicePose[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking); _currentRenderFrameInfo.renderPose = _trackedDevicePoseMat4[vr::k_unTrackedDeviceIndex_Hmd]; - Lock lock(_mutex); - _frameInfos[frameIndex] = _currentRenderFrameInfo; + bool keyboardVisible = isOpenVrKeyboardShown(); + withRenderThreadLock([&] { + // Make controller poses available to the presentation thread + for (int i = 0; i < 2; ++i) { + if (keyboardVisible || handIndices[i] == vr::k_unTrackedDeviceIndexInvalid) { + _handPoses[i] = glm::mat4(); + } else { + _handPoses[i] = _sensorResetMat * toGlm(_trackedDevicePose[handIndices[i]].mDeviceToAbsoluteTracking); + } + } + _frameInfos[frameIndex] = _currentRenderFrameInfo; + }); return true; } void OpenVrDisplayPlugin::hmdPresent() { - PROFILE_RANGE_EX(__FUNCTION__, 0xff00ff00, (uint64_t)_currentPresentFrameIndex) // Flip y-axis since GL UV coords are backwards. - static vr::VRTextureBounds_t leftBounds{ 0, 0, 0.5f, 1 }; - static vr::VRTextureBounds_t rightBounds{ 0.5f, 0, 1, 1 }; + static vr::VRTextureBounds_t leftBounds { 0, 0, 0.5f, 1 }; + static vr::VRTextureBounds_t rightBounds { 0.5f, 0, 1, 1 }; vr::Texture_t texture { (void*)oglplus::GetName(_compositeFramebuffer->color), vr::API_OpenGL, vr::ColorSpace_Auto }; @@ -191,6 +219,10 @@ bool OpenVrDisplayPlugin::isHmdMounted() const { } void OpenVrDisplayPlugin::updatePresentPose() { + mat4 sensorResetMat; + withPresentThreadLock([&] { + sensorResetMat = _sensorResetMat; + }); { float fSecondsSinceLastVsync; _system->GetTimeSinceLastVsync(&fSecondsSinceLastVsync, nullptr); @@ -202,9 +234,8 @@ void OpenVrDisplayPlugin::updatePresentPose() { _system->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseStanding, fPredictedSecondsFromNow, &pose, 1); _currentPresentFrameInfo.rawPresentPose = toGlm(pose.mDeviceToAbsoluteTracking); } - _currentPresentFrameInfo.presentPose = _sensorResetMat * _currentPresentFrameInfo.rawPresentPose; + _currentPresentFrameInfo.presentPose = sensorResetMat * _currentPresentFrameInfo.rawPresentPose; mat3 renderRotation(_currentPresentFrameInfo.rawRenderPose); mat3 presentRotation(_currentPresentFrameInfo.rawPresentPose); _currentPresentFrameInfo.presentReprojection = glm::mat3(glm::inverse(renderRotation) * presentRotation); } - diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.h b/plugins/openvr/src/OpenVrDisplayPlugin.h index fda5e37c2a..ee693b8091 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.h +++ b/plugins/openvr/src/OpenVrDisplayPlugin.h @@ -43,5 +43,4 @@ private: vr::IVRSystem* _system { nullptr }; std::atomic _hmdActivityLevel { vr::k_EDeviceActivityLevel_Unknown }; static const QString NAME; - mutable Mutex _poseMutex; }; diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index 1ff1c65ef8..e4cca6ecd6 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -208,6 +208,10 @@ void disableOpenVrKeyboard() { QObject::disconnect(_focusConnection); } +bool isOpenVrKeyboardShown() { + return _keyboardShown; +} + void handleOpenVrEvents() { if (!activeHmd) { diff --git a/plugins/openvr/src/OpenVrHelpers.h b/plugins/openvr/src/OpenVrHelpers.h index 426178cd65..db8bb4f2e8 100644 --- a/plugins/openvr/src/OpenVrHelpers.h +++ b/plugins/openvr/src/OpenVrHelpers.h @@ -20,6 +20,7 @@ void handleOpenVrEvents(); bool openVrQuitRequested(); void enableOpenVrKeyboard(); void disableOpenVrKeyboard(); +bool isOpenVrKeyboardShown(); template From bb3722d91516d120a6dc14592dc93ef330109efd Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 10 Jun 2016 01:36:25 -0700 Subject: [PATCH 036/171] trying again to fix mac build. --- plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp index 29a2a4bb1a..8d2bc24177 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp @@ -47,7 +47,7 @@ bool OculusLegacyDisplayPlugin::beginFrameRender(uint32_t frameIndex) { _currentRenderFrameInfo.rawRenderPose = _currentRenderFrameInfo.renderPose = toGlm(_trackingState.HeadPose.ThePose); withRenderThreadLock([&]{ _frameInfos[frameIndex] = _currentRenderFrameInfo; - }) + }); return true; } From 130c0dda318335d70e1d26de6625fb1bc46112ee Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 10 Jun 2016 10:09:09 -0700 Subject: [PATCH 037/171] PR feedback --- .../display-plugins/src/display-plugins/OpenGLDisplayPlugin.h | 2 +- .../src/display-plugins/hmd/HmdDisplayPlugin.cpp | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index ec8aa45840..02a30a2570 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -152,7 +152,7 @@ protected: } private: - // Any resource shared by the main thread and the presntaion thread must + // Any resource shared by the main thread and the presentation thread must // be serialized through this mutex mutable Mutex _presentMutex; ProgramPtr _activeProgram; diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index ce1a42971e..9b71d3703b 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -408,9 +408,11 @@ bool HmdDisplayPlugin::setHandLaser(uint32_t hands, HandLaserMode mode, const ve } }); // FIXME defer to a child class plugin to determine if hand lasers are actually + // available based on the presence or absence of hand controllers return true; } +// FIXME try to consolidate the duplication of logic between this function and a similar one in CompsitorHelper. static float calculateRayUiCollisionDistance(const glm::mat4& headPose, const glm::vec3& position, const glm::vec3& direction) { auto relativePosition4 = glm::inverse(headPose) * vec4(position, 1); auto relativePosition = vec3(relativePosition4) / relativePosition4.w; @@ -418,6 +420,7 @@ static float calculateRayUiCollisionDistance(const glm::mat4& headPose, const gl if (glm::abs(glm::length2(relativeDirection) - 1.0f) > EPSILON) { relativeDirection = glm::normalize(relativeDirection); } + // FIXME fetch the actual UI radius from... somewhere? float uiRadius = 1.0f; float instersectionDistance; if (!glm::intersectRaySphere(relativePosition, relativeDirection, vec3(0), uiRadius * uiRadius, instersectionDistance)) { From 62051ad297056a5fd0e07966c9ea0cc108f6f3f4 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Mon, 13 Jun 2016 14:40:15 -0700 Subject: [PATCH 038/171] Working on touch --- .../controllers/src/controllers/Forward.h | 1 + .../oculus/src/OculusBaseDisplayPlugin.cpp | 10 +++ .../oculus/src/OculusControllerManager.cpp | 82 +---------------- plugins/oculus/src/OculusHelpers.cpp | 88 +++++++++++++++++++ plugins/oculus/src/OculusHelpers.h | 5 ++ 5 files changed, 106 insertions(+), 80 deletions(-) diff --git a/libraries/controllers/src/controllers/Forward.h b/libraries/controllers/src/controllers/Forward.h index e1a62556d4..23dd162831 100644 --- a/libraries/controllers/src/controllers/Forward.h +++ b/libraries/controllers/src/controllers/Forward.h @@ -32,6 +32,7 @@ class Mapping; using MappingPointer = std::shared_ptr; using MappingList = std::list; +struct Pose; } #endif diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp index f5fdbd303c..a16f630bf8 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp @@ -24,7 +24,17 @@ bool OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) { auto trackingState = ovr_GetTrackingState(_session, _currentRenderFrameInfo.predictedDisplayTime, ovrTrue); _currentRenderFrameInfo.renderPose = toGlm(trackingState.HeadPose.ThePose); _currentRenderFrameInfo.presentPose = _currentRenderFrameInfo.renderPose; + withRenderThreadLock([&] { + // Make controller poses available to the presentation thread + ovr_for_each_hand([&](ovrHandType hand){ + static const auto REQUIRED_HAND_STATUS = ovrStatus_OrientationTracked & ovrStatus_PositionTracked; + if (REQUIRED_HAND_STATUS == (trackingState.HandStatusFlags[hand] & REQUIRED_HAND_STATUS)) { + _handPoses[hand] = toGlm(trackingState.HandPoses[hand].ThePose); + } else { + _handPoses[hand] = glm::mat4(); + } + }); _frameInfos[frameIndex] = _currentRenderFrameInfo; }); return true; diff --git a/plugins/oculus/src/OculusControllerManager.cpp b/plugins/oculus/src/OculusControllerManager.cpp index 9f0e76363b..0e9ca21804 100644 --- a/plugins/oculus/src/OculusControllerManager.cpp +++ b/plugins/oculus/src/OculusControllerManager.cpp @@ -243,91 +243,13 @@ void OculusControllerManager::TouchDevice::focusOutEvent() { void OculusControllerManager::TouchDevice::handlePose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, ovrHandType hand, const ovrPoseStatef& handPose) { - // When the sensor-to-world rotation is identity the coordinate axes look like this: - // - // user - // forward - // -z - // | - // y| user - // y o----x right - // o-----x user - // | up - // | - // z - // - // Rift - - // From ABOVE the hand canonical axes looks like this: - // - // | | | | y | | | | - // | | | | | | | | | - // | | | | | - // |left | / x---- + \ |right| - // | _/ z \_ | - // | | | | - // | | | | - // - - // So when the user is in Rift space facing the -zAxis with hands outstretched and palms down - // the rotation to align the Touch axes with those of the hands is: - // - // touchToHand = halfTurnAboutY * quaterTurnAboutX - - // Due to how the Touch controllers fit into the palm there is an offset that is different for each hand. - // You can think of this offset as the inverse of the measured rotation when the hands are posed, such that - // the combination (measurement * offset) is identity at this orientation. - // - // Qoffset = glm::inverse(deltaRotation when hand is posed fingers forward, palm down) - // - // An approximate offset for the Touch can be obtained by inspection: - // - // Qoffset = glm::inverse(glm::angleAxis(sign * PI/2.0f, zAxis) * glm::angleAxis(PI/4.0f, xAxis)) - // - // So the full equation is: - // - // Q = combinedMeasurement * touchToHand - // - // Q = (deltaQ * QOffset) * (yFlip * quarterTurnAboutX) - // - // Q = (deltaQ * inverse(deltaQForAlignedHand)) * (yFlip * quarterTurnAboutX) - auto poseId = hand == ovrHand_Left ? controller::LEFT_HAND : controller::RIGHT_HAND; auto& pose = _poseStateMap[poseId]; - - static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y); - static const glm::quat quarterX = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_X); - static const glm::quat touchToHand = yFlip * quarterX; - - static const glm::quat leftQuarterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z); - static const glm::quat rightQuarterZ = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_Z); - static const glm::quat eighthX = glm::angleAxis(PI / 4.0f, Vectors::UNIT_X); - - static const glm::quat leftRotationOffset = glm::inverse(leftQuarterZ * eighthX) * touchToHand; - static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ * eighthX) * touchToHand; - - static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches - static const glm::vec3 CONTROLLER_OFFSET = glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f, - CONTROLLER_LENGTH_OFFSET / 2.0f, - CONTROLLER_LENGTH_OFFSET * 2.0f); - static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET; - static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET; - - auto translationOffset = (hand == ovrHand_Left ? leftTranslationOffset : rightTranslationOffset); - auto rotationOffset = (hand == ovrHand_Left ? leftRotationOffset : rightRotationOffset); - - glm::quat rotation = toGlm(handPose.ThePose.Orientation); - - pose.translation = toGlm(handPose.ThePose.Position); - pose.translation += rotation * translationOffset; - pose.rotation = rotation * rotationOffset; - pose.angularVelocity = toGlm(handPose.AngularVelocity); - pose.velocity = toGlm(handPose.LinearVelocity); - pose.valid = true; - + pose = ovrControllerPoseToHandPose(hand, handPose); // transform into avatar frame glm::mat4 controllerToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat; pose = pose.transform(controllerToAvatar); + } bool OculusControllerManager::TouchDevice::triggerHapticPulse(float strength, float duration, controller::Hand hand) { diff --git a/plugins/oculus/src/OculusHelpers.cpp b/plugins/oculus/src/OculusHelpers.cpp index 6ddace684b..705c0a0781 100644 --- a/plugins/oculus/src/OculusHelpers.cpp +++ b/plugins/oculus/src/OculusHelpers.cpp @@ -15,6 +15,9 @@ #include #include +#include +#include + using Mutex = std::mutex; using Lock = std::unique_lock; @@ -191,3 +194,88 @@ void SwapFramebufferWrapper::onBind(oglplus::Framebuffer::Target target) { void SwapFramebufferWrapper::onUnbind(oglplus::Framebuffer::Target target) { glFramebufferTexture2D(toEnum(target), GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); } + + +controller::Pose ovrControllerPoseToHandPose( + ovrHandType hand, + const ovrPoseStatef& handPose) { + // When the sensor-to-world rotation is identity the coordinate axes look like this: + // + // user + // forward + // -z + // | + // y| user + // y o----x right + // o-----x user + // | up + // | + // z + // + // Rift + + // From ABOVE the hand canonical axes looks like this: + // + // | | | | y | | | | + // | | | | | | | | | + // | | | | | + // |left | / x---- + \ |right| + // | _/ z \_ | + // | | | | + // | | | | + // + + // So when the user is in Rift space facing the -zAxis with hands outstretched and palms down + // the rotation to align the Touch axes with those of the hands is: + // + // touchToHand = halfTurnAboutY * quaterTurnAboutX + + // Due to how the Touch controllers fit into the palm there is an offset that is different for each hand. + // You can think of this offset as the inverse of the measured rotation when the hands are posed, such that + // the combination (measurement * offset) is identity at this orientation. + // + // Qoffset = glm::inverse(deltaRotation when hand is posed fingers forward, palm down) + // + // An approximate offset for the Touch can be obtained by inspection: + // + // Qoffset = glm::inverse(glm::angleAxis(sign * PI/2.0f, zAxis) * glm::angleAxis(PI/4.0f, xAxis)) + // + // So the full equation is: + // + // Q = combinedMeasurement * touchToHand + // + // Q = (deltaQ * QOffset) * (yFlip * quarterTurnAboutX) + // + // Q = (deltaQ * inverse(deltaQForAlignedHand)) * (yFlip * quarterTurnAboutX) + static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y); + static const glm::quat quarterX = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_X); + static const glm::quat touchToHand = yFlip * quarterX; + + static const glm::quat leftQuarterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z); + static const glm::quat rightQuarterZ = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_Z); + static const glm::quat eighthX = glm::angleAxis(PI / 4.0f, Vectors::UNIT_X); + + static const glm::quat leftRotationOffset = glm::inverse(leftQuarterZ * eighthX) * touchToHand; + static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ * eighthX) * touchToHand; + + static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches + static const glm::vec3 CONTROLLER_OFFSET = glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f, + CONTROLLER_LENGTH_OFFSET / 2.0f, + CONTROLLER_LENGTH_OFFSET * 2.0f); + static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET; + static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET; + + auto translationOffset = (hand == ovrHand_Left ? leftTranslationOffset : rightTranslationOffset); + auto rotationOffset = (hand == ovrHand_Left ? leftRotationOffset : rightRotationOffset); + + glm::quat rotation = toGlm(handPose.ThePose.Orientation); + + controller::Pose pose; + pose.translation = toGlm(handPose.ThePose.Position); + pose.translation += rotation * translationOffset; + pose.rotation = rotation * rotationOffset; + pose.angularVelocity = toGlm(handPose.AngularVelocity); + pose.velocity = toGlm(handPose.LinearVelocity); + pose.valid = true; + return pose; +} \ No newline at end of file diff --git a/plugins/oculus/src/OculusHelpers.h b/plugins/oculus/src/OculusHelpers.h index 2f13c45466..66cdccf15a 100644 --- a/plugins/oculus/src/OculusHelpers.h +++ b/plugins/oculus/src/OculusHelpers.h @@ -13,6 +13,7 @@ #include #include +#include void logWarning(const char* what); void logFatal(const char* what); @@ -128,3 +129,7 @@ protected: private: ovrSession _session; }; + +controller::Pose ovrControllerPoseToHandPose( + ovrHandType hand, + const ovrPoseStatef& handPose); From 6d2181f0f137d8789efabb341bb5b64053edbcd1 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 13 Jun 2016 11:59:38 -0700 Subject: [PATCH 039/171] Combine DISTANCE_HOLDING and CONTINUE_DISTANCE_HOLDING states. --- .../system/controllers/handControllerGrab.js | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 25cd100991..f4bcefe3ac 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -157,20 +157,19 @@ var STATE_OFF = 0; var STATE_SEARCHING = 1; var STATE_HOLD_SEARCHING = 2; var STATE_DISTANCE_HOLDING = 3; -var STATE_CONTINUE_DISTANCE_HOLDING = 4; -var STATE_NEAR_GRABBING = 5; -var STATE_CONTINUE_NEAR_GRABBING = 6; -var STATE_NEAR_TRIGGER = 7; -var STATE_CONTINUE_NEAR_TRIGGER = 8; -var STATE_FAR_TRIGGER = 9; -var STATE_CONTINUE_FAR_TRIGGER = 10; -var STATE_RELEASE = 11; -var STATE_EQUIP = 12; -var STATE_HOLD = 13; -var STATE_CONTINUE_HOLD = 14; -var STATE_CONTINUE_EQUIP = 15; -var STATE_WAITING_FOR_RELEASE_THUMB_RELEASE = 16; -var STATE_WAITING_FOR_EQUIP_THUMB_RELEASE = 17; +var STATE_NEAR_GRABBING = 4; +var STATE_CONTINUE_NEAR_GRABBING = 5; +var STATE_NEAR_TRIGGER = 6; +var STATE_CONTINUE_NEAR_TRIGGER = 7; +var STATE_FAR_TRIGGER = 8; +var STATE_CONTINUE_FAR_TRIGGER = 9; +var STATE_RELEASE = 10; +var STATE_EQUIP = 11; +var STATE_HOLD = 12; +var STATE_CONTINUE_HOLD = 13; +var STATE_CONTINUE_EQUIP = 14; +var STATE_WAITING_FOR_RELEASE_THUMB_RELEASE = 15; +var STATE_WAITING_FOR_EQUIP_THUMB_RELEASE = 16; // "collidesWith" is specified by comma-separated list of group names // the possible group names are: static, dynamic, kinematic, myAvatar, otherAvatar @@ -198,12 +197,9 @@ CONTROLLER_STATE_MACHINE[STATE_HOLD_SEARCHING] = { }; CONTROLLER_STATE_MACHINE[STATE_DISTANCE_HOLDING] = { name: "distance_holding", + enterMethod: "distanceHoldingEnter", updateMethod: "distanceHolding" }; -CONTROLLER_STATE_MACHINE[STATE_CONTINUE_DISTANCE_HOLDING] = { - name: "continue_distance_holding", - updateMethod: "continueDistanceHolding" -}; CONTROLLER_STATE_MACHINE[STATE_NEAR_GRABBING] = { name: "near_grabbing", updateMethod: "nearGrabbing" @@ -1207,7 +1203,7 @@ function MyController(hand) { return (dimensions.x * dimensions.y * dimensions.z) * density; } - this.distanceHolding = function() { + this.distanceHoldingEnter = function() { // controller pose is in avatar frame var avatarControllerPose = @@ -1256,9 +1252,12 @@ function MyController(hand) { this.actionTimeout = now + (ACTION_TTL * MSECS_PER_SEC); if (this.actionID !== null) { - this.setState(STATE_CONTINUE_DISTANCE_HOLDING); this.activateEntity(this.grabbedEntity, grabbedProperties, false); this.callEntityMethodOnGrabbed("startDistanceGrab"); + } else { + // addAction failed? + this.setState(STATE_RELEASE); + return; } this.turnOffVisualizations(); @@ -1267,7 +1266,7 @@ function MyController(hand) { this.previousControllerRotation = controllerRotation; }; - this.continueDistanceHolding = function() { + this.distanceHolding = function() { if (this.triggerSmoothedReleased() && this.secondaryReleased()) { this.setState(STATE_RELEASE); this.callEntityMethodOnGrabbed("releaseGrab"); From bd3326d2fc45c66da5e403de99d4d1f905c8539c Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 13 Jun 2016 13:59:10 -0700 Subject: [PATCH 040/171] Collapse continueNearGrabbing and nearGrabbing into a single state. * Moved the update logic from STATE_NEAR_GRABBING, STATE_HOLD & STATE_EQUIP into the entryMethods for those states. * Removed STATE_CONTINUE_NEAR_GRABBING, STATE_CONTINUE_HOLD & STATE_CONTINUE_EQUIP states This functionality has been moved into the updateMethod for their respective states. This *should* be a pure re-factor no functionality was changed. --- .../system/controllers/handControllerGrab.js | 76 +++++-------------- 1 file changed, 19 insertions(+), 57 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index f4bcefe3ac..3e611b9f00 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -19,7 +19,7 @@ Script.include("/~/system/libraries/utils.js"); // add lines where the hand ray picking is happening // var WANT_DEBUG = false; -var WANT_DEBUG_STATE = false; +var WANT_DEBUG_STATE = true; var WANT_DEBUG_SEARCH_NAME = null; // @@ -158,7 +158,6 @@ var STATE_SEARCHING = 1; var STATE_HOLD_SEARCHING = 2; var STATE_DISTANCE_HOLDING = 3; var STATE_NEAR_GRABBING = 4; -var STATE_CONTINUE_NEAR_GRABBING = 5; var STATE_NEAR_TRIGGER = 6; var STATE_CONTINUE_NEAR_TRIGGER = 7; var STATE_FAR_TRIGGER = 8; @@ -166,8 +165,6 @@ var STATE_CONTINUE_FAR_TRIGGER = 9; var STATE_RELEASE = 10; var STATE_EQUIP = 11; var STATE_HOLD = 12; -var STATE_CONTINUE_HOLD = 13; -var STATE_CONTINUE_EQUIP = 14; var STATE_WAITING_FOR_RELEASE_THUMB_RELEASE = 15; var STATE_WAITING_FOR_EQUIP_THUMB_RELEASE = 16; @@ -202,27 +199,18 @@ CONTROLLER_STATE_MACHINE[STATE_DISTANCE_HOLDING] = { }; CONTROLLER_STATE_MACHINE[STATE_NEAR_GRABBING] = { name: "near_grabbing", + enterMethod: "nearGrabbingEnter", updateMethod: "nearGrabbing" }; CONTROLLER_STATE_MACHINE[STATE_EQUIP] = { name: "equip", + enterMethod: "nearGrabbingEnter", updateMethod: "nearGrabbing" }; CONTROLLER_STATE_MACHINE[STATE_HOLD] = { - name: "hold", - updateMethod: "nearGrabbing" -}; -CONTROLLER_STATE_MACHINE[STATE_CONTINUE_NEAR_GRABBING] = { - name: "continue_near_grabbing", - updateMethod: "continueNearGrabbing" -}; -CONTROLLER_STATE_MACHINE[STATE_CONTINUE_HOLD] = { name: "continue_hold", - updateMethod: "continueNearGrabbing" -}; -CONTROLLER_STATE_MACHINE[STATE_CONTINUE_EQUIP] = { - name: "continue_equip", - updateMethod: "continueNearGrabbing" + enterMethod: "nearGrabbingEnter", + updateMethod: "nearGrabbing" }; CONTROLLER_STATE_MACHINE[STATE_NEAR_TRIGGER] = { name: "near_trigger", @@ -400,6 +388,8 @@ function MyController(hand) { print("WARNING: could not find state " + this.state + " in state machine"); } + this.state = newState; + // enter the new state if (CONTROLLER_STATE_MACHINE[newState]) { var enterMethodName = CONTROLLER_STATE_MACHINE[newState].enterMethod; @@ -410,8 +400,6 @@ function MyController(hand) { } else { print("WARNING: could not find newState " + newState + " in state machine"); } - - this.state = newState; }; this.debugLine = function(closePoint, farPoint, color) { @@ -1254,10 +1242,6 @@ function MyController(hand) { if (this.actionID !== null) { this.activateEntity(this.grabbedEntity, grabbedProperties, false); this.callEntityMethodOnGrabbed("startDistanceGrab"); - } else { - // addAction failed? - this.setState(STATE_RELEASE); - return; } this.turnOffVisualizations(); @@ -1483,20 +1467,9 @@ function MyController(hand) { } } - this.nearGrabbing = function() { + this.nearGrabbingEnter = function() { var now = Date.now(); - if (this.state == STATE_NEAR_GRABBING && this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - this.callEntityMethodOnGrabbed("releaseGrab"); - return; - } - if (this.state == STATE_HOLD && this.secondaryReleased()) { - this.setState(STATE_RELEASE); - this.callEntityMethodOnGrabbed("releaseGrab"); - return; - } - this.lineOff(); this.overlayLineOff(); @@ -1578,17 +1551,6 @@ function MyController(hand) { this.callEntityMethodOnGrabbed("startEquip"); } - if (this.state == STATE_NEAR_GRABBING) { - // near grabbing - this.setState(STATE_CONTINUE_NEAR_GRABBING); - } else if (this.state == STATE_HOLD) { - // holding - this.setState(STATE_CONTINUE_HOLD); - } else { // (this.state == STATE_EQUIP) - // equipping - this.setState(STATE_CONTINUE_EQUIP); - } - this.currentHandControllerTipPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandTipPosition : MyAvatar.leftHandTipPosition; this.currentObjectTime = Date.now(); @@ -1599,29 +1561,29 @@ function MyController(hand) { this.currentAngularVelocity = ZERO_VEC; }; - this.continueNearGrabbing = function() { - if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.triggerSmoothedReleased()) { + this.nearGrabbing = function() { + if (this.state == STATE_NEAR_GRABBING && this.triggerSmoothedReleased()) { this.setState(STATE_RELEASE); this.callEntityMethodOnGrabbed("releaseGrab"); return; } - if (this.state == STATE_CONTINUE_HOLD && this.secondaryReleased()) { + if (this.state == STATE_HOLD && this.secondaryReleased()) { this.setState(STATE_RELEASE); this.callEntityMethodOnGrabbed("releaseEquip"); return; } - if (this.state == STATE_CONTINUE_EQUIP && this.thumbPressed()) { + if (this.state == STATE_EQUIP && this.thumbPressed()) { this.setState(STATE_WAITING_FOR_RELEASE_THUMB_RELEASE); this.callEntityMethodOnGrabbed("releaseEquip"); return; } - if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.thumbPressed()) { + if (this.state == STATE_NEAR_GRABBING && this.thumbPressed()) { this.setState(STATE_WAITING_FOR_EQUIP_THUMB_RELEASE); this.callEntityMethodOnGrabbed("releaseGrab"); this.callEntityMethodOnGrabbed("startEquip"); return; } - if (this.state == STATE_CONTINUE_HOLD && this.thumbPressed()) { + if (this.state == STATE_HOLD && this.thumbPressed()) { this.setState(STATE_WAITING_FOR_EQUIP_THUMB_RELEASE); return; } @@ -1652,9 +1614,9 @@ function MyController(hand) { print("handControllerGrab -- autoreleasing held or equipped item because it is far from hand." + props.parentID + " " + vec3toStr(props.position)); this.setState(STATE_RELEASE); - if (this.state == STATE_CONTINUE_NEAR_GRABBING) { + if (this.state == STATE_NEAR_GRABBING) { this.callEntityMethodOnGrabbed("releaseGrab"); - } else { // (this.state == STATE_CONTINUE_EQUIP || this.state == STATE_CONTINUE_HOLD) + } else { // (this.state == STATE_EQUIP || this.state == STATE_HOLD) this.callEntityMethodOnGrabbed("releaseEquip"); } return; @@ -1693,10 +1655,10 @@ function MyController(hand) { this.currentObjectTime = now; var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, {}); - if (this.state === STATE_CONTINUE_EQUIP || this.state === STATE_CONTINUE_HOLD) { + if (this.state === STATE_EQUIP || this.state === STATE_HOLD) { this.callEntityMethodOnGrabbed("continueEquip"); } - if (this.state == STATE_CONTINUE_NEAR_GRABBING) { + if (this.state == STATE_NEAR_GRABBING) { this.callEntityMethodOnGrabbed("continueNearGrab"); } @@ -2029,7 +1991,7 @@ function MyController(hand) { this.activateEntity(this.grabbedEntity, loadedProps, true); this.isInitialGrab = true; this.callEntityMethodOnGrabbed("startEquip"); - this.setState(STATE_CONTINUE_EQUIP); + this.setState(STATE_EQUIP); } } }; From fe65df350bc23282cd5d3260a9de69dafba6efb1 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 13 Jun 2016 15:05:34 -0700 Subject: [PATCH 041/171] Combined the release and off states. --- .../system/controllers/handControllerGrab.js | 35 ++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 3e611b9f00..386a4905f1 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -162,7 +162,6 @@ var STATE_NEAR_TRIGGER = 6; var STATE_CONTINUE_NEAR_TRIGGER = 7; var STATE_FAR_TRIGGER = 8; var STATE_CONTINUE_FAR_TRIGGER = 9; -var STATE_RELEASE = 10; var STATE_EQUIP = 11; var STATE_HOLD = 12; var STATE_WAITING_FOR_RELEASE_THUMB_RELEASE = 15; @@ -180,6 +179,7 @@ var CONTROLLER_STATE_MACHINE = {}; CONTROLLER_STATE_MACHINE[STATE_OFF] = { name: "off", + enterMethod: "offEnter", updateMethod: "off" }; CONTROLLER_STATE_MACHINE[STATE_SEARCHING] = { @@ -228,10 +228,6 @@ CONTROLLER_STATE_MACHINE[STATE_CONTINUE_FAR_TRIGGER] = { name: "continue_far_trigger", updateMethod: "continueFarTrigger" }; -CONTROLLER_STATE_MACHINE[STATE_RELEASE] = { - name: "release", - updateMethod: "release" -}; CONTROLLER_STATE_MACHINE[STATE_WAITING_FOR_EQUIP_THUMB_RELEASE] = { name: "waiting_for_equip_thumb_release", updateMethod: "waitingForEquipThumbRelease" @@ -918,12 +914,12 @@ function MyController(hand) { this.checkForStrayChildren(); if (this.state == STATE_SEARCHING && this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); + this.setState(STATE_OFF); return; } if (this.state == STATE_HOLD_SEARCHING && this.secondaryReleased()) { - this.setState(STATE_RELEASE); + this.setState(STATE_OFF); return; } @@ -1252,7 +1248,7 @@ function MyController(hand) { this.distanceHolding = function() { if (this.triggerSmoothedReleased() && this.secondaryReleased()) { - this.setState(STATE_RELEASE); + this.setState(STATE_OFF); this.callEntityMethodOnGrabbed("releaseGrab"); return; } @@ -1563,12 +1559,12 @@ function MyController(hand) { this.nearGrabbing = function() { if (this.state == STATE_NEAR_GRABBING && this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); + this.setState(STATE_OFF); this.callEntityMethodOnGrabbed("releaseGrab"); return; } if (this.state == STATE_HOLD && this.secondaryReleased()) { - this.setState(STATE_RELEASE); + this.setState(STATE_OFF); this.callEntityMethodOnGrabbed("releaseEquip"); return; } @@ -1593,7 +1589,7 @@ function MyController(hand) { var props = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "parentID", "position", "rotation"]); if (!props.position) { // server may have reset, taking our equipped entity with it. move back to "off" stte - this.setState(STATE_RELEASE); + this.setState(STATE_OFF); this.callEntityMethodOnGrabbed("releaseGrab"); return; } @@ -1613,7 +1609,7 @@ function MyController(hand) { // for whatever reason, the held/equipped entity has been pulled away. ungrab or unequip. print("handControllerGrab -- autoreleasing held or equipped item because it is far from hand." + props.parentID + " " + vec3toStr(props.position)); - this.setState(STATE_RELEASE); + this.setState(STATE_OFF); if (this.state == STATE_NEAR_GRABBING) { this.callEntityMethodOnGrabbed("releaseGrab"); } else { // (this.state == STATE_EQUIP || this.state == STATE_HOLD) @@ -1691,13 +1687,13 @@ function MyController(hand) { }; this.waitingForReleaseThumbRelease = function() { if (this.thumbReleased() && this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); + this.setState(STATE_OFF); } }; this.nearTrigger = function() { if (this.triggerSmoothedReleased() && this.secondaryReleased()) { - this.setState(STATE_RELEASE); + this.setState(STATE_OFF); this.callEntityMethodOnGrabbed("stopNearTrigger"); return; } @@ -1707,7 +1703,7 @@ function MyController(hand) { this.farTrigger = function() { if (this.triggerSmoothedReleased() && this.secondaryReleased()) { - this.setState(STATE_RELEASE); + this.setState(STATE_OFF); this.callEntityMethodOnGrabbed("stopFarTrigger"); return; } @@ -1717,7 +1713,7 @@ function MyController(hand) { this.continueNearTrigger = function() { if (this.triggerSmoothedReleased() && this.secondaryReleased()) { - this.setState(STATE_RELEASE); + this.setState(STATE_OFF); this.callEntityMethodOnGrabbed("stopNearTrigger"); return; } @@ -1726,7 +1722,7 @@ function MyController(hand) { this.continueFarTrigger = function() { if (this.triggerSmoothedReleased() && this.secondaryReleased()) { - this.setState(STATE_RELEASE); + this.setState(STATE_OFF); this.callEntityMethodOnGrabbed("stopFarTrigger"); return; } @@ -1743,7 +1739,7 @@ function MyController(hand) { if (intersection.accurate) { this.lastPickTime = now; if (intersection.entityID != this.grabbedEntity) { - this.setState(STATE_RELEASE); + this.setState(STATE_OFF); this.callEntityMethodOnGrabbed("stopFarTrigger"); return; } @@ -1757,7 +1753,7 @@ function MyController(hand) { this.callEntityMethodOnGrabbed("continueFarTrigger"); }; - this.release = function() { + this.offEnter = function() { this.turnLightsOff(); this.turnOffVisualizations(); @@ -1780,7 +1776,6 @@ function MyController(hand) { this.deactivateEntity(this.grabbedEntity, noVelocity); this.actionID = null; - this.setState(STATE_OFF); Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ action: 'release', From b9bf7977535e4780bf99cb14f97d4413927bf8b5 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Mon, 13 Jun 2016 15:26:48 -0700 Subject: [PATCH 042/171] Fixing issues with unclosed groups in settings persistence --- libraries/networking/src/AccountManager.cpp | 1 + libraries/script-engine/src/ScriptEngines.cpp | 1 + libraries/shared/src/SettingHandle.cpp | 9 +++++++++ libraries/shared/src/SettingHandle.h | 2 ++ libraries/shared/src/SettingInterface.cpp | 2 ++ 5 files changed, 15 insertions(+) diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index bac031885f..26b3801ec1 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -173,6 +173,7 @@ void AccountManager::setAuthURL(const QUrl& authURL) { << "from previous settings file"; } } + settings.endGroup(); if (_accountInfo.getAccessToken().token.isEmpty()) { qCWarning(networking) << "Unable to load account file. No existing account settings will be loaded."; diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index 29c223f4b3..beddc21787 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -334,6 +334,7 @@ void ScriptEngines::clearScripts() { Settings settings; settings.beginWriteArray(SETTINGS_KEY); settings.remove(""); + settings.endArray(); } void ScriptEngines::saveScripts() { diff --git a/libraries/shared/src/SettingHandle.cpp b/libraries/shared/src/SettingHandle.cpp index b2f23f5a04..13f9ea48ce 100644 --- a/libraries/shared/src/SettingHandle.cpp +++ b/libraries/shared/src/SettingHandle.cpp @@ -18,6 +18,7 @@ const QString Settings::firstRun { "firstRun" }; + Settings::Settings() : _manager(DependencyManager::get()), _locker(&(_manager->getLock())) @@ -25,6 +26,9 @@ Settings::Settings() : } Settings::~Settings() { + if (_prefixes.size() != 0) { + qFatal("Unstable Settings Prefixes: You must call endGroup for every beginGroup and endArray for every begin*Array call"); + } } void Settings::remove(const QString& key) { @@ -50,14 +54,17 @@ bool Settings::contains(const QString& key) const { } int Settings::beginReadArray(const QString & prefix) { + _prefixes.push(prefix); return _manager->beginReadArray(prefix); } void Settings::beginWriteArray(const QString& prefix, int size) { + _prefixes.push(prefix); _manager->beginWriteArray(prefix, size); } void Settings::endArray() { + _prefixes.pop(); _manager->endArray(); } @@ -66,10 +73,12 @@ void Settings::setArrayIndex(int i) { } void Settings::beginGroup(const QString& prefix) { + _prefixes.push(prefix); _manager->beginGroup(prefix); } void Settings::endGroup() { + _prefixes.pop(); _manager->endGroup(); } diff --git a/libraries/shared/src/SettingHandle.h b/libraries/shared/src/SettingHandle.h index e83c563036..f19fc5875b 100644 --- a/libraries/shared/src/SettingHandle.h +++ b/libraries/shared/src/SettingHandle.h @@ -58,8 +58,10 @@ public: void setQuatValue(const QString& name, const glm::quat& quatValue); void getQuatValueIfValid(const QString& name, glm::quat& quatValue); +private: QSharedPointer _manager; QWriteLocker _locker; + QStack _prefixes; }; namespace Setting { diff --git a/libraries/shared/src/SettingInterface.cpp b/libraries/shared/src/SettingInterface.cpp index 630d52bf7d..1ebaa5cf82 100644 --- a/libraries/shared/src/SettingInterface.cpp +++ b/libraries/shared/src/SettingInterface.cpp @@ -98,6 +98,8 @@ namespace Setting { // Register Handle manager->registerHandle(this); _isInitialized = true; + } else { + qWarning() << "Settings interface used after manager destroyed"; } // Load value from disk From b01eb0439df25f813f1f7f9d329bd1cda0266cc5 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 13 Jun 2016 16:49:38 -0700 Subject: [PATCH 043/171] removed sticky thumb equip states. * removed STATE_WAITING_FOR_EQUIP_THUMB_RELEASE and waitingForEquipThumbRelease * removed STATE_EQUIP * removed STATE_WAITING_FOR_RELEASE_THUMB_RELEASE and waitingForReleaseThumbRelease * removed 'Hifi-Object-Manipulation' 'loaded' support and checkNewlyLoaded method. --- .../system/controllers/handControllerGrab.js | 84 ++++--------------- 1 file changed, 14 insertions(+), 70 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 386a4905f1..2857c91936 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -158,14 +158,11 @@ var STATE_SEARCHING = 1; var STATE_HOLD_SEARCHING = 2; var STATE_DISTANCE_HOLDING = 3; var STATE_NEAR_GRABBING = 4; -var STATE_NEAR_TRIGGER = 6; -var STATE_CONTINUE_NEAR_TRIGGER = 7; -var STATE_FAR_TRIGGER = 8; -var STATE_CONTINUE_FAR_TRIGGER = 9; -var STATE_EQUIP = 11; -var STATE_HOLD = 12; -var STATE_WAITING_FOR_RELEASE_THUMB_RELEASE = 15; -var STATE_WAITING_FOR_EQUIP_THUMB_RELEASE = 16; +var STATE_NEAR_TRIGGER = 4; +var STATE_CONTINUE_NEAR_TRIGGER = 6; +var STATE_FAR_TRIGGER = 7; +var STATE_CONTINUE_FAR_TRIGGER = 8; +var STATE_HOLD = 9; // "collidesWith" is specified by comma-separated list of group names // the possible group names are: static, dynamic, kinematic, myAvatar, otherAvatar @@ -202,11 +199,6 @@ CONTROLLER_STATE_MACHINE[STATE_NEAR_GRABBING] = { enterMethod: "nearGrabbingEnter", updateMethod: "nearGrabbing" }; -CONTROLLER_STATE_MACHINE[STATE_EQUIP] = { - name: "equip", - enterMethod: "nearGrabbingEnter", - updateMethod: "nearGrabbing" -}; CONTROLLER_STATE_MACHINE[STATE_HOLD] = { name: "continue_hold", enterMethod: "nearGrabbingEnter", @@ -228,14 +220,6 @@ CONTROLLER_STATE_MACHINE[STATE_CONTINUE_FAR_TRIGGER] = { name: "continue_far_trigger", updateMethod: "continueFarTrigger" }; -CONTROLLER_STATE_MACHINE[STATE_WAITING_FOR_EQUIP_THUMB_RELEASE] = { - name: "waiting_for_equip_thumb_release", - updateMethod: "waitingForEquipThumbRelease" -}; -CONTROLLER_STATE_MACHINE[STATE_WAITING_FOR_RELEASE_THUMB_RELEASE] = { - name: "waiting_for_release_thumb_release", - updateMethod: "waitingForReleaseThumbRelease" -}; function stateToName(state) { return CONTROLLER_STATE_MACHINE[state] ? CONTROLLER_STATE_MACHINE[state].name : "???"; @@ -1483,7 +1467,7 @@ function MyController(hand) { var handPosition = this.getHandPosition(); var hasPresetPosition = false; - if ((this.state == STATE_EQUIP || this.state == STATE_HOLD) && this.hasPresetOffsets()) { + if (this.state == STATE_HOLD && this.hasPresetOffsets()) { var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); // if an object is "equipped" and has a predefined offset, use it. this.ignoreIK = grabbableData.ignoreIK ? grabbableData.ignoreIK : false; @@ -1499,7 +1483,7 @@ function MyController(hand) { var currentObjectPosition = grabbedProperties.position; var offset = Vec3.subtract(currentObjectPosition, handPosition); this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset); - if (this.temporaryPositionOffset && (this.state == STATE_EQUIP)) { + if (this.temporaryPositionOffset) { this.offsetPosition = this.temporaryPositionOffset; // hasPresetPosition = true; } @@ -1543,7 +1527,7 @@ function MyController(hand) { if (this.state == STATE_NEAR_GRABBING) { this.callEntityMethodOnGrabbed("startNearGrab"); - } else { // this.state == STATE_EQUIP || this.state == STATE_HOLD + } else { // this.state == STATE_HOLD this.callEntityMethodOnGrabbed("startEquip"); } @@ -1568,21 +1552,6 @@ function MyController(hand) { this.callEntityMethodOnGrabbed("releaseEquip"); return; } - if (this.state == STATE_EQUIP && this.thumbPressed()) { - this.setState(STATE_WAITING_FOR_RELEASE_THUMB_RELEASE); - this.callEntityMethodOnGrabbed("releaseEquip"); - return; - } - if (this.state == STATE_NEAR_GRABBING && this.thumbPressed()) { - this.setState(STATE_WAITING_FOR_EQUIP_THUMB_RELEASE); - this.callEntityMethodOnGrabbed("releaseGrab"); - this.callEntityMethodOnGrabbed("startEquip"); - return; - } - if (this.state == STATE_HOLD && this.thumbPressed()) { - this.setState(STATE_WAITING_FOR_EQUIP_THUMB_RELEASE); - return; - } this.heartBeat(this.grabbedEntity); @@ -1612,7 +1581,7 @@ function MyController(hand) { this.setState(STATE_OFF); if (this.state == STATE_NEAR_GRABBING) { this.callEntityMethodOnGrabbed("releaseGrab"); - } else { // (this.state == STATE_EQUIP || this.state == STATE_HOLD) + } else { // this.state == STATE_HOLD this.callEntityMethodOnGrabbed("releaseEquip"); } return; @@ -1651,7 +1620,7 @@ function MyController(hand) { this.currentObjectTime = now; var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, {}); - if (this.state === STATE_EQUIP || this.state === STATE_HOLD) { + if (this.state === STATE_HOLD) { this.callEntityMethodOnGrabbed("continueEquip"); } if (this.state == STATE_NEAR_GRABBING) { @@ -1680,17 +1649,6 @@ function MyController(hand) { } }; - this.waitingForEquipThumbRelease = function() { - if (this.thumbReleased() && this.triggerSmoothedReleased()) { - this.setState(STATE_EQUIP); - } - }; - this.waitingForReleaseThumbRelease = function() { - if (this.thumbReleased() && this.triggerSmoothedReleased()) { - this.setState(STATE_OFF); - } - }; - this.nearTrigger = function() { if (this.triggerSmoothedReleased() && this.secondaryReleased()) { this.setState(STATE_OFF); @@ -1968,6 +1926,8 @@ function MyController(hand) { setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); }; + // AJT: WTF TO DO WITH THIS? + /* this.checkNewlyLoaded = function(loadedEntityID) { if (this.state == STATE_OFF || this.state == STATE_SEARCHING || @@ -1989,6 +1949,7 @@ function MyController(hand) { this.setState(STATE_EQUIP); } } + */ }; var rightController = new MyController(RIGHT_HAND); @@ -2044,7 +2005,7 @@ handleHandMessages = function(channel, message, sender) { var data = JSON.parse(message); var selectedController = (data.hand === 'left') ? leftController : rightController; selectedController.release(); - selectedController.setState(STATE_EQUIP); + selectedController.setState(STATE_HOLD); selectedController.grabbedEntity = data.entityID; } catch (e) {} @@ -2066,23 +2027,6 @@ handleHandMessages = function(channel, message, sender) { } } catch (e) {} - } else if (channel === 'Hifi-Object-Manipulation') { - if (sender !== MyAvatar.sessionUUID) { - return; - } - - var parsedMessage = null; - try { - parsedMessage = JSON.parse(message); - } catch (e) { - print('error parsing Hifi-Object-Manipulation message'); - return; - } - - if (parsedMessage.action === 'loaded') { - rightController.checkNewlyLoaded(parsedMessage['grabbedEntity']); - leftController.checkNewlyLoaded(parsedMessage['grabbedEntity']); - } } } } From 526fc7d062366dbf3b2be7c8e87b80c70d01e391 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Mon, 13 Jun 2016 17:13:10 -0700 Subject: [PATCH 044/171] Make handControllerGrab independent of whether we're using laser or Reticle. --- scripts/system/controllers/handControllerGrab.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 25cd100991..53c16f26c1 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -300,7 +300,10 @@ function propsArePhysical(props) { // and we should not be showing lasers when someone else is using the Reticle to indicate a 2D minor mode. var EXTERNALLY_MANAGED_2D_MINOR_MODE = true; function isIn2DMode() { - return EXTERNALLY_MANAGED_2D_MINOR_MODE && Reticle.visible; + // In this version, we make our own determination of whether we're aimed a HUD element, + // because other scripts (such as handControllerPointer) might be using some other visualization + // instead of setting Reticle.visible. + return EXTERNALLY_MANAGED_2D_MINOR_MODE && (Reticle.pointingAtSystemOverlay || Overlays.getOverlayAtPoint(Reticle.position)); } function restore2DMode() { if (!EXTERNALLY_MANAGED_2D_MINOR_MODE) { From dfd03d5e6184ffb7a0f3b9a0b81e584dcf78e2a6 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Mon, 13 Jun 2016 17:15:51 -0700 Subject: [PATCH 045/171] Prepare for working laser. But at this point, we still show the laser AND the Reticle. --- .../system/controllers/handControllerPointer.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index 1728647e5e..f5c0c4bb7c 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -319,6 +319,7 @@ function toggleHand() { // unequivocally switch which hand controls mouse positi activeTrigger = rightTrigger; activeHudLaser = RIGHT_HUD_LASER; } + clearSystemLaser(); } function makeToggleAction(hand) { // return a function(0|1) that makes the specified hand control mouse when 1 return function (on) { @@ -335,8 +336,8 @@ Script.scriptEnding.connect(clickMapping.disable); clickMapping.from(Controller.Standard.RT).peek().to(rightTrigger.triggerPress); clickMapping.from(Controller.Standard.LT).peek().to(leftTrigger.triggerPress); // Full smoothed trigger is a click. -clickMapping.from(rightTrigger.full).to(Controller.Actions.ReticleClick); -clickMapping.from(leftTrigger.full).to(Controller.Actions.ReticleClick); +clickMapping.from(rightTrigger.full).when(isPointingAtOverlay).to(Controller.Actions.ReticleClick); +clickMapping.from(leftTrigger.full).when(isPointingAtOverlay).to(Controller.Actions.ReticleClick); clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(Controller.Actions.ContextMenu); clickMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(Controller.Actions.ContextMenu); // Partial smoothed trigger is activation. @@ -363,8 +364,8 @@ Script.scriptEnding.connect(function () { overlays.forEach(Overlays.deleteOverlay); }); var visualizationIsShowing = false; // Not whether it desired, but simply whether it is. Just an optimziation. -var systemLaserOn = false; var SYSTEM_LASER_DIRECTION = Vec3.normalize({x: 0, y: -1, z: -1}); // Guessing 45 degrees. +var systemLaserOn = false; function clearSystemLaser() { if (!systemLaserOn) { return; @@ -379,8 +380,12 @@ function turnOffVisualization(optionalEnableClicks) { // because we're showing c clearSystemLaser(); } else if (!systemLaserOn) { print('FIXME remove: setHandLasers', activeHudLaser, true, JSON.stringify(LASER_COLOR_XYZW), JSON.stringify(SYSTEM_LASER_DIRECTION)); - HMD.setHandLasers(activeHudLaser, true, LASER_COLOR_XYZW, SYSTEM_LASER_DIRECTION); + // If the active plugin doesn't implement hand lasers, show the mouse reticle instead. + /*Reticle.visible = !*/HMD.setHandLasers(activeHudLaser, true, LASER_COLOR_XYZW, SYSTEM_LASER_DIRECTION); + Reticle.visible = true; // FIXME: just for now, while hand lasers has the bug that requires this. systemLaserOn = true; + } else { + Reticle.visible = true; } if (!visualizationIsShowing) { return; @@ -415,9 +420,11 @@ function updateVisualization(controllerPosition, controllerDirection, hudPositio // For now, though, we present a false projection of the cursor onto whatever is below it. This is // different from the hand beam termination because the false projection is from the camera, while // the hand beam termination is from the hand. + /* // FIXME: We can tighten this up later, once we know what will and won't be included. var eye = Camera.getPosition(); var falseProjection = intersection3d(eye, Vec3.subtract(hudPosition3d, eye)); Overlays.editOverlay(fakeProjectionBall, {visible: true, position: falseProjection}); + */ Reticle.visible = false; return visualizationIsShowing; // In case we change caller to act conditionally. @@ -465,7 +472,6 @@ function update() { if (HMD.active) { // Doesn't hurt anything without the guard, but consider it documentation. Reticle.depth = SPHERICAL_HUD_DISTANCE; // NOT CORRECT IF WE SWITCH TO OFFSET SPHERE! } - Reticle.visible = true; return turnOffVisualization(true); } // We are not pointing at a HUD element (but it could be a 3d overlay). From 6707f889b82d2e8fdaa065ac32e4d536f24b8dd6 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Mon, 13 Jun 2016 17:21:09 -0700 Subject: [PATCH 046/171] Fixing laser offset, support laser in Oculus --- .../display-plugins/OpenGLDisplayPlugin.cpp | 1 + .../src/display-plugins/OpenGLDisplayPlugin.h | 1 + .../display-plugins/hmd/HmdDisplayPlugin.cpp | 32 +++---- .../display-plugins/hmd/HmdDisplayPlugin.h | 4 +- .../oculus/src/OculusBaseDisplayPlugin.cpp | 24 ++++-- plugins/openvr/src/OpenVrDisplayPlugin.cpp | 26 ++++-- plugins/openvr/src/OpenVrHelpers.cpp | 86 ++++++++++++++++++- plugins/openvr/src/OpenVrHelpers.h | 4 + plugins/openvr/src/ViveControllerManager.cpp | 83 +----------------- 9 files changed, 138 insertions(+), 123 deletions(-) diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 363bde15e6..23c836b3de 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -517,6 +517,7 @@ void OpenGLDisplayPlugin::compositeLayers() { glBindTexture(GL_TEXTURE_2D, 0); Context::Disable(Capability::Blend); } + compositeExtra(); }); } diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 02a30a2570..69653b8c76 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -74,6 +74,7 @@ protected: virtual void compositeScene(); virtual void compositeOverlay(); virtual void compositePointer(); + virtual void compositeExtra() {}; virtual bool hasFocus() const override; diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 9b71d3703b..9ae843d45d 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -316,10 +316,9 @@ void HmdDisplayPlugin::compositePointer() { Uniform(*_program, _mvpUniform).Set(mvp); _plane->Draw(); }); - - compositeLasers(); } + void HmdDisplayPlugin::internalPresent() { PROFILE_RANGE_EX(__FUNCTION__, 0xff00ff00, (uint64_t)presentCount()) @@ -412,24 +411,7 @@ bool HmdDisplayPlugin::setHandLaser(uint32_t hands, HandLaserMode mode, const ve return true; } -// FIXME try to consolidate the duplication of logic between this function and a similar one in CompsitorHelper. -static float calculateRayUiCollisionDistance(const glm::mat4& headPose, const glm::vec3& position, const glm::vec3& direction) { - auto relativePosition4 = glm::inverse(headPose) * vec4(position, 1); - auto relativePosition = vec3(relativePosition4) / relativePosition4.w; - auto relativeDirection = glm::inverse(glm::quat_cast(headPose)) * direction; - if (glm::abs(glm::length2(relativeDirection) - 1.0f) > EPSILON) { - relativeDirection = glm::normalize(relativeDirection); - } - // FIXME fetch the actual UI radius from... somewhere? - float uiRadius = 1.0f; - float instersectionDistance; - if (!glm::intersectRaySphere(relativePosition, relativeDirection, vec3(0), uiRadius * uiRadius, instersectionDistance)) { - return -1; - } - return instersectionDistance; -} - -void HmdDisplayPlugin::compositeLasers() { +void HmdDisplayPlugin::compositeExtra() { std::array handLasers; std::array renderHandPoses; withPresentThreadLock([&] { @@ -465,10 +447,16 @@ void HmdDisplayPlugin::compositeLasers() { const auto& laserDirection = handLaser.direction; auto model = renderHandPoses[i]; auto castDirection = glm::quat_cast(model) * laserDirection; + if (glm::abs(glm::length2(castDirection) - 1.0f) > EPSILON) { + castDirection = glm::normalize(castDirection); + } + + // FIXME fetch the actual UI radius from... somewhere? + float uiRadius = 1.0f; // Find the intersection of the laser with he UI and use it to scale the model matrix - float distance = calculateRayUiCollisionDistance(_currentPresentFrameInfo.presentPose, vec3(renderHandPoses[i][3]), castDirection); - if (distance < 0) { + float distance; + if (!glm::intersectRaySphere(vec3(renderHandPoses[i][3]), castDirection, vec3(0), uiRadius * uiRadius, distance)) { continue; } diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index 7cdcf06e9a..738028d684 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -46,9 +46,7 @@ protected: void customizeContext() override; void uncustomizeContext() override; void updateFrameData() override; - - void compositeLasers(); - + void compositeExtra() override; struct HandLaserInfo { HandLaserMode mode { HandLaserMode::None }; diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp index a16f630bf8..61c4696f51 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp @@ -8,6 +8,7 @@ #include "OculusBaseDisplayPlugin.h" #include +#include #include "OculusHelpers.h" @@ -25,16 +26,21 @@ bool OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) { _currentRenderFrameInfo.renderPose = toGlm(trackingState.HeadPose.ThePose); _currentRenderFrameInfo.presentPose = _currentRenderFrameInfo.renderPose; + std::array handPoses; + // Make controller poses available to the presentation thread + ovr_for_each_hand([&](ovrHandType hand) { + static const auto REQUIRED_HAND_STATUS = ovrStatus_OrientationTracked & ovrStatus_PositionTracked; + if (REQUIRED_HAND_STATUS != (trackingState.HandStatusFlags[hand] & REQUIRED_HAND_STATUS)) { + return; + } + + auto correctedPose = ovrControllerPoseToHandPose(hand, trackingState.HandPoses[hand]); + static const glm::quat HAND_TO_LASER_ROTATION = glm::rotation(Vectors::UNIT_Z, Vectors::UNIT_NEG_Y); + handPoses[hand] = glm::translate(glm::mat4(), correctedPose.translation) * glm::mat4_cast(correctedPose.rotation * HAND_TO_LASER_ROTATION); + }); + withRenderThreadLock([&] { - // Make controller poses available to the presentation thread - ovr_for_each_hand([&](ovrHandType hand){ - static const auto REQUIRED_HAND_STATUS = ovrStatus_OrientationTracked & ovrStatus_PositionTracked; - if (REQUIRED_HAND_STATUS == (trackingState.HandStatusFlags[hand] & REQUIRED_HAND_STATUS)) { - _handPoses[hand] = toGlm(trackingState.HandPoses[hand].ThePose); - } else { - _handPoses[hand] = glm::mat4(); - } - }); + _handPoses = handPoses; _frameInfos[frameIndex] = _currentRenderFrameInfo; }); return true; diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 92c01dc0a3..d57dddf512 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -179,15 +180,26 @@ bool OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { _currentRenderFrameInfo.renderPose = _trackedDevicePoseMat4[vr::k_unTrackedDeviceIndex_Hmd]; bool keyboardVisible = isOpenVrKeyboardShown(); + + std::array handPoses; + if (!keyboardVisible) { + for (int i = 0; i < 2; ++i) { + if (handIndices[i] == vr::k_unTrackedDeviceIndexInvalid) { + continue; + } + auto deviceIndex = handIndices[i]; + const mat4& mat = _trackedDevicePoseMat4[deviceIndex]; + const vec3& linearVelocity = _trackedDeviceLinearVelocities[deviceIndex]; + const vec3& angularVelocity = _trackedDeviceAngularVelocities[deviceIndex]; + auto correctedPose = openVrControllerPoseToHandPose(i == 0, mat, linearVelocity, angularVelocity); + static const glm::quat HAND_TO_LASER_ROTATION = glm::rotation(Vectors::UNIT_Z, Vectors::UNIT_NEG_Y); + handPoses[i] = glm::translate(glm::mat4(), correctedPose.translation) * glm::mat4_cast(correctedPose.rotation * HAND_TO_LASER_ROTATION); + } + } + withRenderThreadLock([&] { // Make controller poses available to the presentation thread - for (int i = 0; i < 2; ++i) { - if (keyboardVisible || handIndices[i] == vr::k_unTrackedDeviceIndexInvalid) { - _handPoses[i] = glm::mat4(); - } else { - _handPoses[i] = _sensorResetMat * toGlm(_trackedDevicePose[handIndices[i]].mDeviceToAbsoluteTracking); - } - } + _handPoses = handPoses; _frameInfos[frameIndex] = _currentRenderFrameInfo; }); return true; diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index e4cca6ecd6..4b4eef0538 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -18,8 +18,9 @@ #include #include - #include +#include +#include Q_DECLARE_LOGGING_CATEGORY(displayplugins) Q_LOGGING_CATEGORY(displayplugins, "hifi.plugins.display") @@ -242,3 +243,86 @@ void handleOpenVrEvents() { } +controller::Pose openVrControllerPoseToHandPose(bool isLeftHand, const mat4& mat, const vec3& linearVelocity, const vec3& angularVelocity) { + // When the sensor-to-world rotation is identity the coordinate axes look like this: + // + // user + // forward + // -z + // | + // y| user + // y o----x right + // o-----x user + // | up + // | + // z + // + // Rift + + // From ABOVE the hand canonical axes looks like this: + // + // | | | | y | | | | + // | | | | | | | | | + // | | | | | + // |left | / x---- + \ |right| + // | _/ z \_ | + // | | | | + // | | | | + // + + // So when the user is in Rift space facing the -zAxis with hands outstretched and palms down + // the rotation to align the Touch axes with those of the hands is: + // + // touchToHand = halfTurnAboutY * quaterTurnAboutX + + // Due to how the Touch controllers fit into the palm there is an offset that is different for each hand. + // You can think of this offset as the inverse of the measured rotation when the hands are posed, such that + // the combination (measurement * offset) is identity at this orientation. + // + // Qoffset = glm::inverse(deltaRotation when hand is posed fingers forward, palm down) + // + // An approximate offset for the Touch can be obtained by inspection: + // + // Qoffset = glm::inverse(glm::angleAxis(sign * PI/2.0f, zAxis) * glm::angleAxis(PI/4.0f, xAxis)) + // + // So the full equation is: + // + // Q = combinedMeasurement * touchToHand + // + // Q = (deltaQ * QOffset) * (yFlip * quarterTurnAboutX) + // + // Q = (deltaQ * inverse(deltaQForAlignedHand)) * (yFlip * quarterTurnAboutX) + static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y); + static const glm::quat quarterX = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_X); + static const glm::quat touchToHand = yFlip * quarterX; + + static const glm::quat leftQuarterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z); + static const glm::quat rightQuarterZ = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_Z); + static const glm::quat eighthX = glm::angleAxis(PI / 4.0f, Vectors::UNIT_X); + + static const glm::quat leftRotationOffset = glm::inverse(leftQuarterZ * eighthX) * touchToHand; + static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ * eighthX) * touchToHand; + + static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches + static const glm::vec3 CONTROLLER_OFFSET = glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f, + CONTROLLER_LENGTH_OFFSET / 2.0f, + CONTROLLER_LENGTH_OFFSET * 2.0f); + static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET; + static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET; + + auto translationOffset = (isLeftHand ? leftTranslationOffset : rightTranslationOffset); + auto rotationOffset = (isLeftHand ? leftRotationOffset : rightRotationOffset); + + glm::vec3 position = extractTranslation(mat); + glm::quat rotation = glm::normalize(glm::quat_cast(mat)); + + position += rotation * translationOffset; + rotation = rotation * rotationOffset; + + // transform into avatar frame + auto result = controller::Pose(position, rotation); + // handle change in velocity due to translationOffset + result.velocity = linearVelocity + glm::cross(angularVelocity, position - extractTranslation(mat)); + result.angularVelocity = angularVelocity; + return result; +} \ No newline at end of file diff --git a/plugins/openvr/src/OpenVrHelpers.h b/plugins/openvr/src/OpenVrHelpers.h index db8bb4f2e8..131db517ab 100644 --- a/plugins/openvr/src/OpenVrHelpers.h +++ b/plugins/openvr/src/OpenVrHelpers.h @@ -12,6 +12,8 @@ #include #include +#include + bool openVrSupported(); vr::IVRSystem* acquireOpenVrSystem(); @@ -55,3 +57,5 @@ inline vr::HmdMatrix34_t toOpenVr(const mat4& m) { } return result; } + +controller::Pose openVrControllerPoseToHandPose(bool isLeftHand, const mat4& mat, const vec3& linearVelocity, const vec3& angularVelocity); diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 4e17a2edf4..83b72c5c08 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -37,10 +37,6 @@ vr::IVRSystem* acquireOpenVrSystem(); void releaseOpenVrSystem(); -static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches -static const glm::vec3 CONTROLLER_OFFSET = glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f, - CONTROLLER_LENGTH_OFFSET / 2.0f, - CONTROLLER_LENGTH_OFFSET * 2.0f); static const char* CONTROLLER_MODEL_STRING = "vr_controller_05_wireless_b"; static const QString MENU_PARENT = "Avatar"; @@ -382,86 +378,11 @@ void ViveControllerManager::InputDevice::handleButtonEvent(float deltaTime, uint void ViveControllerManager::InputDevice::handlePoseEvent(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, const mat4& mat, const vec3& linearVelocity, const vec3& angularVelocity, bool isLeftHand) { - // When the sensor-to-world rotation is identity the coordinate axes look like this: - // - // user - // forward - // -z - // | - // y| user - // y o----x right - // o-----x user - // | up - // | - // z - // - // Vive - // - - // From ABOVE the hand canonical axes looks like this: - // - // | | | | y | | | | - // | | | | | | | | | - // | | | | | - // |left | / x---- + \ |right| - // | _/ z \_ | - // | | | | - // | | | | - // - - // So when the user is standing in Vive space facing the -zAxis with hands outstretched and palms down - // the rotation to align the Vive axes with those of the hands is: - // - // QviveToHand = halfTurnAboutY * quaterTurnAboutX - - // Due to how the Vive controllers fit into the palm there is an offset that is different for each hand. - // You can think of this offset as the inverse of the measured rotation when the hands are posed, such that - // the combination (measurement * offset) is identity at this orientation. - // - // Qoffset = glm::inverse(deltaRotation when hand is posed fingers forward, palm down) - // - // An approximate offset for the Vive can be obtained by inspection: - // - // Qoffset = glm::inverse(glm::angleAxis(sign * PI/4.0f, zAxis) * glm::angleAxis(PI/2.0f, xAxis)) - // - // So the full equation is: - // - // Q = combinedMeasurement * viveToHand - // - // Q = (deltaQ * QOffset) * (yFlip * quarterTurnAboutX) - // - // Q = (deltaQ * inverse(deltaQForAlignedHand)) * (yFlip * quarterTurnAboutX) - - static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y); - static const glm::quat quarterX = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_X); - static const glm::quat viveToHand = yFlip * quarterX; - - static const glm::quat leftQuaterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z); - static const glm::quat rightQuaterZ = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_Z); - static const glm::quat eighthX = glm::angleAxis(PI / 4.0f, Vectors::UNIT_X); - - static const glm::quat leftRotationOffset = glm::inverse(leftQuaterZ * eighthX) * viveToHand; - static const glm::quat rightRotationOffset = glm::inverse(rightQuaterZ * eighthX) * viveToHand; - - static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET; - static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET; - - auto translationOffset = (isLeftHand ? leftTranslationOffset : rightTranslationOffset); - auto rotationOffset = (isLeftHand ? leftRotationOffset : rightRotationOffset); - - glm::vec3 position = extractTranslation(mat); - glm::quat rotation = glm::normalize(glm::quat_cast(mat)); - - position += rotation * translationOffset; - rotation = rotation * rotationOffset; + auto pose = openVrControllerPoseToHandPose(isLeftHand, mat, linearVelocity, angularVelocity); // transform into avatar frame glm::mat4 controllerToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat; - auto avatarPose = controller::Pose(position, rotation); - // handle change in velocity due to translationOffset - avatarPose.velocity = linearVelocity + glm::cross(angularVelocity, position - extractTranslation(mat)); - avatarPose.angularVelocity = angularVelocity; - _poseStateMap[isLeftHand ? controller::LEFT_HAND : controller::RIGHT_HAND] = avatarPose.transform(controllerToAvatar); + _poseStateMap[isLeftHand ? controller::LEFT_HAND : controller::RIGHT_HAND] = pose.transform(controllerToAvatar); } bool ViveControllerManager::InputDevice::triggerHapticPulse(float strength, float duration, controller::Hand hand) { From 28b6cc2777a577474a027ace28b350e948bf70b3 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 13 Jun 2016 18:54:21 -0700 Subject: [PATCH 047/171] handControllerGrab.js is now eslint clean. --- .../system/controllers/handControllerGrab.js | 127 ++++++------------ 1 file changed, 43 insertions(+), 84 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 2857c91936..34d4ca6294 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -10,7 +10,7 @@ // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt, pointInExtents, vec3equal, setEntityCustomData, getEntityCustomData */ +/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, Reticle, Messages, setEntityCustomData, getEntityCustomData, vec3toStr */ Script.include("/~/system/libraries/utils.js"); @@ -47,7 +47,6 @@ var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position var DISTANCE_HOLDING_UNITY_MASS = 1200; // The mass at which the distance holding action timeframe is unmodified var DISTANCE_HOLDING_UNITY_DISTANCE = 6; // The distance at which the distance holding action timeframe is unmodified -var DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR = 2.0; // object rotates this much more than hand did var MOVE_WITH_HEAD = true; // experimental head-control of distantly held objects var FAR_TO_NEAR_GRAB_PADDING_FACTOR = 1.2; @@ -81,13 +80,6 @@ var NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-g var SHOW_GRAB_SPHERE = false; // draw a green sphere to show the grab search position and size var CHECK_TOO_FAR_UNEQUIP_TIME = 1.0; // seconds -// -// equip -// - -var EQUIP_SPRING_SHUTOFF_DISTANCE = 0.05; -var EQUIP_SPRING_TIMEFRAME = 0.4; // how quickly objects move to their new position - // // other constants // @@ -158,7 +150,7 @@ var STATE_SEARCHING = 1; var STATE_HOLD_SEARCHING = 2; var STATE_DISTANCE_HOLDING = 3; var STATE_NEAR_GRABBING = 4; -var STATE_NEAR_TRIGGER = 4; +var STATE_NEAR_TRIGGER = 5; var STATE_CONTINUE_NEAR_TRIGGER = 6; var STATE_FAR_TRIGGER = 7; var STATE_CONTINUE_FAR_TRIGGER = 8; @@ -200,7 +192,7 @@ CONTROLLER_STATE_MACHINE[STATE_NEAR_GRABBING] = { updateMethod: "nearGrabbing" }; CONTROLLER_STATE_MACHINE[STATE_HOLD] = { - name: "continue_hold", + name: "hold", enterMethod: "nearGrabbingEnter", updateMethod: "nearGrabbing" }; @@ -505,7 +497,7 @@ function MyController(hand) { this.overlayLine = Overlays.addOverlay("line3d", lineProperties); } else { - var success = Overlays.editOverlay(this.overlayLine, { + Overlays.editOverlay(this.overlayLine, { lineWidth: 5, start: closePoint, end: farPoint, @@ -621,13 +613,6 @@ function MyController(hand) { }) }; - this.renewParticleBeamLifetime = function() { - var props = Entities.getEntityProperties(this.particleBeamObject, "age"); - Entities.editEntity(this.particleBeamObject, { - lifetime: TEMPORARY_PARTICLE_BEAM_LIFETIME + props.age // renew lifetime - }) - } - this.evalLightWorldTransform = function(modelPos, modelRot) { var MODEL_LIGHT_POSITION = { @@ -648,7 +633,7 @@ function MyController(hand) { }; }; - this.handleSpotlight = function(parentID, position) { + this.handleSpotlight = function(parentID) { var LIFETIME = 100; var modelProperties = Entities.getEntityProperties(parentID, ['position', 'rotation']); @@ -672,7 +657,7 @@ function MyController(hand) { exponent: 0.3, cutoff: 20, lifetime: LIFETIME, - position: lightTransform.p, + position: lightTransform.p }; if (this.spotlight === null) { @@ -680,12 +665,12 @@ function MyController(hand) { } else { Entities.editEntity(this.spotlight, { //without this, this light would maintain rotation with its parent - rotation: Quat.fromPitchYawRollDegrees(-90, 0, 0), + rotation: Quat.fromPitchYawRollDegrees(-90, 0, 0) }) } }; - this.handlePointLight = function(parentID, position) { + this.handlePointLight = function(parentID) { var LIFETIME = 100; var modelProperties = Entities.getEntityProperties(parentID, ['position', 'rotation']); @@ -709,13 +694,11 @@ function MyController(hand) { exponent: 0.3, cutoff: 20, lifetime: LIFETIME, - position: lightTransform.p, + position: lightTransform.p }; if (this.pointlight === null) { this.pointlight = Entities.addEntity(lightProperties); - } else { - } }; @@ -915,12 +898,9 @@ function MyController(hand) { } var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - var currentHandRotation = Controller.getPoseValue(controllerHandInput).rotation; var currentControllerPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).translation), MyAvatar.position); - var handDeltaRotation = Quat.multiply(currentHandRotation, Quat.inverse(this.startingHandRotation)); - var avatarControllerPose = Controller.getPoseValue((this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand); var controllerRotation = Quat.multiply(MyAvatar.orientation, avatarControllerPose.rotation); @@ -941,7 +921,7 @@ function MyController(hand) { this.lastPickTime = now; } - rayPickedCandidateEntities = []; // the list of candidates to consider grabbing + var rayPickedCandidateEntities = []; // the list of candidates to consider grabbing this.intersectionDistance = 0.0; for (var index = 0; index < pickRays.length; ++index) { @@ -974,8 +954,8 @@ function MyController(hand) { } } - nearPickedCandidateEntities = Entities.findEntities(handPosition, GRAB_RADIUS); - candidateEntities = rayPickedCandidateEntities.concat(nearPickedCandidateEntities); + var nearPickedCandidateEntities = Entities.findEntities(handPosition, GRAB_RADIUS); + var candidateEntities = rayPickedCandidateEntities.concat(nearPickedCandidateEntities); var forbiddenNames = ["Grab Debug Entity", "grab pointer"]; var forbiddenTypes = ['Unknown', 'Light', 'PolyLine', 'Zone']; @@ -990,9 +970,9 @@ function MyController(hand) { var propsForCandidate = Entities.getEntityProperties(candidateEntities[i], GRABBABLE_PROPERTIES); var near = (nearPickedCandidateEntities.indexOf(candidateEntities[i]) >= 0); - var isPhysical = propsArePhysical(propsForCandidate); + var physical = propsArePhysical(propsForCandidate); var grabbable; - if (isPhysical) { + if (physical) { // physical things default to grabbable grabbable = true; } else { @@ -1051,7 +1031,7 @@ function MyController(hand) { } if (this.state == STATE_SEARCHING && - !isPhysical && distance > NEAR_PICK_MAX_DISTANCE && !near && !grabbableDataForCandidate.wantsTrigger) { + !physical && distance > NEAR_PICK_MAX_DISTANCE && !near && !grabbableDataForCandidate.wantsTrigger) { // we can't distance-grab non-physical if (WANT_DEBUG_SEARCH_NAME && propsForCandidate.name == WANT_DEBUG_SEARCH_NAME) { print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': not physical and too far for near-grab"); @@ -1068,18 +1048,18 @@ function MyController(hand) { } if ((this.grabbedEntity !== null) && (this.triggerSmoothedGrab() || this.secondarySqueezed())) { // We are squeezing enough to grab, and we've found an entity that we'll try to do something with. - var near = (nearPickedCandidateEntities.indexOf(this.grabbedEntity) >= 0) || minDistance <= NEAR_PICK_MAX_DISTANCE; + var isNear = (nearPickedCandidateEntities.indexOf(this.grabbedEntity) >= 0) || minDistance <= NEAR_PICK_MAX_DISTANCE; var isPhysical = propsArePhysical(props); // near or far trigger if (grabbableData.wantsTrigger) { - this.setState(near ? STATE_NEAR_TRIGGER : STATE_FAR_TRIGGER); + this.setState(isNear ? STATE_NEAR_TRIGGER : STATE_FAR_TRIGGER); return; } // near grab with action or equip var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, {}); var refCount = ("refCount" in grabData) ? grabData.refCount : 0; - if (near && (refCount < 1 || entityHasActions(this.grabbedEntity))) { + if (isNear && (refCount < 1 || entityHasActions(this.grabbedEntity))) { if (this.state == STATE_SEARCHING) { this.setState(STATE_NEAR_GRABBING); } else { // (this.state == STATE_HOLD_SEARCHING) @@ -1092,7 +1072,7 @@ function MyController(hand) { return; } // far grab - if (isPhysical && !near) { + if (isPhysical && !isNear) { if (entityIsGrabbedByOther(this.grabbedEntity)) { // don't distance grab something that is already grabbed. if (WANT_DEBUG_SEARCH_NAME && props.name == WANT_DEBUG_SEARCH_NAME) { @@ -1142,9 +1122,6 @@ function MyController(hand) { } return; } - if (WANT_DEBUG_SEARCH_NAME && props.name == WANT_DEBUG_SEARCH_NAME) { - print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': fell through."); - } } //search line visualizations @@ -1266,10 +1243,13 @@ function MyController(hand) { radius); // double delta controller rotation + /* + var DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR = 2.0; // object rotates this much more than hand did var handChange = Quat.multiply(Quat.slerp(this.previousControllerRotation, controllerRotation, DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR), Quat.inverse(this.previousControllerRotation)); + */ // update the currentObject position and rotation. this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, handMoved); @@ -1323,9 +1303,10 @@ function MyController(hand) { } } + /* var defaultConstraintData = { axisStart: false, - axisEnd: false, + axisEnd: false } var constraintData = getEntityCustomData('lightModifierKey', this.grabbedEntity, defaultConstraintData); @@ -1343,6 +1324,7 @@ function MyController(hand) { z: this.currentObjectPosition.z } } + */ var handPosition = this.getHandPosition(); @@ -1448,7 +1430,6 @@ function MyController(hand) { } this.nearGrabbingEnter = function() { - var now = Date.now(); this.lineOff(); this.overlayLineOff(); @@ -1503,7 +1484,7 @@ function MyController(hand) { // grab entity via parenting this.actionID = null; var handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); - reparentProps = { + var reparentProps = { parentID: MyAvatar.sessionUUID, parentJointIndex: handJointIndex } @@ -1573,7 +1554,7 @@ function MyController(hand) { var handPosition = this.getHandPosition(); // the center of the equipped object being far from the hand isn't enough to autoequip -- we also // need to fail the findEntities test. - nearPickedCandidateEntities = Entities.findEntities(handPosition, GRAB_RADIUS); + var nearPickedCandidateEntities = Entities.findEntities(handPosition, GRAB_RADIUS); if (nearPickedCandidateEntities.indexOf(this.grabbedEntity) == -1) { // for whatever reason, the held/equipped entity has been pulled away. ungrab or unequip. print("handControllerGrab -- autoreleasing held or equipped item because it is far from hand." + @@ -1597,9 +1578,7 @@ function MyController(hand) { // from the palm. var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; - var now = Date.now(); - var deltaPosition = Vec3.subtract(handControllerPosition, this.currentHandControllerTipPosition); // meters var deltaTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds if (deltaTime > 0.0) { @@ -1619,7 +1598,6 @@ function MyController(hand) { this.currentHandControllerTipPosition = handControllerPosition; this.currentObjectTime = now; - var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, {}); if (this.state === STATE_HOLD) { this.callEntityMethodOnGrabbed("continueEquip"); } @@ -1843,6 +1821,8 @@ function MyController(hand) { } this.deactivateEntity = function(entityID, noVelocity) { + var deactiveProps; + if (!this.entityActivated) { return; } @@ -1852,7 +1832,7 @@ function MyController(hand) { if (data && data["refCount"]) { data["refCount"] = data["refCount"] - 1; if (data["refCount"] < 1) { - var deactiveProps = { + deactiveProps = { gravity: data["gravity"], collidesWith: data["collidesWith"], collisionless: data["collisionless"], @@ -1865,7 +1845,6 @@ function MyController(hand) { // it looks like the dropped thing should fall, give it a little velocity. var props = Entities.getEntityProperties(entityID, ["parentID", "velocity", "dynamic", "shapeType"]) var parentID = props.parentID; - var forceVelocity = false; var doSetVelocity = false; if (parentID != NULL_UUID && deactiveProps.parentID == NULL_UUID && propsArePhysical(props)) { @@ -1900,7 +1879,7 @@ function MyController(hand) { // the parent causes it to go off in the wrong direction. This is a bug that should // be fixed. Entities.editEntity(entityID, { - velocity: this.currentVelocity, + velocity: this.currentVelocity // angularVelocity: this.currentAngularVelocity }); } @@ -1908,7 +1887,7 @@ function MyController(hand) { data = null; } else if (this.shouldResetParentOnRelease) { // we parent-grabbed this from another parent grab. try to put it back where we found it. - var deactiveProps = { + deactiveProps = { parentID: this.previousParentID, parentJointIndex: this.previousParentJointIndex, velocity: {x: 0.0, y: 0.0, z: 0.0}, @@ -1925,32 +1904,7 @@ function MyController(hand) { } setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); }; - - // AJT: WTF TO DO WITH THIS? - /* - this.checkNewlyLoaded = function(loadedEntityID) { - if (this.state == STATE_OFF || - this.state == STATE_SEARCHING || - this.state == STATE_HOLD_SEARCHING) { - var loadedProps = Entities.getEntityProperties(loadedEntityID); - if (loadedProps.parentID != MyAvatar.sessionUUID) { - return; - } - var handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); - if (loadedProps.parentJointIndex != handJointIndex) { - return; - } - print("--- handControllerGrab found loaded entity ---"); - // an entity has been loaded and it's where this script would have equipped something, so switch states. - this.grabbedEntity = loadedEntityID; - this.activateEntity(this.grabbedEntity, loadedProps, true); - this.isInitialGrab = true; - this.callEntityMethodOnGrabbed("startEquip"); - this.setState(STATE_EQUIP); - } - } - */ -}; +} var rightController = new MyController(RIGHT_HAND); var leftController = new MyController(LEFT_HAND); @@ -1988,7 +1942,8 @@ Messages.subscribe('Hifi-Hand-Grab'); Messages.subscribe('Hifi-Hand-RayPick-Blacklist'); Messages.subscribe('Hifi-Object-Manipulation'); -handleHandMessages = function(channel, message, sender) { +var handleHandMessages = function(channel, message, sender) { + var data; if (sender === MyAvatar.sessionUUID) { if (channel === 'Hifi-Hand-Disabler') { if (message === 'left') { @@ -2002,17 +1957,19 @@ handleHandMessages = function(channel, message, sender) { } } else if (channel === 'Hifi-Hand-Grab') { try { - var data = JSON.parse(message); + data = JSON.parse(message); var selectedController = (data.hand === 'left') ? leftController : rightController; selectedController.release(); selectedController.setState(STATE_HOLD); selectedController.grabbedEntity = data.entityID; - } catch (e) {} + } catch (e) { + print("WARNING: error parsing Hifi-Hand-Grab message"); + } } else if (channel === 'Hifi-Hand-RayPick-Blacklist') { try { - var data = JSON.parse(message); + data = JSON.parse(message); var action = data.action; var id = data.id; var index = blacklist.indexOf(id); @@ -2026,7 +1983,9 @@ handleHandMessages = function(channel, message, sender) { } } - } catch (e) {} + } catch (e) { + print("WARNING: error parsing Hifi-Hand-RayPick-Blacklist message"); + } } } } From f11735550fbeae426aa0f2675c80764a95d04b94 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 13 Jun 2016 18:55:53 -0700 Subject: [PATCH 048/171] Fix for Vive RIGHT_GRIP LEFT_GRIP button never becoming zero. --- plugins/openvr/src/ViveControllerManager.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 4e17a2edf4..97c502454b 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -370,6 +370,10 @@ void ViveControllerManager::InputDevice::handleButtonEvent(float deltaTime, uint } else if (button == vr::k_EButton_SteamVR_Touchpad) { _buttonPressedMap.insert(isLeftHand ? LS : RS); } + } else { + if (button == vr::k_EButton_Grip) { + _axisStateMap[isLeftHand ? LEFT_GRIP : RIGHT_GRIP] = 0.0f; + } } if (touched) { From cb51d00c1d50090d43a1ae03eb236f3f8e2a8fdc Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 13 Jun 2016 19:08:33 -0700 Subject: [PATCH 049/171] Removed CONTINUE_FAR_TRIGGER and CONTINUE_NEAR_TRIGGER --- .../system/controllers/handControllerGrab.js | 38 +++++-------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 34d4ca6294..6daafa425f 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -151,10 +151,8 @@ var STATE_HOLD_SEARCHING = 2; var STATE_DISTANCE_HOLDING = 3; var STATE_NEAR_GRABBING = 4; var STATE_NEAR_TRIGGER = 5; -var STATE_CONTINUE_NEAR_TRIGGER = 6; -var STATE_FAR_TRIGGER = 7; -var STATE_CONTINUE_FAR_TRIGGER = 8; -var STATE_HOLD = 9; +var STATE_FAR_TRIGGER = 6; +var STATE_HOLD = 7; // "collidesWith" is specified by comma-separated list of group names // the possible group names are: static, dynamic, kinematic, myAvatar, otherAvatar @@ -197,21 +195,15 @@ CONTROLLER_STATE_MACHINE[STATE_HOLD] = { updateMethod: "nearGrabbing" }; CONTROLLER_STATE_MACHINE[STATE_NEAR_TRIGGER] = { - name: "near_trigger", + name: "trigger", + enterMethod: "nearTriggerEnter", updateMethod: "nearTrigger" }; -CONTROLLER_STATE_MACHINE[STATE_CONTINUE_NEAR_TRIGGER] = { - name: "continue_near_trigger", - updateMethod: "continueNearTrigger" -}; CONTROLLER_STATE_MACHINE[STATE_FAR_TRIGGER] = { name: "far_trigger", + enterMethod: "farTriggerEnter", updateMethod: "farTrigger" }; -CONTROLLER_STATE_MACHINE[STATE_CONTINUE_FAR_TRIGGER] = { - name: "continue_far_trigger", - updateMethod: "continueFarTrigger" -}; function stateToName(state) { return CONTROLLER_STATE_MACHINE[state] ? CONTROLLER_STATE_MACHINE[state].name : "???"; @@ -1627,27 +1619,15 @@ function MyController(hand) { } }; - this.nearTrigger = function() { - if (this.triggerSmoothedReleased() && this.secondaryReleased()) { - this.setState(STATE_OFF); - this.callEntityMethodOnGrabbed("stopNearTrigger"); - return; - } + this.nearTriggerEnter = function() { this.callEntityMethodOnGrabbed("startNearTrigger"); - this.setState(STATE_CONTINUE_NEAR_TRIGGER); }; - this.farTrigger = function() { - if (this.triggerSmoothedReleased() && this.secondaryReleased()) { - this.setState(STATE_OFF); - this.callEntityMethodOnGrabbed("stopFarTrigger"); - return; - } + this.farTriggerEnter = function() { this.callEntityMethodOnGrabbed("startFarTrigger"); - this.setState(STATE_CONTINUE_FAR_TRIGGER); }; - this.continueNearTrigger = function() { + this.nearTrigger = function() { if (this.triggerSmoothedReleased() && this.secondaryReleased()) { this.setState(STATE_OFF); this.callEntityMethodOnGrabbed("stopNearTrigger"); @@ -1656,7 +1636,7 @@ function MyController(hand) { this.callEntityMethodOnGrabbed("continueNearTrigger"); }; - this.continueFarTrigger = function() { + this.farTrigger = function() { if (this.triggerSmoothedReleased() && this.secondaryReleased()) { this.setState(STATE_OFF); this.callEntityMethodOnGrabbed("stopFarTrigger"); From f3c47acbb240871a9889606fe1b0f6127dd3f160 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Mon, 13 Jun 2016 18:20:55 -0700 Subject: [PATCH 050/171] Fix offset when UI transform is not identity --- .../src/display-plugins/hmd/HmdDisplayPlugin.cpp | 4 +++- .../src/display-plugins/hmd/HmdDisplayPlugin.h | 2 ++ plugins/oculus/src/OculusBaseDisplayPlugin.cpp | 2 ++ plugins/openvr/src/OpenVrDisplayPlugin.cpp | 2 ++ 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 698e19fcab..6801d94f2f 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -415,9 +415,11 @@ bool HmdDisplayPlugin::setHandLaser(uint32_t hands, HandLaserMode mode, const ve void HmdDisplayPlugin::compositeExtra() { std::array handLasers; std::array renderHandPoses; + Transform uiModelTransform; withPresentThreadLock([&] { handLasers = _handLasers; renderHandPoses = _handPoses; + uiModelTransform = _uiModelTransform; }); // If neither hand laser is activated, exit @@ -457,7 +459,7 @@ void HmdDisplayPlugin::compositeExtra() { // Find the intersection of the laser with he UI and use it to scale the model matrix float distance; - if (!glm::intersectRaySphere(vec3(renderHandPoses[i][3]), castDirection, vec3(0), uiRadius * uiRadius, distance)) { + if (!glm::intersectRaySphere(vec3(renderHandPoses[i][3]), castDirection, uiModelTransform.getTranslation(), uiRadius * uiRadius, distance)) { continue; } diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index 738028d684..fada15d864 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -10,6 +10,7 @@ #include #include +#include #include "../OpenGLDisplayPlugin.h" @@ -59,6 +60,7 @@ protected: } }; + Transform _uiModelTransform; std::array _handLasers; std::array _handPoses; std::array _eyeOffsets; diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp index 61c4696f51..e188bea52e 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp @@ -9,6 +9,7 @@ #include #include +#include #include "OculusHelpers.h" @@ -40,6 +41,7 @@ bool OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) { }); withRenderThreadLock([&] { + _uiModelTransform = DependencyManager::get()->getModelTransform(); _handPoses = handPoses; _frameInfos[frameIndex] = _currentRenderFrameInfo; }); diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index d57dddf512..3a5f027013 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include "OpenVrHelpers.h" @@ -198,6 +199,7 @@ bool OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { } withRenderThreadLock([&] { + _uiModelTransform = DependencyManager::get()->getModelTransform(); // Make controller poses available to the presentation thread _handPoses = handPoses; _frameInfos[frameIndex] = _currentRenderFrameInfo; From 5759c2d29d1c058b8190ee0fff6b5662939fbc73 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Tue, 14 Jun 2016 11:28:44 -0700 Subject: [PATCH 051/171] final --- scripts/system/controllers/handControllerPointer.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index f5c0c4bb7c..7046ed16a5 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -364,13 +364,12 @@ Script.scriptEnding.connect(function () { overlays.forEach(Overlays.deleteOverlay); }); var visualizationIsShowing = false; // Not whether it desired, but simply whether it is. Just an optimziation. -var SYSTEM_LASER_DIRECTION = Vec3.normalize({x: 0, y: -1, z: -1}); // Guessing 45 degrees. +var SYSTEM_LASER_DIRECTION = {x: 0, y: 0, z: -1}; var systemLaserOn = false; function clearSystemLaser() { if (!systemLaserOn) { return; } - print('FIXME remove: disableHandLasers', BOTH_HUD_LASERS); HMD.disableHandLasers(BOTH_HUD_LASERS); systemLaserOn = false; } @@ -379,13 +378,9 @@ function turnOffVisualization(optionalEnableClicks) { // because we're showing c expireMouseCursor(); clearSystemLaser(); } else if (!systemLaserOn) { - print('FIXME remove: setHandLasers', activeHudLaser, true, JSON.stringify(LASER_COLOR_XYZW), JSON.stringify(SYSTEM_LASER_DIRECTION)); // If the active plugin doesn't implement hand lasers, show the mouse reticle instead. - /*Reticle.visible = !*/HMD.setHandLasers(activeHudLaser, true, LASER_COLOR_XYZW, SYSTEM_LASER_DIRECTION); - Reticle.visible = true; // FIXME: just for now, while hand lasers has the bug that requires this. - systemLaserOn = true; - } else { - Reticle.visible = true; + systemLaserOn = HMD.setHandLasers(activeHudLaser, true, LASER_COLOR_XYZW, SYSTEM_LASER_DIRECTION); + Reticle.visible = !systemLaserOn; } if (!visualizationIsShowing) { return; From 67aac09033d0e713adb1e41b4d5bd8dca2c607b2 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 14 Jun 2016 14:59:29 -0700 Subject: [PATCH 052/171] Set state debug flag to false --- scripts/system/controllers/handControllerGrab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 6daafa425f..6e39b16f82 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -19,7 +19,7 @@ Script.include("/~/system/libraries/utils.js"); // add lines where the hand ray picking is happening // var WANT_DEBUG = false; -var WANT_DEBUG_STATE = true; +var WANT_DEBUG_STATE = false; var WANT_DEBUG_SEARCH_NAME = null; // From ae65790bf3f0341319e25217cd61d4d802ebc579 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Tue, 14 Jun 2016 18:07:08 -0700 Subject: [PATCH 053/171] no laser (or mouse cursor) from hand controllers unless trigger squeezed --- scripts/system/controllers/handControllerPointer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index 7046ed16a5..7371680c32 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -377,7 +377,7 @@ function turnOffVisualization(optionalEnableClicks) { // because we're showing c if (!optionalEnableClicks) { expireMouseCursor(); clearSystemLaser(); - } else if (!systemLaserOn) { + } else if (!systemLaserOn && activeTrigger.state) { // If the active plugin doesn't implement hand lasers, show the mouse reticle instead. systemLaserOn = HMD.setHandLasers(activeHudLaser, true, LASER_COLOR_XYZW, SYSTEM_LASER_DIRECTION); Reticle.visible = !systemLaserOn; From b9754c85649a80d1332a573f120f8f11efc3bf13 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Tue, 14 Jun 2016 20:17:52 -0700 Subject: [PATCH 054/171] continue grabbing through overlays --- scripts/system/controllers/handControllerGrab.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 53c16f26c1..25889c9c1c 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -361,11 +361,15 @@ function MyController(hand) { var _this = this; + this.ignoreInput = function () { + return (_this.state <= STATE_HOLD_SEARCHING) && isIn2DMode(); + }; + this.update = function() { this.updateSmoothedTrigger(); - if (isIn2DMode()) { + if (_this.ignoreInput()) { _this.turnOffVisualizations(); return; } From 0b0977f362e6e114e37e7e4d20f5701a47f3c2a5 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Tue, 14 Jun 2016 18:25:01 -0700 Subject: [PATCH 055/171] Support keyboard suppresion, control suppresion while in keyboard mode --- interface/resources/controllers/vive.json | 15 ++- .../src/scripting/HMDScriptingInterface.cpp | 12 ++ .../src/scripting/HMDScriptingInterface.h | 17 ++- libraries/plugins/src/plugins/DisplayPlugin.h | 126 ++++++++++-------- libraries/shared/src/shared/Bilateral.h | 49 +++++++ plugins/oculus/src/OculusHelpers.cpp | 8 ++ plugins/openvr/src/OpenVrDisplayPlugin.cpp | 24 ++++ plugins/openvr/src/OpenVrDisplayPlugin.h | 6 + plugins/openvr/src/ViveControllerManager.cpp | 6 + 9 files changed, 206 insertions(+), 57 deletions(-) create mode 100644 libraries/shared/src/shared/Bilateral.h diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 95c8f5a734..677ec1ccfc 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -4,15 +4,24 @@ { "from": "Vive.LY", "when": "Vive.LSY", "filters": ["invert"], "to": "Standard.LY" }, { "from": "Vive.LX", "when": "Vive.LSX", "to": "Standard.LX" }, - { "from": "Vive.LT", "to": "Standard.LT" }, + { + "from": "Vive.LT", "to": "Standard.LT", + "filters": [ + { "type": "deadZone", "min": 0.05 }, + ] + }, { "from": "Vive.LeftGrip", "to": "Standard.LeftGrip" }, { "from": "Vive.LS", "to": "Standard.LS" }, { "from": "Vive.LSTouch", "to": "Standard.LSTouch" }, { "from": "Vive.RY", "when": "Vive.RSY", "filters": ["invert"], "to": "Standard.RY" }, { "from": "Vive.RX", "when": "Vive.RSX", "to": "Standard.RX" }, - - { "from": "Vive.RT", "to": "Standard.RT" }, + { + "from": "Vive.RT", "to": "Standard.RT", + "filters": [ + { "type": "deadZone", "min": 0.05 }, + ] + }, { "from": "Vive.RightGrip", "to": "Standard.RightGrip" }, { "from": "Vive.RS", "to": "Standard.RS" }, { "from": "Vive.RSTouch", "to": "Standard.RSTouch" }, diff --git a/interface/src/scripting/HMDScriptingInterface.cpp b/interface/src/scripting/HMDScriptingInterface.cpp index 02840a9775..4dd43dfdf1 100644 --- a/interface/src/scripting/HMDScriptingInterface.cpp +++ b/interface/src/scripting/HMDScriptingInterface.cpp @@ -115,3 +115,15 @@ bool HMDScriptingInterface::setHandLasers(int hands, bool enabled, const glm::ve void HMDScriptingInterface::disableHandLasers(int hands) const { qApp->getActiveDisplayPlugin()->setHandLaser(hands, DisplayPlugin::HandLaserMode::None); } + +bool HMDScriptingInterface::suppressKeyboard() { + return qApp->getActiveDisplayPlugin()->suppressKeyboard(); +} + +void HMDScriptingInterface::unsuppressKeyboard() { + qApp->getActiveDisplayPlugin()->unsuppressKeyboard(); +} + +bool HMDScriptingInterface::isKeyboardVisible() { + return qApp->getActiveDisplayPlugin()->isKeyboardVisible(); +} diff --git a/interface/src/scripting/HMDScriptingInterface.h b/interface/src/scripting/HMDScriptingInterface.h index c55320ca83..e2985b1ce5 100644 --- a/interface/src/scripting/HMDScriptingInterface.h +++ b/interface/src/scripting/HMDScriptingInterface.h @@ -1,4 +1,4 @@ -// + // HMDScriptingInterface.h // interface/src/scripting // @@ -12,6 +12,8 @@ #ifndef hifi_HMDScriptingInterface_h #define hifi_HMDScriptingInterface_h +#include + #include class QScriptContext; class QScriptEngine; @@ -38,6 +40,19 @@ public: Q_INVOKABLE QString preferredAudioOutput() const; Q_INVOKABLE bool setHandLasers(int hands, bool enabled, const glm::vec4& color, const glm::vec3& direction) const; Q_INVOKABLE void disableHandLasers(int hands) const; + /// Suppress the activation of any on-screen keyboard so that a script operation will + /// not be interrupted by a keyboard popup + /// Returns false if there is already an active keyboard displayed. + /// Clients should re-enable the keyboard when the operation is complete and ensure + /// that they balance any call to suppressKeyboard() that returns true with a corresponding + /// call to unsuppressKeyboard() within a reasonable amount of time + Q_INVOKABLE bool suppressKeyboard(); + + /// Enable the keyboard following a suppressKeyboard call + Q_INVOKABLE void unsuppressKeyboard(); + + /// Query the display plugin to determine the current VR keyboard visibility + Q_INVOKABLE bool isKeyboardVisible(); public: HMDScriptingInterface(); diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index 9cb4e071f3..5f659f45f9 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -16,16 +16,18 @@ #include #include #include -class QImage; #include #include +#include #include "Plugin.h" +class QImage; + enum Eye { - Left, - Right + Left = bilateral::Side::Left, + Right = bilateral::Side::Right }; /* @@ -56,7 +58,73 @@ namespace gpu { using TexturePointer = std::shared_ptr; } -class DisplayPlugin : public Plugin { +// Stereo display functionality +// TODO move out of this file don't derive DisplayPlugin from this. Instead use dynamic casting when +// displayPlugin->isStereo returns true +class StereoDisplay { +public: + // Stereo specific methods + virtual glm::mat4 getEyeProjection(Eye eye, const glm::mat4& baseProjection) const { + return baseProjection; + } + + virtual glm::mat4 getCullingProjection(const glm::mat4& baseProjection) const { + return baseProjection; + } + + virtual float getIPD() const { return AVERAGE_HUMAN_IPD; } +}; + +// HMD display functionality +// TODO move out of this file don't derive DisplayPlugin from this. Instead use dynamic casting when +// displayPlugin->isHmd returns true +class HmdDisplay : public StereoDisplay { +public: + // HMD specific methods + // TODO move these into another class? + virtual glm::mat4 getEyeToHeadTransform(Eye eye) const { + static const glm::mat4 transform; return transform; + } + + // returns a copy of the most recent head pose, computed via updateHeadPose + virtual glm::mat4 getHeadPose() const { + return glm::mat4(); + } + + // Needed for timewarp style features + virtual void setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm::mat4& pose) { + // NOOP + } + + virtual void abandonCalibration() {} + + virtual void resetSensors() {} + + enum Hand { + LeftHand = 0x01, + RightHand = 0x02, + }; + + enum class HandLaserMode { + None, // Render no hand lasers + Overlay, // Render hand lasers only if they intersect with the UI layer, and stop at the UI layer + }; + + virtual bool setHandLaser( + uint32_t hands, // Bits from the Hand enum + HandLaserMode mode, // Mode in which to render + const vec4& color = vec4(1), // The color of the rendered laser + const vec3& direction = vec3(0, 0, -1) // The direction in which to render the hand lasers + ) { + return false; + } + + virtual bool suppressKeyboard() { return false; } + virtual void unsuppressKeyboard() {}; + virtual bool isKeyboardVisible() { return false; } +}; + +class DisplayPlugin : public Plugin, public HmdDisplay { Q_OBJECT using Parent = Plugin; public: @@ -117,42 +185,12 @@ public: return QRect(0, 0, recommendedSize.x, recommendedSize.y); } - // Stereo specific methods - virtual glm::mat4 getEyeProjection(Eye eye, const glm::mat4& baseProjection) const { - return baseProjection; - } - - virtual glm::mat4 getCullingProjection(const glm::mat4& baseProjection) const { - return baseProjection; - } - - // Fetch the most recently displayed image as a QImage virtual QImage getScreenshot() const = 0; - // HMD specific methods - // TODO move these into another class? - virtual glm::mat4 getEyeToHeadTransform(Eye eye) const { - static const glm::mat4 transform; return transform; - } - // will query the underlying hmd api to compute the most recent head pose virtual bool beginFrameRender(uint32_t frameIndex) { return true; } - // returns a copy of the most recent head pose, computed via updateHeadPose - virtual glm::mat4 getHeadPose() const { - return glm::mat4(); - } - - // Needed for timewarp style features - virtual void setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm::mat4& pose) { - // NOOP - } - - virtual float getIPD() const { return AVERAGE_HUMAN_IPD; } - - virtual void abandonCalibration() {} - virtual void resetSensors() {} virtual float devicePixelRatio() { return 1.0f; } // Rate at which we present to the display device virtual float presentRate() const { return -1.0f; } @@ -160,6 +198,7 @@ public: virtual float newFramePresentRate() const { return -1.0f; } // Rate at which rendered frames are being skipped virtual float droppedFrameRate() const { return -1.0f; } + uint32_t presentCount() const { return _presentedFrameIndex; } // Time since last call to incrementPresentCount (only valid if DEBUG_PAINT_DELAY is defined) int64_t getPaintDelayUsecs() const; @@ -168,25 +207,6 @@ public: static const QString& MENU_PATH(); - enum Hand { - LeftHand = 0x01, - RightHand = 0x02, - }; - - enum class HandLaserMode { - None, // Render no hand lasers - Overlay, // Render hand lasers only if they intersect with the UI layer, and stop at the UI layer - }; - - virtual bool setHandLaser( - uint32_t hands, // Bits from the Hand enum - HandLaserMode mode, // Mode in which to render - const vec4& color = vec4(1), // The color of the rendered laser - const vec3& direction = vec3(0, 0, -1) // The direction in which to render the hand lasers - ) { - return false; - } - signals: void recommendedFramebufferSizeChanged(const QSize & size); diff --git a/libraries/shared/src/shared/Bilateral.h b/libraries/shared/src/shared/Bilateral.h new file mode 100644 index 0000000000..c4daf60177 --- /dev/null +++ b/libraries/shared/src/shared/Bilateral.h @@ -0,0 +1,49 @@ +// +// Created by Bradley Austin Davis 2015/10/09 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once + +namespace bilateral { + enum class Side { + Left = 0, + Right = 1 + }; + + using Indices = Side; + + enum class Bits { + Left = 0x01, + Right = 0x02 + }; + + inline uint8_t bit(Side side) { + switch (side) { + case Side::Left: + return 0x01; + case Side::Right: + return 0x02; + } + return UINT8_MAX; + } + + inline uint8_t index(Side side) { + switch (side) { + case Side::Left: + return 0; + case Side::Right: + return 1; + } + return UINT8_MAX; + } + + template + void for_each_side(F f) { + f(Side::Left); + f(Side::Right); + } +} diff --git a/plugins/oculus/src/OculusHelpers.cpp b/plugins/oculus/src/OculusHelpers.cpp index 705c0a0781..49c14c8d66 100644 --- a/plugins/oculus/src/OculusHelpers.cpp +++ b/plugins/oculus/src/OculusHelpers.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -53,6 +54,13 @@ bool oculusAvailable() { static std::once_flag once; static bool result { false }; std::call_once(once, [&] { + + static const QString DEBUG_FLAG("HIFI_DEBUG_OPENVR"); + static bool enableDebugOpenVR = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); + if (enableDebugOpenVR) { + return; + } + ovrDetectResult detect = ovr_Detect(0); if (!detect.IsOculusServiceRunning || !detect.IsOculusHMDConnected) { return; diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 3a5f027013..acbf386980 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -253,3 +253,27 @@ void OpenVrDisplayPlugin::updatePresentPose() { mat3 presentRotation(_currentPresentFrameInfo.rawPresentPose); _currentPresentFrameInfo.presentReprojection = glm::mat3(glm::inverse(renderRotation) * presentRotation); } + +bool OpenVrDisplayPlugin::suppressKeyboard() { + if (isOpenVrKeyboardShown()) { + return false; + } + if (!_keyboardSupressionCount.fetch_add(1)) { + disableOpenVrKeyboard(); + } + return true; +} + +void OpenVrDisplayPlugin::unsuppressKeyboard() { + if (_keyboardSupressionCount == 0) { + qWarning() << "Attempted to unsuppress a keyboard that was not suppressed"; + return; + } + if (1 == _keyboardSupressionCount.fetch_sub(1)) { + enableOpenVrKeyboard(); + } +} + +bool OpenVrDisplayPlugin::isKeyboardVisible() { + return isOpenVrKeyboardShown(); +} diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.h b/plugins/openvr/src/OpenVrDisplayPlugin.h index ee693b8091..b22fadb52c 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.h +++ b/plugins/openvr/src/OpenVrDisplayPlugin.h @@ -30,6 +30,10 @@ public: bool beginFrameRender(uint32_t frameIndex) override; void cycleDebugOutput() override { _lockCurrentTexture = !_lockCurrentTexture; } + bool suppressKeyboard() override; + void unsuppressKeyboard() override; + bool isKeyboardVisible() override; + protected: bool internalActivate() override; void internalDeactivate() override; @@ -39,8 +43,10 @@ protected: bool isHmdMounted() const override; void postPreview() override; + private: vr::IVRSystem* _system { nullptr }; std::atomic _hmdActivityLevel { vr::k_EDeviceActivityLevel_Unknown }; + std::atomic _keyboardSupressionCount{ 0 }; static const QString NAME; }; diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 69c98d48c3..631efca4a2 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -240,6 +240,12 @@ void ViveControllerManager::InputDevice::update(float deltaTime, const controlle _poseStateMap.clear(); _buttonPressedMap.clear(); + // While the keyboard is open, we defer strictly to the keyboard values + if (isOpenVrKeyboardShown()) { + _axisStateMap.clear(); + return; + } + PerformanceTimer perfTimer("ViveControllerManager::update"); auto leftHandDeviceIndex = _system->GetTrackedDeviceIndexForControllerRole(vr::TrackedControllerRole_LeftHand); From 60035486d3e2835708756e7bdcc5165138f34cff Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Wed, 15 Jun 2016 09:35:24 -0700 Subject: [PATCH 056/171] Build fix --- libraries/plugins/src/plugins/DisplayPlugin.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index 5f659f45f9..a65e8e7371 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -26,8 +26,8 @@ class QImage; enum Eye { - Left = bilateral::Side::Left, - Right = bilateral::Side::Right + Left = (int)bilateral::Side::Left, + Right = (int)bilateral::Side::Right }; /* From 6366ca7508ce77de4230900141298669f3c49fcf Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 15 Jun 2016 11:08:59 -0700 Subject: [PATCH 057/171] Go red for full trigger. --- .../system/controllers/handControllerPointer.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index 7371680c32..5873040dd9 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -349,7 +349,8 @@ clickMapping.enable(); // Same properties as handControllerGrab search sphere var BALL_SIZE = 0.011; var BALL_ALPHA = 0.5; -var LASER_COLOR_XYZW = {x: 10 / 255, y: 10 / 255, z: 255 / 255, w: BALL_ALPHA}; +var LASER_SEARCH_COLOR_XYZW = {x: 10 / 255, y: 10 / 255, z: 255 / 255, w: BALL_ALPHA}; +var LASER_TRIGGER_COLOR_XYZW = {x: 250 / 255, y: 10 / 255, z: 10 / 255, w: BALL_ALPHA}; var fakeProjectionBall = Overlays.addOverlay("sphere", { size: 5 * BALL_SIZE, color: {red: 255, green: 10, blue: 10}, @@ -373,15 +374,24 @@ function clearSystemLaser() { HMD.disableHandLasers(BOTH_HUD_LASERS); systemLaserOn = false; } +function setColoredLaser() { // answer trigger state if lasers supported, else falsey. + var color = (activeTrigger.state === 'full') ? LASER_TRIGGER_COLOR_XYZW : LASER_SEARCH_COLOR_XYZW; + return HMD.setHandLasers(activeHudLaser, true, color, SYSTEM_LASER_DIRECTION) && activeTrigger.state; + +} function turnOffVisualization(optionalEnableClicks) { // because we're showing cursor on HUD if (!optionalEnableClicks) { expireMouseCursor(); clearSystemLaser(); - } else if (!systemLaserOn && activeTrigger.state) { + } else if (activeTrigger.state && (!systemLaserOn || (systemLaserOn !== activeTrigger.state))) { // last=>wrong color // If the active plugin doesn't implement hand lasers, show the mouse reticle instead. - systemLaserOn = HMD.setHandLasers(activeHudLaser, true, LASER_COLOR_XYZW, SYSTEM_LASER_DIRECTION); + systemLaserOn = setColoredLaser(); Reticle.visible = !systemLaserOn; + } else if ((systemLaserOn || Reticle.visible) && !activeTrigger.state) { + clearSystemLaser(); + Reticle.visible = false; } + if (!visualizationIsShowing) { return; } From bc838ac0b476f586723ecf81a8e8cb86d647e8d6 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Wed, 15 Jun 2016 11:41:28 -0700 Subject: [PATCH 058/171] Fix vive json --- interface/resources/controllers/vive.json | 5 ++--- interface/src/scripting/HMDScriptingInterface.h | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 677ec1ccfc..6be672900a 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -3,11 +3,10 @@ "channels": [ { "from": "Vive.LY", "when": "Vive.LSY", "filters": ["invert"], "to": "Standard.LY" }, { "from": "Vive.LX", "when": "Vive.LSX", "to": "Standard.LX" }, - { "from": "Vive.LT", "to": "Standard.LT", "filters": [ - { "type": "deadZone", "min": 0.05 }, + { "type": "deadZone", "min": 0.05 } ] }, { "from": "Vive.LeftGrip", "to": "Standard.LeftGrip" }, @@ -19,7 +18,7 @@ { "from": "Vive.RT", "to": "Standard.RT", "filters": [ - { "type": "deadZone", "min": 0.05 }, + { "type": "deadZone", "min": 0.05 } ] }, { "from": "Vive.RightGrip", "to": "Standard.RightGrip" }, diff --git a/interface/src/scripting/HMDScriptingInterface.h b/interface/src/scripting/HMDScriptingInterface.h index e2985b1ce5..2739522adf 100644 --- a/interface/src/scripting/HMDScriptingInterface.h +++ b/interface/src/scripting/HMDScriptingInterface.h @@ -33,12 +33,13 @@ public: Q_INVOKABLE glm::vec3 calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction) const; Q_INVOKABLE glm::vec2 overlayFromWorldPoint(const glm::vec3& position) const; Q_INVOKABLE glm::vec3 worldPointFromOverlay(const glm::vec2& overlay) const; - Q_INVOKABLE glm::vec2 sphericalToOverlay(const glm::vec2 & sphericalPos) const; Q_INVOKABLE glm::vec2 overlayToSpherical(const glm::vec2 & overlayPos) const; Q_INVOKABLE QString preferredAudioInput() const; Q_INVOKABLE QString preferredAudioOutput() const; + Q_INVOKABLE bool setHandLasers(int hands, bool enabled, const glm::vec4& color, const glm::vec3& direction) const; + Q_INVOKABLE void disableHandLasers(int hands) const; /// Suppress the activation of any on-screen keyboard so that a script operation will /// not be interrupted by a keyboard popup From 2c7642a3674aa100e46c2dd4a6ba2dc6f6ba5e80 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 15 Jun 2016 12:28:42 -0700 Subject: [PATCH 059/171] Do not make ReticleClick when we enter an overlay with the trigger pressed. --- .../controllers/handControllerPointer.js | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index 7046ed16a5..2faf2cb361 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -48,9 +48,10 @@ function TimeLock(expiration) { } var handControllerLockOut = new TimeLock(2000); -function Trigger() { +function Trigger(label) { // This part is copied and adapted from handControllerGrab.js. Maybe we should refactor this. var that = this; + that.label = label; that.TRIGGER_SMOOTH_RATIO = 0.1; // Time averaging of trigger - 0.0 disables smoothing that.TRIGGER_ON_VALUE = 0.4; // Squeezed just enough to activate search or near grab that.TRIGGER_GRAB_VALUE = 0.85; // Squeezed far enough to complete distant grab @@ -301,8 +302,8 @@ setupHandler(Controller.mouseDoublePressEvent, onMouseClick); // CONTROLLER MAPPING --------- // -var leftTrigger = new Trigger(); -var rightTrigger = new Trigger(); +var leftTrigger = new Trigger('left'); +var rightTrigger = new Trigger('right'); var activeTrigger = rightTrigger; var activeHand = Controller.Standard.RightHand; var LEFT_HUD_LASER = 1; @@ -336,8 +337,26 @@ Script.scriptEnding.connect(clickMapping.disable); clickMapping.from(Controller.Standard.RT).peek().to(rightTrigger.triggerPress); clickMapping.from(Controller.Standard.LT).peek().to(leftTrigger.triggerPress); // Full smoothed trigger is a click. -clickMapping.from(rightTrigger.full).when(isPointingAtOverlay).to(Controller.Actions.ReticleClick); -clickMapping.from(leftTrigger.full).when(isPointingAtOverlay).to(Controller.Actions.ReticleClick); +function isPointingAtOverlayStartedNonFullTrigger(trigger) { + // true if isPointingAtOverlay AND we were NOT full triggered when we became so. + // The idea is to not count clicks when we're full-triggering and reach the edge of a window. + var lockedIn = false; + return function () { + if (trigger !== activeTrigger) { + return lockedIn = false; + } + if (!isPointingAtOverlay()) { + return lockedIn = false; + } + if (lockedIn) { + return true; + } + lockedIn = !trigger.full(); + return lockedIn; + } +} +clickMapping.from(rightTrigger.full).when(isPointingAtOverlayStartedNonFullTrigger(rightTrigger)).to(Controller.Actions.ReticleClick); +clickMapping.from(leftTrigger.full).when(isPointingAtOverlayStartedNonFullTrigger(leftTrigger)).to(Controller.Actions.ReticleClick); clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(Controller.Actions.ContextMenu); clickMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(Controller.Actions.ContextMenu); // Partial smoothed trigger is activation. From 1e3b20b66d9445f49368573902f29fd26cc5df8b Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 15 Jun 2016 15:06:36 -0700 Subject: [PATCH 060/171] Explicit check enumerated states instead of relying on order, per feedback. --- scripts/system/controllers/handControllerGrab.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 25889c9c1c..ea2c4c1653 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -361,8 +361,9 @@ function MyController(hand) { var _this = this; + var suppressedIn2D = [STATE_OFF, STATE_SEARCHING, STATE_HOLD_SEARCHING]; this.ignoreInput = function () { - return (_this.state <= STATE_HOLD_SEARCHING) && isIn2DMode(); + return (-1 !== suppressedIn2D.indexOf(_this.state)) && isIn2DMode(); }; this.update = function() { From 87dc6649ceacffb819516125af4ee3d284d6a997 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Wed, 15 Jun 2016 15:16:29 -0700 Subject: [PATCH 061/171] Better track keyboard open state, fix text replacement --- plugins/openvr/src/OpenVrHelpers.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index 4b4eef0538..4a756e12b0 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -164,12 +164,13 @@ void finishOpenVrKeyboardInput() { auto offscreenUi = DependencyManager::get(); auto chars = _overlay->GetKeyboardText(textArray, 8192); auto newText = QString(QByteArray(textArray, chars)); - // TODO modify the new text to match the possible input hints: - // ImhDigitsOnly ImhFormattedNumbersOnly ImhUppercaseOnly ImhLowercaseOnly - // ImhDialableCharactersOnly ImhEmailCharactersOnly ImhUrlCharactersOnly ImhLatinOnly - QInputMethodEvent event(_existingText, QList()); - event.setCommitString(newText, 0, _existingText.size()); - qApp->sendEvent(_keyboardFocusObject, &event); + _keyboardFocusObject->setProperty("text", newText); + //// TODO modify the new text to match the possible input hints: + //// ImhDigitsOnly ImhFormattedNumbersOnly ImhUppercaseOnly ImhLowercaseOnly + //// ImhDialableCharactersOnly ImhEmailCharactersOnly ImhUrlCharactersOnly ImhLatinOnly + //QInputMethodEvent event(_existingText, QList()); + //event.setCommitString(newText, 0, _existingText.size()); + //qApp->sendEvent(_keyboardFocusObject, &event); // Simulate an enter press on the top level window to trigger the action if (0 == (_currentHints & Qt::ImhMultiLine)) { qApp->sendEvent(offscreenUi->getWindow(), &QKeyEvent(QEvent::KeyPress, Qt::Key_Return, Qt::KeyboardModifiers(), QString("\n"))); @@ -233,6 +234,11 @@ void handleOpenVrEvents() { case vr::VREvent_KeyboardDone: finishOpenVrKeyboardInput(); + + // FALL THROUGH + case vr::VREvent_KeyboardClosed: + _keyboardFocusObject = nullptr; + _keyboardShown = false; break; default: From 6f671b7cde167005965a47539e048b09bf8f5171 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 15 Jun 2016 15:17:34 -0700 Subject: [PATCH 062/171] see comment. --- scripts/system/controllers/handControllerGrab.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index ea2c4c1653..316fe0ff88 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -363,15 +363,17 @@ function MyController(hand) { var suppressedIn2D = [STATE_OFF, STATE_SEARCHING, STATE_HOLD_SEARCHING]; this.ignoreInput = function () { - return (-1 !== suppressedIn2D.indexOf(_this.state)) && isIn2DMode(); + // We've made the decision to use 'this' for new code, even though it is fragile, + // in order to keep/ the code uniform without making any no-op line changes. + return (-1 !== suppressedIn2D.indexOf(this.state)) && isIn2DMode(); }; this.update = function() { this.updateSmoothedTrigger(); - if (_this.ignoreInput()) { - _this.turnOffVisualizations(); + if (this.ignoreInput()) { + this.turnOffVisualizations(); return; } From 6ad1008a566f179e7752af7456c5d3a4a3e55efa Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Wed, 15 Jun 2016 13:42:48 -0700 Subject: [PATCH 063/171] Allow explicit overlay alpha from scripts --- .../scripting/DesktopScriptingInterface.cpp | 6 +++++ .../src/scripting/DesktopScriptingInterface.h | 2 ++ .../src/display-plugins/CompositorHelper.h | 6 +++++ .../display-plugins/OpenGLDisplayPlugin.cpp | 27 +++++++++++++++++++ .../src/display-plugins/OpenGLDisplayPlugin.h | 8 +++++- .../display-plugins/hmd/HmdDisplayPlugin.cpp | 9 ++++++- .../oculus/src/OculusBaseDisplayPlugin.cpp | 2 +- .../src/OculusLegacyDisplayPlugin.cpp | 3 ++- plugins/openvr/src/OpenVrDisplayPlugin.cpp | 2 +- 9 files changed, 60 insertions(+), 5 deletions(-) diff --git a/interface/src/scripting/DesktopScriptingInterface.cpp b/interface/src/scripting/DesktopScriptingInterface.cpp index 843a40348e..f7bc8afe36 100644 --- a/interface/src/scripting/DesktopScriptingInterface.cpp +++ b/interface/src/scripting/DesktopScriptingInterface.cpp @@ -16,6 +16,7 @@ #include "Application.h" #include "MainWindow.h" +#include int DesktopScriptingInterface::getWidth() { QSize size = qApp->getWindow()->windowHandle()->screen()->virtualSize(); @@ -25,3 +26,8 @@ int DesktopScriptingInterface::getHeight() { QSize size = qApp->getWindow()->windowHandle()->screen()->virtualSize(); return size.height(); } + +void DesktopScriptingInterface::setOverlayAlpha(float alpha) { + qApp->getApplicationCompositor().setAlpha(alpha); +} + diff --git a/interface/src/scripting/DesktopScriptingInterface.h b/interface/src/scripting/DesktopScriptingInterface.h index be4eaadbfb..8da502cb11 100644 --- a/interface/src/scripting/DesktopScriptingInterface.h +++ b/interface/src/scripting/DesktopScriptingInterface.h @@ -22,6 +22,8 @@ class DesktopScriptingInterface : public QObject, public Dependency { Q_PROPERTY(int height READ getHeight) // Physical height of screen(s) including task bars and system menus public: + Q_INVOKABLE void setOverlayAlpha(float alpha); + int getWidth(); int getHeight(); }; diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.h b/libraries/display-plugins/src/display-plugins/CompositorHelper.h index 868beec53f..2a3dd0c852 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.h +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.h @@ -38,6 +38,7 @@ const float MAGNIFY_MULT = 2.0f; class CompositorHelper : public QObject, public Dependency { Q_OBJECT + Q_PROPERTY(float alpha READ getAlpha WRITE setAlpha NOTIFY alphaChanged) Q_PROPERTY(bool reticleOverDesktop READ getReticleOverDesktop WRITE setReticleOverDesktop) public: static const uvec2 VIRTUAL_SCREEN_SIZE; @@ -74,6 +75,9 @@ public: void setModelTransform(const Transform& transform) { _modelTransform = transform; } const Transform& getModelTransform() const { return _modelTransform; } + float getAlpha() const { return _alpha; } + void setAlpha(float alpha) { if (alpha != _alpha) { emit alphaChanged(); _alpha = alpha; } } + bool getReticleVisible() const { return _reticleVisible; } void setReticleVisible(bool visible) { _reticleVisible = visible; } @@ -109,6 +113,7 @@ public: signals: void allowMouseCaptureChanged(); + void alphaChanged(); protected slots: void sendFakeMouseEvent(); @@ -134,6 +139,7 @@ private: float _textureFov { VIRTUAL_UI_TARGET_FOV.y }; float _textureAspectRatio { VIRTUAL_UI_ASPECT_RATIO }; + float _alpha { 1.0f }; float _hmdUIRadius { 1.0f }; int _previousBorderWidth { -1 }; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 23c836b3de..b49b41d8b2 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -273,11 +273,23 @@ bool OpenGLDisplayPlugin::activate() { _container->makeRenderingContextCurrent(); #endif + auto compositorHelper = DependencyManager::get(); + connect(compositorHelper.data(), &CompositorHelper::alphaChanged, [this] { + auto compositorHelper = DependencyManager::get(); + auto animation = new QPropertyAnimation(this, "overlayAlpha"); + animation->setDuration(200); + animation->setEndValue(compositorHelper->getAlpha()); + animation->start(); + }); + return DisplayPlugin::activate(); } void OpenGLDisplayPlugin::deactivate() { + auto compositorHelper = DependencyManager::get(); + disconnect(compositorHelper.data()); + #if THREADED_PRESENT auto presentThread = DependencyManager::get(); // Does not return until the GL transition has completeed @@ -445,6 +457,8 @@ void OpenGLDisplayPlugin::compositeOverlay() { auto compositorHelper = DependencyManager::get(); useProgram(_program); + // set the alpha + Uniform(*_program, _alphaUniform).Set(_compositeOverlayAlpha); // check the alpha // Overlay draw if (isStereo()) { @@ -458,6 +472,8 @@ void OpenGLDisplayPlugin::compositeOverlay() { Uniform(*_program, _mvpUniform).Set(mat4()); drawUnitQuad(); } + // restore the alpha + Uniform(*_program, _alphaUniform).Set(1.0); } void OpenGLDisplayPlugin::compositePointer() { @@ -465,6 +481,8 @@ void OpenGLDisplayPlugin::compositePointer() { auto compositorHelper = DependencyManager::get(); useProgram(_program); + // set the alpha + Uniform(*_program, _alphaUniform).Set(_compositeOverlayAlpha); Uniform(*_program, _mvpUniform).Set(compositorHelper->getReticleTransform(glm::mat4())); if (isStereo()) { for_each_eye([&](Eye eye) { @@ -475,6 +493,8 @@ void OpenGLDisplayPlugin::compositePointer() { drawUnitQuad(); } Uniform(*_program, _mvpUniform).Set(mat4()); + // restore the alpha + Uniform(*_program, _alphaUniform).Set(1.0); } void OpenGLDisplayPlugin::compositeScene() { @@ -682,3 +702,10 @@ void OpenGLDisplayPlugin::assertIsRenderThread() const { void OpenGLDisplayPlugin::assertIsPresentThread() const { Q_ASSERT(QThread::currentThread() == _presentThread); } + +bool OpenGLDisplayPlugin::beginFrameRender(uint32_t frameIndex) { + withRenderThreadLock([&] { + _compositeOverlayAlpha = _overlayAlpha; + }); + return Parent::beginFrameRender(frameIndex); +} diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 69653b8c76..d8d4d8ff8c 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -24,6 +24,9 @@ #define THREADED_PRESENT 1 class OpenGLDisplayPlugin : public DisplayPlugin { + Q_OBJECT + Q_PROPERTY(float overlayAlpha MEMBER _overlayAlpha) + using Parent = DisplayPlugin; protected: using Mutex = std::mutex; using Lock = std::unique_lock; @@ -60,6 +63,7 @@ public: float droppedFrameRate() const override; + bool beginFrameRender(uint32_t frameIndex) override; protected: #if THREADED_PRESENT friend class PresentThread; @@ -115,7 +119,8 @@ protected: RateCounter<> _presentRate; QMap _sceneTextureToFrameIndexMap; uint32_t _currentPresentFrameIndex { 0 }; - + float _compositeOverlayAlpha{ 1.0f }; + gpu::TexturePointer _currentSceneTexture; gpu::TexturePointer _currentOverlayTexture; @@ -157,6 +162,7 @@ private: // be serialized through this mutex mutable Mutex _presentMutex; ProgramPtr _activeProgram; + float _overlayAlpha{ 1.0f }; }; diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 6801d94f2f..efdb68226b 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -287,6 +287,8 @@ void HmdDisplayPlugin::compositeOverlay() { glm::mat4 modelMat = compositorHelper->getModelTransform().getMatrix(); useProgram(_program); + // set the alpha + Uniform(*_program, _alphaUniform).Set(_compositeOverlayAlpha); _sphereSection->Use(); for_each_eye([&](Eye eye) { eyeViewport(eye); @@ -295,6 +297,8 @@ void HmdDisplayPlugin::compositeOverlay() { Uniform(*_program, _mvpUniform).Set(mvp); _sphereSection->Draw(); }); + // restore the alpha + Uniform(*_program, _alphaUniform).Set(1.0); } void HmdDisplayPlugin::compositePointer() { @@ -302,8 +306,9 @@ void HmdDisplayPlugin::compositePointer() { auto compositorHelper = DependencyManager::get(); - // check the alpha useProgram(_program); + // set the alpha + Uniform(*_program, _alphaUniform).Set(_compositeOverlayAlpha); // Mouse pointer _plane->Use(); @@ -317,6 +322,8 @@ void HmdDisplayPlugin::compositePointer() { Uniform(*_program, _mvpUniform).Set(mvp); _plane->Draw(); }); + // restore the alpha + Uniform(*_program, _alphaUniform).Set(1.0); } diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp index e188bea52e..e26a48b89c 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp @@ -45,7 +45,7 @@ bool OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) { _handPoses = handPoses; _frameInfos[frameIndex] = _currentRenderFrameInfo; }); - return true; + return Parent::beginFrameRender(frameIndex); } bool OculusBaseDisplayPlugin::isSupported() const { diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp index 8d2bc24177..699891deaa 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp @@ -41,6 +41,7 @@ void OculusLegacyDisplayPlugin::resetSensors() { } bool OculusLegacyDisplayPlugin::beginFrameRender(uint32_t frameIndex) { + _currentRenderFrameInfo = FrameInfo(); _currentRenderFrameInfo.predictedDisplayTime = _currentRenderFrameInfo.sensorSampleTime = ovr_GetTimeInSeconds(); _trackingState = ovrHmd_GetTrackingState(_hmd, _currentRenderFrameInfo.predictedDisplayTime); @@ -48,7 +49,7 @@ bool OculusLegacyDisplayPlugin::beginFrameRender(uint32_t frameIndex) { withRenderThreadLock([&]{ _frameInfos[frameIndex] = _currentRenderFrameInfo; }); - return true; + return Parent::beginFrameRender(frameIndex); } bool OculusLegacyDisplayPlugin::isSupported() const { diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index acbf386980..a92c930cbf 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -204,7 +204,7 @@ bool OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { _handPoses = handPoses; _frameInfos[frameIndex] = _currentRenderFrameInfo; }); - return true; + return Parent::beginFrameRender(frameIndex); } void OpenVrDisplayPlugin::hmdPresent() { From 33e9caa63687f0682d3eff619e963b8952a23b2a Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Wed, 15 Jun 2016 17:20:18 -0700 Subject: [PATCH 064/171] Simplify overlay conductor, menu interation --- .../resources/qml/hifi/ToggleHudButton.qml | 4 +- interface/src/Application.cpp | 13 +- interface/src/ui/OverlayConductor.cpp | 112 +++++------------- interface/src/ui/OverlayConductor.h | 23 +--- 4 files changed, 42 insertions(+), 110 deletions(-) diff --git a/interface/resources/qml/hifi/ToggleHudButton.qml b/interface/resources/qml/hifi/ToggleHudButton.qml index 6454f42305..63c056b352 100644 --- a/interface/resources/qml/hifi/ToggleHudButton.qml +++ b/interface/resources/qml/hifi/ToggleHudButton.qml @@ -16,6 +16,8 @@ Window { height: 50 clip: true visible: true + // Disable this window from being able to call 'desktop.raise() and desktop.showDesktop' + activator: Item {} Item { width: 50 @@ -28,7 +30,7 @@ Window { MouseArea { readonly property string overlayMenuItem: "Overlays" anchors.fill: parent - onClicked: MenuInterface.setIsOptionChecked(overlayMenuItem, !MenuInterface.isOptionChecked(overlayMenuItem)) + onClicked: MenuInterface.setIsOptionChecked(overlayMenuItem, !MenuInterface.isOptionChecked(overlayMenuItem)); } } } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ec86f5f1e0..29238cf153 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2716,7 +2716,6 @@ void Application::idle(float nsecsElapsed) { if (firstIdle) { firstIdle = false; connect(offscreenUi.data(), &OffscreenUi::showDesktop, this, &Application::showDesktop); - _overlayConductor.setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Overlays)); } PROFILE_RANGE(__FUNCTION__); @@ -3217,13 +3216,13 @@ void Application::updateThreads(float deltaTime) { } void Application::toggleOverlays() { - auto newOverlaysVisible = !_overlayConductor.getEnabled(); - Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, newOverlaysVisible); - _overlayConductor.setEnabled(newOverlaysVisible); + auto menu = Menu::getInstance(); + menu->setIsOptionChecked(MenuOption::Overlays, menu->isOptionChecked(MenuOption::Overlays)); } void Application::setOverlaysVisible(bool visible) { - _overlayConductor.setEnabled(visible); + auto menu = Menu::getInstance(); + menu->setIsOptionChecked(MenuOption::Overlays, true); } void Application::cycleCamera() { @@ -5306,9 +5305,7 @@ void Application::readArgumentsFromLocalSocket() const { } void Application::showDesktop() { - if (!_overlayConductor.getEnabled()) { - _overlayConductor.setEnabled(true); - } + Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, true); } CompositorHelper& Application::getApplicationCompositor() const { diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index a897d85472..a455147bb8 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -99,104 +99,50 @@ void OverlayConductor::centerUI() { qApp->getApplicationCompositor().setModelTransform(Transform(camMat)); } -bool OverlayConductor::userWishesToHide() const { - // user pressed toggle button. - return Menu::getInstance()->isOptionChecked(MenuOption::Overlays) != _prevOverlayMenuChecked && Menu::getInstance()->isOptionChecked(MenuOption::Overlays); -} - -bool OverlayConductor::userWishesToShow() const { - // user pressed toggle button. - return Menu::getInstance()->isOptionChecked(MenuOption::Overlays) != _prevOverlayMenuChecked && !Menu::getInstance()->isOptionChecked(MenuOption::Overlays); -} - -void OverlayConductor::setState(State state) { -#ifdef WANT_DEBUG - static QString stateToString[NumStates] = { "Enabled", "DisabledByDrive", "DisabledByHead", "DisabledByToggle" }; - qDebug() << "OverlayConductor " << stateToString[state] << "<--" << stateToString[_state]; -#endif - _state = state; -} - -OverlayConductor::State OverlayConductor::getState() const { - return _state; -} - void OverlayConductor::update(float dt) { + auto offscreenUi = DependencyManager::get(); + bool currentVisible = !offscreenUi->getDesktop()->property("pinned").toBool(); MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - - // centerUI when hmd mode is first enabled - if (qApp->isHMDMode() && !_hmdMode) { + // centerUI when hmd mode is first enabled and mounted + if (qApp->isHMDMode() && qApp->getActiveDisplayPlugin()->isDisplayVisible() && !_hmdMode) { + _hmdMode = true; centerUI(); + } else { + _hmdMode = false; } - _hmdMode = qApp->isHMDMode(); bool prevDriving = _currentDriving; bool isDriving = updateAvatarHasDriveInput(); bool drivingChanged = prevDriving != isDriving; - bool isAtRest = updateAvatarIsAtRest(); - switch (getState()) { - case Enabled: - if (myAvatar->getClearOverlayWhenDriving() && qApp->isHMDMode() && headOutsideOverlay()) { - setState(DisabledByHead); - setEnabled(false); - } - if (userWishesToHide()) { - setState(DisabledByToggle); - setEnabled(false); - } - if (myAvatar->getClearOverlayWhenDriving() && drivingChanged && isDriving) { - setState(DisabledByDrive); - setEnabled(false); - } - break; - case DisabledByDrive: - if (!isDriving || userWishesToShow()) { - setState(Enabled); - setEnabled(true); - } - break; - case DisabledByHead: - if (isAtRest || userWishesToShow()) { - setState(Enabled); - setEnabled(true); - } - break; - case DisabledByToggle: - if (userWishesToShow()) { - setState(Enabled); - setEnabled(true); - } - break; - default: - break; + if (_flags & SuppressedByDrive) { + if (!isDriving) { + _flags &= ~SuppressedByDrive; + } + } else { + if (myAvatar->getClearOverlayWhenDriving() && drivingChanged && isDriving) { + _flags |= SuppressedByDrive; + } } - _prevOverlayMenuChecked = Menu::getInstance()->isOptionChecked(MenuOption::Overlays); -} - -void OverlayConductor::setEnabled(bool enabled) { - if (enabled == _enabled) { - return; + if (_flags & SuppressedByHead) { + if (isAtRest) { + _flags &= ~SuppressedByHead; + } + } else { + if (_hmdMode && headOutsideOverlay()) { + _flags |= SuppressedByHead; + } } - _enabled = enabled; // set the new value - auto offscreenUi = DependencyManager::get(); - offscreenUi->setPinned(!_enabled); - // ensure that the the state of the menu item reflects the state of the overlay. - Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, _enabled); - _prevOverlayMenuChecked = _enabled; - - // if the new state is visible/enabled... - if (_enabled && qApp->isHMDMode()) { - centerUI(); + bool targetVisible = Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && (0 == (_flags & SuppressMask)); + if (targetVisible != currentVisible) { + offscreenUi->setPinned(!targetVisible); + if (targetVisible && _hmdMode) { + centerUI(); + } } } - -bool OverlayConductor::getEnabled() const { - return _enabled; -} - diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h index 375b2652f6..1bdfe2ed79 100644 --- a/interface/src/ui/OverlayConductor.h +++ b/interface/src/ui/OverlayConductor.h @@ -17,33 +17,20 @@ public: ~OverlayConductor(); void update(float dt); - void setEnabled(bool enable); - bool getEnabled() const; - void centerUI(); private: bool headOutsideOverlay() const; bool updateAvatarHasDriveInput(); bool updateAvatarIsAtRest(); - bool userWishesToHide() const; - bool userWishesToShow() const; - enum State { - Enabled = 0, - DisabledByDrive, - DisabledByHead, - DisabledByToggle, - NumStates + enum SupressionFlags { + SuppressedByDrive = 0x01, + SuppressedByHead = 0x02, + SuppressMask = 0x03, }; - void setState(State state); - State getState() const; - - State _state { DisabledByDrive }; - - bool _prevOverlayMenuChecked { true }; - bool _enabled { false }; + uint8_t _flags { SuppressedByDrive }; bool _hmdMode { false }; // used by updateAvatarHasDriveInput From 067539ff6d3bb1de82ba5ebd79542e79228ac767 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Wed, 15 Jun 2016 17:44:58 -0700 Subject: [PATCH 065/171] Fix overlay stuck to face --- interface/src/ui/OverlayConductor.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index a455147bb8..74e27122e4 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -105,9 +105,11 @@ void OverlayConductor::update(float dt) { MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); // centerUI when hmd mode is first enabled and mounted - if (qApp->isHMDMode() && qApp->getActiveDisplayPlugin()->isDisplayVisible() && !_hmdMode) { - _hmdMode = true; - centerUI(); + if (qApp->isHMDMode() && qApp->getActiveDisplayPlugin()->isDisplayVisible()) { + if (!_hmdMode) { + _hmdMode = true; + centerUI(); + } } else { _hmdMode = false; } From 74a3db4e1c488ece88e793e020e4d62133e568af Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 17 Jun 2016 11:14:56 -0700 Subject: [PATCH 066/171] When setting Reticle.depth, don't assume that we're in the center of the HUD. --- .../controllers/handControllerPointer.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index e87f5da946..0f1e23b45c 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -153,9 +153,8 @@ function isPointingAtOverlay(optionalHudPosition2d) { } // Generalized HUD utilities, with or without HMD: -// These two "vars" are for documentation. Do not change their values! -var SPHERICAL_HUD_DISTANCE = 1; // meters. -var PLANAR_PERPENDICULAR_HUD_DISTANCE = SPHERICAL_HUD_DISTANCE; +// This "var" is for documentation. Do not change the value! +var PLANAR_PERPENDICULAR_HUD_DISTANCE = 1; function calculateRayUICollisionPoint(position, direction) { // Answer the 3D intersection of the HUD by the given ray, or falsey if no intersection. if (HMD.active) { @@ -274,6 +273,11 @@ function expireMouseCursor(now) { Reticle.visible = false; } } +function hudReticleDistance() { // 3d distance from camera to the reticle position on hud + // (The camera is only in the center of the sphere on reset.) + var reticlePositionOnHUD = HMD.worldPointFromOverlay(Reticle.position); + return Vec3.distance(reticlePositionOnHUD, HMD.position); +} function onMouseMove() { // Display cursor at correct depth (as in depthReticle.js), and updateMouseActivity. if (ignoreMouseActivity()) { @@ -283,11 +287,10 @@ function onMouseMove() { if (HMD.active) { // set depth updateSeeking(); if (isPointingAtOverlay()) { - Reticle.setDepth(SPHERICAL_HUD_DISTANCE); // NOT CORRECT IF WE SWITCH TO OFFSET SPHERE! + Reticle.depth = hudReticleDistance(); } else { var result = findRayIntersection(Camera.computePickRay(Reticle.position.x, Reticle.position.y)); - var depth = result.intersects ? result.distance : APPARENT_MAXIMUM_DEPTH; - Reticle.setDepth(depth); + Reticle.depth = result.intersects ? result.distance : APPARENT_MAXIMUM_DEPTH; } } updateMouseActivity(); // After the above, just in case the depth movement is awkward when becoming visible. @@ -493,8 +496,8 @@ function update() { setReticlePosition(hudPoint2d); // If there's a HUD element at the (newly moved) reticle, just make it visible and bail. if (isPointingAtOverlay(hudPoint2d)) { - if (HMD.active) { // Doesn't hurt anything without the guard, but consider it documentation. - Reticle.depth = SPHERICAL_HUD_DISTANCE; // NOT CORRECT IF WE SWITCH TO OFFSET SPHERE! + if (HMD.active) { + Reticle.depth = hudReticleDistance(); } return turnOffVisualization(true); } From e04d86a75a202d9ad108c70e9266429a29ed05e2 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 17 Jun 2016 12:10:26 -0700 Subject: [PATCH 067/171] Change rendering of reticle when in HMD mode Properly take the overlay model matrix into account. Reticle depth is now relative to the eye, not relative to the overlay sphere. --- .../src/display-plugins/CompositorHelper.cpp | 35 ++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp index 2d3c79071f..a17fe2fd49 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp @@ -409,30 +409,25 @@ void CompositorHelper::updateTooltips() { //} } +// eyePose and headPosition are in sensor space. +// the resulting matrix should be in view space. glm::mat4 CompositorHelper::getReticleTransform(const glm::mat4& eyePose, const glm::vec3& headPosition) const { glm::mat4 result; if (isHMD()) { - vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize); - auto reticlePosition = getReticlePosition(); - auto spherical = overlayToSpherical(reticlePosition); - // The pointer transform relative to the sensor - auto pointerTransform = glm::mat4_cast(quat(vec3(-spherical.y, spherical.x, 0.0f))) * glm::translate(mat4(), vec3(0, 0, -1)); - float reticleDepth = getReticleDepth(); - if (reticleDepth != 1.0f) { - // Cursor position in UI space - auto cursorPosition = vec3(pointerTransform[3]) / pointerTransform[3].w; - // Ray to the cursor, in UI space - auto cursorRay = glm::normalize(cursorPosition - headPosition) * reticleDepth; - // Move the ray to be relative to the head pose - pointerTransform[3] = vec4(cursorRay + headPosition, 1); - // Scale up the cursor because of distance - reticleScale *= reticleDepth; + vec2 spherical = overlayToSpherical(getReticlePosition()); + vec3 overlaySurfacePoint = getPoint(spherical.x, spherical.y); // overlay space + vec3 sensorSurfacePoint = _modelTransform.transform(overlaySurfacePoint); // sensor space + vec3 d = sensorSurfacePoint - headPosition; + vec3 reticlePosition; + if (glm::length(d) >= EPSILON) { + d = glm::normalize(d); + } else { + d = glm::normalize(overlaySurfacePoint); } - glm::mat4 overlayXfm; - _modelTransform.getMatrix(overlayXfm); - pointerTransform = overlayXfm * pointerTransform; - pointerTransform = glm::inverse(eyePose) * pointerTransform; - result = glm::scale(pointerTransform, reticleScale); + reticlePosition = headPosition + (d * getReticleDepth()); + quat reticleOrientation = quat(vec3(-spherical.y, spherical.x, 0.0f)); + vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize * getReticleDepth()); + return glm::inverse(eyePose) * createMatFromScaleQuatAndPos(reticleScale, reticleOrientation, reticlePosition); } else { static const float CURSOR_PIXEL_SIZE = 32.0f; const auto canvasSize = vec2(toGlm(_renderingWidget->size()));; From ec615caa80d66ec3e7f2544a904951ef85856522 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Fri, 17 Jun 2016 10:56:35 -0700 Subject: [PATCH 068/171] Support toolbar API --- interface/resources/qml/AssetServer.qml | 2 +- interface/resources/qml/Browser.qml | 2 +- interface/resources/qml/InfoView.qml | 2 +- interface/resources/qml/LoginDialog.qml | 2 +- interface/resources/qml/QmlWebWindow.qml | 2 +- interface/resources/qml/ToolWindow.qml | 2 +- interface/resources/qml/UpdateDialog.qml | 2 +- .../qml/dialogs/PreferencesDialog.qml | 2 +- .../qml/dialogs/preferences/AvatarBrowser.qml | 15 +- interface/resources/qml/hifi/Desktop.qml | 46 +++-- .../resources/qml/hifi/ToggleHudButton.qml | 38 ----- .../qml/hifi/dialogs/AttachmentsDialog.qml | 2 +- .../qml/hifi/dialogs/ModelBrowserDialog.qml | 2 +- .../qml/hifi/dialogs/RunningScripts.qml | 2 +- .../qml/hifi/dialogs/SnapshotShareDialog.qml | 2 +- .../resources/qml/hifi/toolbars/Toolbar.qml | 117 +++++++++++++ .../qml/hifi/toolbars/ToolbarButton.qml | 39 +++++ interface/resources/qml/windows/Frame.qml | 2 +- .../resources/qml/windows/ModalWindow.qml | 2 +- .../resources/qml/windows/ScrollingWindow.qml | 157 ++++++++++++++++++ interface/resources/qml/windows/ToolFrame.qml | 49 ++++++ interface/resources/qml/windows/Window.qml | 146 ++-------------- interface/src/Application.cpp | 3 + .../scripting/ToolbarScriptingInterface.cpp | 124 ++++++++++++++ .../src/scripting/ToolbarScriptingInterface.h | 26 +++ scripts/defaultScripts.js | 3 +- scripts/developer/tests/toolbarTest.js | 118 +++++++++++++ .../system/assets/images/tools/microphone.svg | 13 ++ scripts/system/examples.js | 98 ++--------- scripts/system/goto.js | 42 ++--- scripts/system/hmd.js | 55 +++--- scripts/system/mute.js | 29 ++++ tests/ui/qml/Stubs.qml | 12 ++ tests/ui/qml/main.qml | 89 +++++++--- tests/ui/qmlscratch.pro | 1 + tests/ui/src/main.cpp | 3 + 36 files changed, 870 insertions(+), 381 deletions(-) delete mode 100644 interface/resources/qml/hifi/ToggleHudButton.qml create mode 100644 interface/resources/qml/hifi/toolbars/Toolbar.qml create mode 100644 interface/resources/qml/hifi/toolbars/ToolbarButton.qml create mode 100644 interface/resources/qml/windows/ScrollingWindow.qml create mode 100644 interface/resources/qml/windows/ToolFrame.qml create mode 100644 interface/src/scripting/ToolbarScriptingInterface.cpp create mode 100644 interface/src/scripting/ToolbarScriptingInterface.h create mode 100644 scripts/developer/tests/toolbarTest.js create mode 100644 scripts/system/assets/images/tools/microphone.svg create mode 100644 scripts/system/mute.js diff --git a/interface/resources/qml/AssetServer.qml b/interface/resources/qml/AssetServer.qml index 5b02020272..c9b6305258 100644 --- a/interface/resources/qml/AssetServer.qml +++ b/interface/resources/qml/AssetServer.qml @@ -18,7 +18,7 @@ import "controls-uit" as HifiControls import "windows" import "dialogs" -Window { +ScrollingWindow { id: root objectName: "AssetServer" title: "Asset Browser" diff --git a/interface/resources/qml/Browser.qml b/interface/resources/qml/Browser.qml index 01e1bb7680..8c8cf05444 100644 --- a/interface/resources/qml/Browser.qml +++ b/interface/resources/qml/Browser.qml @@ -6,7 +6,7 @@ import "controls-uit" import "styles-uit" import "windows" -Window { +ScrollingWindow { id: root HifiConstants { id: hifi } title: "Browser" diff --git a/interface/resources/qml/InfoView.qml b/interface/resources/qml/InfoView.qml index 0f17a88614..f18969fb2f 100644 --- a/interface/resources/qml/InfoView.qml +++ b/interface/resources/qml/InfoView.qml @@ -14,7 +14,7 @@ import Hifi 1.0 as Hifi import "controls-uit" import "windows" as Windows -Windows.Window { +Windows.ScrollingWindow { id: root width: 800 height: 800 diff --git a/interface/resources/qml/LoginDialog.qml b/interface/resources/qml/LoginDialog.qml index f5030cb88d..f75e83e36e 100644 --- a/interface/resources/qml/LoginDialog.qml +++ b/interface/resources/qml/LoginDialog.qml @@ -14,7 +14,7 @@ import "controls" import "styles" import "windows" -Window { +ScrollingWindow { id: root HifiConstants { id: hifi } objectName: "LoginDialog" diff --git a/interface/resources/qml/QmlWebWindow.qml b/interface/resources/qml/QmlWebWindow.qml index 09c3bd7f28..542b44b95e 100644 --- a/interface/resources/qml/QmlWebWindow.qml +++ b/interface/resources/qml/QmlWebWindow.qml @@ -17,7 +17,7 @@ import "windows" as Windows import "controls-uit" as Controls import "styles-uit" -Windows.Window { +Windows.ScrollingWindow { id: root HifiConstants { id: hifi } title: "WebWindow" diff --git a/interface/resources/qml/ToolWindow.qml b/interface/resources/qml/ToolWindow.qml index faa96fac5a..bbfc74493d 100644 --- a/interface/resources/qml/ToolWindow.qml +++ b/interface/resources/qml/ToolWindow.qml @@ -19,7 +19,7 @@ import "windows" import "controls-uit" import "styles-uit" -Window { +ScrollingWindow { id: toolWindow resizable: true objectName: "ToolWindow" diff --git a/interface/resources/qml/UpdateDialog.qml b/interface/resources/qml/UpdateDialog.qml index 52fdab25f9..91dc210eda 100644 --- a/interface/resources/qml/UpdateDialog.qml +++ b/interface/resources/qml/UpdateDialog.qml @@ -7,7 +7,7 @@ import "controls-uit" import "styles-uit" import "windows" -Window { +ScrollingWindow { id: root HifiConstants { id: hifi } objectName: "UpdateDialog" diff --git a/interface/resources/qml/dialogs/PreferencesDialog.qml b/interface/resources/qml/dialogs/PreferencesDialog.qml index 398e0abd8e..5278118a22 100644 --- a/interface/resources/qml/dialogs/PreferencesDialog.qml +++ b/interface/resources/qml/dialogs/PreferencesDialog.qml @@ -16,7 +16,7 @@ import "../styles-uit" import "../windows" import "preferences" -Window { +ScrollingWindow { id: root title: "Preferences" resizable: true diff --git a/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml b/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml index 90d2dc5284..16d25b3c4c 100644 --- a/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml +++ b/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml @@ -23,15 +23,10 @@ Windows.Window { resizable: true modality: Qt.ApplicationModal - Item { - width: pane.contentWidth - implicitHeight: pane.scrollHeight - - Controls.WebView { - id: webview - anchors.fill: parent - url: "https://metaverse.highfidelity.com/marketplace?category=avatars" - focus: true - } + Controls.WebView { + id: webview + anchors.fill: parent + url: "https://metaverse.highfidelity.com/marketplace?category=avatars" + focus: true } } diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index e127a235e6..c14d55cb00 100644 --- a/interface/resources/qml/hifi/Desktop.qml +++ b/interface/resources/qml/hifi/Desktop.qml @@ -1,10 +1,12 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 import QtWebEngine 1.1; +import Qt.labs.settings 1.0 import "../desktop" import ".." import "." +import "./toolbars" Desktop { id: desktop @@ -20,13 +22,6 @@ Desktop { acceptedButtons: Qt.NoButton } - Component.onCompleted: { - WebEngine.settings.javascriptCanOpenWindows = true; - WebEngine.settings.javascriptCanAccessClipboard = false; - WebEngine.settings.spatialNavigationEnabled = false; - WebEngine.settings.localContentCanAccessRemoteUrls = true; - } - // The tool window, one instance property alias toolWindow: toolWindow ToolWindow { id: toolWindow } @@ -49,11 +44,40 @@ Desktop { } } + property var toolbars: ({}) + Component { id: toolbarBuilder; Toolbar { } } - ToggleHudButton { - anchors.bottom: parent.bottom - anchors.bottomMargin: 32 - anchors.horizontalCenter: parent.horizontalCenter + Component.onCompleted: { + WebEngine.settings.javascriptCanOpenWindows = true; + WebEngine.settings.javascriptCanAccessClipboard = false; + WebEngine.settings.spatialNavigationEnabled = false; + WebEngine.settings.localContentCanAccessRemoteUrls = true; + + var sysToolbar = desktop.getToolbar("com.highfidelity.interface.toolbar.system"); + //toolbars[sysToolbar.objectName] = sysToolbar + var toggleHudButton = sysToolbar.addButton({ + imageURL: "../../../icons/hud-01.svg", + visible: true, + + }); + toggleHudButton.yOffset = Qt.binding(function(){ + return desktop.pinned ? 50 : 0 + }); + toggleHudButton.clicked.connect(function(){ + console.log("Clicked on hud button") + var overlayMenuItem = "Overlays" + MenuInterface.setIsOptionChecked(overlayMenuItem, !MenuInterface.isOptionChecked(overlayMenuItem)); + }); + } + + // Create or fetch a toolbar with the given name + function getToolbar(name) { + var result = toolbars[name]; + if (!result) { + result = toolbars[name] = toolbarBuilder.createObject(desktop, {}); + result.objectName = name; + } + return result; } } diff --git a/interface/resources/qml/hifi/ToggleHudButton.qml b/interface/resources/qml/hifi/ToggleHudButton.qml deleted file mode 100644 index 63c056b352..0000000000 --- a/interface/resources/qml/hifi/ToggleHudButton.qml +++ /dev/null @@ -1,38 +0,0 @@ -import QtQuick 2.5 -import QtQuick.Controls 1.4 - -import "../windows" - -Window { - //frame: HiddenFrame {} - hideBackground: true - resizable: false - destroyOnCloseButton: false - destroyOnHidden: false - closable: false - shown: true - pinned: true - width: 50 - height: 50 - clip: true - visible: true - // Disable this window from being able to call 'desktop.raise() and desktop.showDesktop' - activator: Item {} - - Item { - width: 50 - height: 50 - Image { - y: desktop.pinned ? -50 : 0 - id: hudToggleImage - source: "../../icons/hud-01.svg" - } - MouseArea { - readonly property string overlayMenuItem: "Overlays" - anchors.fill: parent - onClicked: MenuInterface.setIsOptionChecked(overlayMenuItem, !MenuInterface.isOptionChecked(overlayMenuItem)); - } - } -} - - diff --git a/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml b/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml index 7b3a368bb0..15467f8021 100755 --- a/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml +++ b/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml @@ -9,7 +9,7 @@ import "../../controls-uit" as HifiControls import "../../windows" import "attachments" -Window { +ScrollingWindow { id: root title: "Attachments" objectName: "AttachmentsDialog" diff --git a/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml b/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml index 29f0498f59..aeffb8e4bf 100644 --- a/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml +++ b/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml @@ -11,7 +11,7 @@ import "../../styles-uit" import "../../controls-uit" as HifiControls import "../../windows" -Window { +ScrollingWindow { id: root resizable: true width: 600 diff --git a/interface/resources/qml/hifi/dialogs/RunningScripts.qml b/interface/resources/qml/hifi/dialogs/RunningScripts.qml index ccf2413421..5457caccf1 100644 --- a/interface/resources/qml/hifi/dialogs/RunningScripts.qml +++ b/interface/resources/qml/hifi/dialogs/RunningScripts.qml @@ -17,7 +17,7 @@ import "../../styles-uit" import "../../controls-uit" as HifiControls import "../../windows" -Window { +ScrollingWindow { id: root objectName: "RunningScripts" title: "Running Scripts" diff --git a/interface/resources/qml/hifi/dialogs/SnapshotShareDialog.qml b/interface/resources/qml/hifi/dialogs/SnapshotShareDialog.qml index f99b770a78..3dacb3b39c 100644 --- a/interface/resources/qml/hifi/dialogs/SnapshotShareDialog.qml +++ b/interface/resources/qml/hifi/dialogs/SnapshotShareDialog.qml @@ -7,7 +7,7 @@ import "../../windows" import "../../js/Utils.js" as Utils import "../models" -Window { +ScrollingWindow { id: root resizable: true width: 516 diff --git a/interface/resources/qml/hifi/toolbars/Toolbar.qml b/interface/resources/qml/hifi/toolbars/Toolbar.qml new file mode 100644 index 0000000000..35c816569b --- /dev/null +++ b/interface/resources/qml/hifi/toolbars/Toolbar.qml @@ -0,0 +1,117 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import Qt.labs.settings 1.0 + +import "../../windows" +import "." + +Window { + id: window + frame: ToolFrame { } + hideBackground: true + resizable: false + destroyOnCloseButton: false + destroyOnHidden: false + closable: false + shown: true + pinned: true + width: content.width + height: content.height + visible: true + // Disable this window from being able to call 'desktop.raise() and desktop.showDesktop' + activator: Item {} + property bool horizontal: true + property real buttonSize: 50; + property var buttons: [] + property var container: horizontal ? row : column + + Settings { + category: "toolbar/" + window.objectName + property alias x: window.x + property alias y: window.y + } + + onHorizontalChanged: { + var oldParent = horizontal ? column : row; + var newParent = horizontal ? row : column; + var move = []; + + var i; + for (i in oldParent.children) { + var child = oldParent.children[i]; + if (child.spacer) { + continue; + } + move.push(oldParent.children[i]); + } + for (i in move) { + move[i].parent = newParent; + if (horizontal) { + move[i].y = 0 + } else { + move[i].x = 0 + } + } + fixSpacers(); + } + + Item { + id: content + implicitHeight: horizontal ? row.height : column.height + implicitWidth: horizontal ? row.width : column.width + Row { + id: row + spacing: 6 + visible: window.horizontal + Rectangle{ readonly property bool spacer: true; id: rowSpacer1; width: 1; height: row.height } + Rectangle{ readonly property bool spacer: true; id: rowSpacer2; width: 1; height: row.height } + Rectangle{ readonly property bool spacer: true; id: rowSpacer3; width: 1; height: row.height } + Rectangle{ readonly property bool spacer: true; id: rowSpacer4; width: 1; height: row.height } + } + + Column { + id: column + spacing: 6 + visible: !window.horizontal + Rectangle{ readonly property bool spacer: true; id: colSpacer1; width: column.width; height: 1 } + Rectangle{ readonly property bool spacer: true; id: colSpacer2; width: column.width; height: 1 } + Rectangle{ readonly property bool spacer: true; id: colSpacer3; width: column.width; height: 1 } + Rectangle{ readonly property bool spacer: true; id: colSpacer4; width: column.width; height: 1 } + } + + Component { id: toolbarButtonBuilder; ToolbarButton { } } + } + + function addButton(properties) { + properties = properties || {} + + // If a name is specified, then check if there's an existing button with that name + // and return it if so. This will allow multiple clients to listen to a single button, + // and allow scripts to be idempotent so they don't duplicate buttons if they're reloaded + if (properties.objectName) { + for (var i in buttons) { + var child = buttons[i]; + if (child.objectName === properties.objectName) { + return child; + } + } + } + + properties.toolbar = this; + var result = toolbarButtonBuilder.createObject(container, properties); + buttons.push(result); + fixSpacers(); + return result; + } + + function fixSpacers() { + colSpacer3.parent = null + colSpacer4.parent = null + rowSpacer3.parent = null + rowSpacer4.parent = null + colSpacer3.parent = column + colSpacer4.parent = column + rowSpacer3.parent = row + rowSpacer4.parent = row + } +} diff --git a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml new file mode 100644 index 0000000000..a8514689e8 --- /dev/null +++ b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml @@ -0,0 +1,39 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +Item { + id: button + property alias imageURL: image.source + property alias alpha: button.opacity + property var subImage; + property int yOffset: 0 + property var toolbar; + property real size: 50 // toolbar ? toolbar.buttonSize : 50 + width: size; height: size + clip: true + + Component.onCompleted: { + if (subImage) { + if (subImage.y) { + yOffset = subImage.y; + } + } + } + + signal clicked() + + Image { + id: image + y: -button.yOffset; + width: parent.width + } + + MouseArea { + anchors.fill: parent + onClicked: { + console.log("Clicked on button " + image.source + " named " + button.objectName) + button.clicked(); + } + } +} + diff --git a/interface/resources/qml/windows/Frame.qml b/interface/resources/qml/windows/Frame.qml index 9519a44cf0..bc8ecc35ec 100644 --- a/interface/resources/qml/windows/Frame.qml +++ b/interface/resources/qml/windows/Frame.qml @@ -80,7 +80,7 @@ Item { border.width: 3 border.color: hifi.colors.white50 radius: hifi.dimensions.borderRadius - visible: window ? !pane.visible : false + visible: window ? !window.content.visible : false } MouseArea { diff --git a/interface/resources/qml/windows/ModalWindow.qml b/interface/resources/qml/windows/ModalWindow.qml index f79dc9084f..2d56099051 100644 --- a/interface/resources/qml/windows/ModalWindow.qml +++ b/interface/resources/qml/windows/ModalWindow.qml @@ -12,7 +12,7 @@ import QtQuick 2.5 import "." -Window { +ScrollingWindow { id: window modality: Qt.ApplicationModal destroyOnCloseButton: true diff --git a/interface/resources/qml/windows/ScrollingWindow.qml b/interface/resources/qml/windows/ScrollingWindow.qml new file mode 100644 index 0000000000..f1dc744344 --- /dev/null +++ b/interface/resources/qml/windows/ScrollingWindow.qml @@ -0,0 +1,157 @@ +// +// Window.qml +// +// Created by Bradley Austin Davis on 12 Jan 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtGraphicalEffects 1.0 + +import "." +import "../styles-uit" + +// FIXME how do I set the initial position of a window without +// overriding places where the a individual client of the window +// might be setting the position with a Settings{} element? + +// FIXME how to I enable dragging without allowing the window to lay outside +// of the desktop? How do I ensure when the desktop resizes all the windows +// are still at least partially visible? +Window { + id: window + HifiConstants { id: hifi } + children: [ swallower, frame, pane, activator ] + + property var footer: Item { } // Optional static footer at the bottom of the dialog. + + // Scrollable window content. + // FIXME this should not define any visual content in this type. The base window + // type should only consist of logic sized areas, with nothing drawn (although the + // default value for the frame property does include visual decorations) + property var pane: Item { + property bool isScrolling: scrollView.height < scrollView.contentItem.height + property int contentWidth: scrollView.width - (isScrolling ? 10 : 0) + property int scrollHeight: scrollView.height + + anchors.fill: parent + anchors.rightMargin: isScrolling ? 11 : 0 + + Rectangle { + id: contentBackground + anchors.fill: parent + anchors.rightMargin: parent.isScrolling ? 11 : 0 + color: hifi.colors.baseGray + visible: !window.hideBackground && modality != Qt.ApplicationModal + } + + + LinearGradient { + visible: !window.hideBackground && gradientsSupported && modality != Qt.ApplicationModal + anchors.top: contentBackground.bottom + anchors.left: contentBackground.left + width: contentBackground.width - 1 + height: 4 + start: Qt.point(0, 0) + end: Qt.point(0, 4) + gradient: Gradient { + GradientStop { position: 0.0; color: hifi.colors.darkGray } + GradientStop { position: 1.0; color: hifi.colors.darkGray0 } + } + cached: true + } + + ScrollView { + id: scrollView + contentItem: content + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff + verticalScrollBarPolicy: Qt.ScrollBarAsNeeded + anchors.fill: parent + anchors.rightMargin: parent.isScrolling ? 1 : 0 + anchors.bottomMargin: footer.height > 0 ? footerPane.height : 0 + + style: ScrollViewStyle { + + padding.right: -7 // Move to right away from content. + + handle: Item { + implicitWidth: 8 + Rectangle { + radius: 4 + color: hifi.colors.white30 + anchors { + fill: parent + leftMargin: 2 // Finesse size and position. + topMargin: 1 + bottomMargin: 1 + } + } + } + + scrollBarBackground: Item { + implicitWidth: 10 + Rectangle { + color: hifi.colors.darkGray30 + radius: 4 + anchors { + fill: parent + topMargin: -1 // Finesse size + bottomMargin: -2 + } + } + } + + incrementControl: Item { + visible: false + } + + decrementControl: Item { + visible: false + } + } + } + + Rectangle { + // Optional non-scrolling footer. + id: footerPane + anchors { + left: parent.left + bottom: parent.bottom + } + width: parent.contentWidth + height: footer.height + 2 * hifi.dimensions.contentSpacing.y + 3 + color: hifi.colors.baseGray + visible: footer.height > 0 + + Item { + // Horizontal rule. + anchors.fill: parent + + Rectangle { + width: parent.width + height: 1 + y: 1 // Stop displaying content just above horizontal rule/=. + color: hifi.colors.baseGrayShadow + } + + Rectangle { + width: parent.width + height: 1 + y: 2 + color: hifi.colors.baseGrayHighlight + } + } + + Item { + anchors.fill: parent + anchors.topMargin: 3 // Horizontal rule. + children: [ footer ] + } + } + } +} diff --git a/interface/resources/qml/windows/ToolFrame.qml b/interface/resources/qml/windows/ToolFrame.qml new file mode 100644 index 0000000000..ac1093092e --- /dev/null +++ b/interface/resources/qml/windows/ToolFrame.qml @@ -0,0 +1,49 @@ +// +// DefaultFrame.qml +// +// Created by Bradley Austin Davis on 12 Jan 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtGraphicalEffects 1.0 + +import "." +import "../styles-uit" + +Frame { + HifiConstants { id: hifi } + + Rectangle { + // Dialog frame + id: frameContent + readonly property int frameMargin: 6 + readonly property int frameMarginLeft: frameMargin + readonly property int frameMarginRight: frameMargin + readonly property int frameMarginTop: frameMargin + readonly property int frameMarginBottom: frameMargin + anchors { + topMargin: -frameMargin + leftMargin: -frameMargin + rightMargin: -frameMargin + bottomMargin: -frameMargin + } + anchors.fill: parent + color: hifi.colors.baseGrayHighlight40 + border { + width: hifi.dimensions.borderWidth + color: hifi.colors.faintGray50 + } + radius: hifi.dimensions.borderRadius / 2 + + // Enable dragging of the window + MouseArea { + anchors.fill: parent + drag.target: window + } + } +} + diff --git a/interface/resources/qml/windows/Window.qml b/interface/resources/qml/windows/Window.qml index ed1b820fc8..e3e70c1e74 100644 --- a/interface/resources/qml/windows/Window.qml +++ b/interface/resources/qml/windows/Window.qml @@ -41,7 +41,7 @@ Fadable { implicitHeight: content ? content.height : 0 implicitWidth: content ? content.width : 0 x: desktop.invalid_position; y: desktop.invalid_position; - children: [ swallower, frame, pane, activator ] + children: [ swallower, frame, content, activator ] // // Custom properties @@ -106,10 +106,10 @@ Fadable { // in the window and have a higher Z-order than the content, but follow // the position and size of frame decoration property var activator: MouseArea { - width: frame.decoration.width - height: frame.decoration.height - x: frame.decoration.anchors.leftMargin - y: frame.decoration.anchors.topMargin + width: frame.decoration ? frame.decoration.width : window.width + height: frame.decoration ? frame.decoration.height : window.height + x: frame.decoration ? frame.decoration.anchors.leftMargin : 0 + y: frame.decoration ? frame.decoration.anchors.topMargin : 0 propagateComposedEvents: true acceptedButtons: Qt.AllButtons enabled: window.visible @@ -124,10 +124,10 @@ Fadable { // to prevent things like mouse wheel events from reaching the application and changing // the camera if the user is scrolling through a list and gets to the end. property var swallower: MouseArea { - width: frame.decoration.width - height: frame.decoration.height - x: frame.decoration.anchors.leftMargin - y: frame.decoration.anchors.topMargin + width: frame.decoration ? frame.decoration.width : window.width + height: frame.decoration ? frame.decoration.height : window.height + x: frame.decoration ? frame.decoration.anchors.leftMargin : 0 + y: frame.decoration ? frame.decoration.anchors.topMargin : 0 hoverEnabled: true acceptedButtons: Qt.AllButtons enabled: window.visible @@ -140,133 +140,11 @@ Fadable { // Default to a standard frame. Can be overriden to provide custom // frame styles, like a full desktop frame to simulate a modal window - property var frame: DefaultFrame { } - - // Scrollable window content. - // FIXME this should not define any visual content in this type. The base window - // type should only consist of logic sized areas, with nothing drawn (although the - // default value for the frame property does include visual decorations) - property var pane: Item { - property bool isScrolling: scrollView.height < scrollView.contentItem.height - property int contentWidth: scrollView.width - (isScrolling ? 10 : 0) - property int scrollHeight: scrollView.height - - anchors.fill: parent - anchors.rightMargin: isScrolling ? 11 : 0 - - Rectangle { - id: contentBackground - anchors.fill: parent - anchors.rightMargin: parent.isScrolling ? 11 : 0 - color: hifi.colors.baseGray - visible: !window.hideBackground && modality != Qt.ApplicationModal - } - - - LinearGradient { - visible: !window.hideBackground && gradientsSupported && modality != Qt.ApplicationModal - anchors.top: contentBackground.bottom - anchors.left: contentBackground.left - width: contentBackground.width - 1 - height: 4 - start: Qt.point(0, 0) - end: Qt.point(0, 4) - gradient: Gradient { - GradientStop { position: 0.0; color: hifi.colors.darkGray } - GradientStop { position: 1.0; color: hifi.colors.darkGray0 } - } - cached: true - } - - ScrollView { - id: scrollView - contentItem: content - horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff - verticalScrollBarPolicy: Qt.ScrollBarAsNeeded - anchors.fill: parent - anchors.rightMargin: parent.isScrolling ? 1 : 0 - anchors.bottomMargin: footer.height > 0 ? footerPane.height : 0 - - style: ScrollViewStyle { - - padding.right: -7 // Move to right away from content. - - handle: Item { - implicitWidth: 8 - Rectangle { - radius: 4 - color: hifi.colors.white30 - anchors { - fill: parent - leftMargin: 2 // Finesse size and position. - topMargin: 1 - bottomMargin: 1 - } - } - } - - scrollBarBackground: Item { - implicitWidth: 10 - Rectangle { - color: hifi.colors.darkGray30 - radius: 4 - anchors { - fill: parent - topMargin: -1 // Finesse size - bottomMargin: -2 - } - } - } - - incrementControl: Item { - visible: false - } - - decrementControl: Item { - visible: false - } - } - } - - Rectangle { - // Optional non-scrolling footer. - id: footerPane - anchors { - left: parent.left - bottom: parent.bottom - } - width: parent.contentWidth - height: footer.height + 2 * hifi.dimensions.contentSpacing.y + 3 - color: hifi.colors.baseGray - visible: footer.height > 0 - - Item { - // Horizontal rule. - anchors.fill: parent - - Rectangle { - width: parent.width - height: 1 - y: 1 // Stop displaying content just above horizontal rule/=. - color: hifi.colors.baseGrayShadow - } - - Rectangle { - width: parent.width - height: 1 - y: 2 - color: hifi.colors.baseGrayHighlight - } - } - - Item { - anchors.fill: parent - anchors.topMargin: 3 // Horizontal rule. - children: [ footer ] - } - } + property var frame: DefaultFrame { + //window: window } + // // Handlers // diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ee365cd5a1..6dc575b572 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -133,6 +133,7 @@ #include "scripting/WebWindowClass.h" #include "scripting/WindowScriptingInterface.h" #include "scripting/ControllerScriptingInterface.h" +#include "scripting/ToolbarScriptingInterface.h" #include "scripting/RatesScriptingInterface.h" #if defined(Q_OS_MAC) || defined(Q_OS_WIN) #include "SpeechRecognizer.h" @@ -437,6 +438,7 @@ bool setupEssentials(int& argc, char** argv) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); #if defined(Q_OS_MAC) || defined(Q_OS_WIN) @@ -4527,6 +4529,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri RayToOverlayIntersectionResultFromScriptValue); scriptEngine->registerGlobalObject("Desktop", DependencyManager::get().data()); + scriptEngine->registerGlobalObject("Toolbars", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Window", DependencyManager::get().data()); scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, diff --git a/interface/src/scripting/ToolbarScriptingInterface.cpp b/interface/src/scripting/ToolbarScriptingInterface.cpp new file mode 100644 index 0000000000..fd96b6a809 --- /dev/null +++ b/interface/src/scripting/ToolbarScriptingInterface.cpp @@ -0,0 +1,124 @@ +// +// Created by Bradley Austin Davis on 2016-06-16 +// Copyright 2013-2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ToolbarScriptingInterface.h" + +#include + +class QmlWrapper : public QObject { + Q_OBJECT +public: + QmlWrapper(QObject* qmlObject, QObject* parent = nullptr) + : QObject(parent), _qmlObject(qmlObject) { + + const QMetaObject *metaobject = qmlObject->metaObject(); + int count = metaobject->propertyCount(); + qDebug() << "Scanning properties for " << qmlObject; + for (int i = 0; i < count; ++i) { + QMetaProperty metaproperty = metaobject->property(i); + const char *name = metaproperty.name(); + qDebug() << "Property " << name; + } + } + + Q_INVOKABLE void writeProperty(QString propertyName, QVariant propertyValue) { + auto offscreenUi = DependencyManager::get(); + offscreenUi->executeOnUiThread([=] { + _qmlObject->setProperty(propertyName.toStdString().c_str(), propertyValue); + }); + } + + Q_INVOKABLE void writeProperties(QVariant propertyMap) { + auto offscreenUi = DependencyManager::get(); + offscreenUi->executeOnUiThread([=] { + QVariantMap map = propertyMap.toMap(); + for (const QString& key : map.keys()) { + _qmlObject->setProperty(key.toStdString().c_str(), map[key]); + } + }); + } + + Q_INVOKABLE QVariant readProperty(const QString& propertyName) { + auto offscreenUi = DependencyManager::get(); + return offscreenUi->returnFromUiThread([&]()->QVariant { + return _qmlObject->property(propertyName.toStdString().c_str()); + }); + } + + Q_INVOKABLE QVariant readProperties(const QVariant& propertyList) { + auto offscreenUi = DependencyManager::get(); + return offscreenUi->returnFromUiThread([&]()->QVariant { + QVariantMap result; + for (const QVariant& property : propertyList.toList()) { + QString propertyString = property.toString(); + result.insert(propertyString, _qmlObject->property(propertyString.toStdString().c_str())); + } + return result; + }); + } + + +protected: + QObject* _qmlObject{ nullptr }; +}; + + +class ToolbarButtonProxy : public QmlWrapper { + Q_OBJECT + +public: + ToolbarButtonProxy(QObject* qmlObject, QObject* parent = nullptr) : QmlWrapper(qmlObject, parent) { + connect(qmlObject, SIGNAL(clicked()), this, SIGNAL(clicked())); + } + +signals: + void clicked(); +}; + +class ToolbarProxy : public QmlWrapper { + Q_OBJECT + +public: + ToolbarProxy(QObject* qmlObject, QObject* parent = nullptr) : QmlWrapper(qmlObject, parent) { } + + Q_INVOKABLE QObject* addButton(const QVariant& properties) { + QVariant resultVar; + bool invokeResult = QMetaObject::invokeMethod(_qmlObject, "addButton", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, properties)); + if (!invokeResult) { + return nullptr; + } + + QObject* rawButton = qvariant_cast(resultVar); + if (!rawButton) { + return nullptr; + } + + return new ToolbarButtonProxy(rawButton, this); + } +}; + + +QObject* ToolbarScriptingInterface::getToolbar(const QString& toolbarId) { + auto offscreenUi = DependencyManager::get(); + auto desktop = offscreenUi->getDesktop(); + QVariant resultVar; + bool invokeResult = QMetaObject::invokeMethod(desktop, "getToolbar", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, toolbarId)); + if (!invokeResult) { + return nullptr; + } + + QObject* rawToolbar = qvariant_cast(resultVar); + if (!rawToolbar) { + return nullptr; + } + + return new ToolbarProxy(rawToolbar); +} + + +#include "ToolbarScriptingInterface.moc" \ No newline at end of file diff --git a/interface/src/scripting/ToolbarScriptingInterface.h b/interface/src/scripting/ToolbarScriptingInterface.h new file mode 100644 index 0000000000..d3706cb6e1 --- /dev/null +++ b/interface/src/scripting/ToolbarScriptingInterface.h @@ -0,0 +1,26 @@ +// +// Created by Bradley Austin Davis on 2016-06-16 +// Copyright 2013-2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ToolbarScriptingInterface_h +#define hifi_ToolbarScriptingInterface_h + +#include + +#include + +#include + +class ToolbarProxy; + +class ToolbarScriptingInterface : public QObject, public Dependency { + Q_OBJECT +public: + Q_INVOKABLE QObject* getToolbar(const QString& toolbarId); +}; + +#endif // hifi_ToolbarScriptingInterface_h diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 218cab8903..bbbe049f39 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -12,9 +12,10 @@ Script.load("system/progress.js"); Script.load("system/away.js"); Script.load("system/users.js"); -Script.load("system/examples.js"); +Script.load("system/mute.js"); Script.load("system/goto.js"); Script.load("system/hmd.js"); +Script.load("system/examples.js"); Script.load("system/edit.js"); Script.load("system/selectAudioDevice.js"); Script.load("system/notifications.js"); diff --git a/scripts/developer/tests/toolbarTest.js b/scripts/developer/tests/toolbarTest.js new file mode 100644 index 0000000000..20f349929f --- /dev/null +++ b/scripts/developer/tests/toolbarTest.js @@ -0,0 +1,118 @@ +var isActive = false; + +var toolBar = (function() { + var that = {}, + toolBar, + activeButton, + newModelButton, + newCubeButton, + newSphereButton, + newLightButton, + newTextButton, + newWebButton, + newZoneButton, + newParticleButton + + var toolIconUrl = Script.resolvePath("assets/images/tools/"); + + function initialize() { + print("Toolbars: " + Toolbars); + toolBar = Toolbars.getToolbar("highfidelity.edit.toolbar"); + print("Toolbar: " + toolBar); + activeButton = toolBar.addButton({ + objectName: "activeButton", + imageURL: toolIconUrl + "edit-01.svg", + visible: true, + alpha: 0.9, + }); + + print("Button " + activeButton); + print("Button signal " + activeButton.clicked); + activeButton.clicked.connect(function(){ + print("Clicked on button " + isActive); + that.setActive(!isActive); + }); + + newModelButton = toolBar.addButton({ + objectName: "newModelButton", + imageURL: toolIconUrl + "model-01.svg", + alpha: 0.9, + visible: false + }); + + newCubeButton = toolBar.addButton({ + objectName: "newCubeButton", + imageURL: toolIconUrl + "cube-01.svg", + alpha: 0.9, + visible: false + }); + + newSphereButton = toolBar.addButton({ + objectName: "newSphereButton", + imageURL: toolIconUrl + "sphere-01.svg", + alpha: 0.9, + visible: false + }); + + newLightButton = toolBar.addButton({ + objectName: "newLightButton", + imageURL: toolIconUrl + "light-01.svg", + alpha: 0.9, + visible: false + }); + + newTextButton = toolBar.addButton({ + objectName: "newTextButton", + imageURL: toolIconUrl + "text-01.svg", + alpha: 0.9, + visible: false + }); + + newWebButton = toolBar.addButton({ + objectName: "newWebButton", + imageURL: toolIconUrl + "web-01.svg", + alpha: 0.9, + visible: false + }); + + newZoneButton = toolBar.addButton({ + objectName: "newZoneButton", + imageURL: toolIconUrl + "zone-01.svg", + alpha: 0.9, + visible: false + }); + + newParticleButton = toolBar.addButton({ + objectName: "newParticleButton", + imageURL: toolIconUrl + "particle-01.svg", + alpha: 0.9, + visible: false + }); + + that.setActive(false); + newModelButton.clicked(); + } + + that.setActive = function(active) { + if (active != isActive) { + isActive = active; + that.showTools(isActive); + } + }; + + // Sets visibility of tool buttons, excluding the power button + that.showTools = function(doShow) { + newModelButton.writeProperty('visible', doShow); + newCubeButton.writeProperty('visible', doShow); + newSphereButton.writeProperty('visible', doShow); + newLightButton.writeProperty('visible', doShow); + newTextButton.writeProperty('visible', doShow); + newWebButton.writeProperty('visible', doShow); + newZoneButton.writeProperty('visible', doShow); + newModelButton.writeProperty('visible', doShow); + newParticleButton.writeProperty('visible', doShow); + }; + + initialize(); + return that; +}()); diff --git a/scripts/system/assets/images/tools/microphone.svg b/scripts/system/assets/images/tools/microphone.svg new file mode 100644 index 0000000000..bd5e8afac7 --- /dev/null +++ b/scripts/system/assets/images/tools/microphone.svg @@ -0,0 +1,13 @@ + + + image/svg+xml + + + Layer 1 + + + + Mute + + + \ No newline at end of file diff --git a/scripts/system/examples.js b/scripts/system/examples.js index 6f4268182c..f850a9789e 100644 --- a/scripts/system/examples.js +++ b/scripts/system/examples.js @@ -9,10 +9,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include([ - "libraries/toolBars.js", -]); - var toolIconUrl = Script.resolvePath("assets/images/tools/"); var EXAMPLES_URL = "https://metaverse.highfidelity.com/examples"; @@ -52,87 +48,21 @@ function toggleExamples() { } } -var toolBar = (function() { - var that = {}, - toolBar, - browseExamplesButton; +var toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); - function initialize() { - toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.examples.toolbar", function(windowDimensions, toolbar) { - return { - x: (windowDimensions.x / 2) + (Tool.IMAGE_WIDTH * 2), - y: windowDimensions.y - }; - }, { - x: -toolWidth / 2, - y: -TOOLBAR_MARGIN_Y - toolHeight - }); - browseExamplesButton = toolBar.addTool({ - imageURL: toolIconUrl + "examples-01.svg", - subImage: { - x: 0, - y: Tool.IMAGE_WIDTH, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT - }, - width: toolWidth, - height: toolHeight, - alpha: 0.9, - visible: true, - showButtonDown: true - }); +var browseExamplesButton = toolBar.addButton({ + imageURL: toolIconUrl + "examples-01.svg", + objectName: "examples", + yOffset: 50, + alpha: 0.9, +}); - toolBar.showTool(browseExamplesButton, true); - } +var browseExamplesButtonDown = false; - var browseExamplesButtonDown = false; - that.mousePressEvent = function(event) { - var clickedOverlay, - url, - file; +browseExamplesButton.clicked.connect(function(){ + toggleExamples(); +}); - if (!event.isLeftButton) { - // if another mouse button than left is pressed ignore it - return false; - } - - clickedOverlay = Overlays.getOverlayAtPoint({ - x: event.x, - y: event.y - }); - - if (browseExamplesButton === toolBar.clicked(clickedOverlay)) { - toggleExamples(); - return true; - } - - return false; - }; - - that.mouseReleaseEvent = function(event) { - var handled = false; - - - if (browseExamplesButtonDown) { - var clickedOverlay = Overlays.getOverlayAtPoint({ - x: event.x, - y: event.y - }); - } - - newModelButtonDown = false; - browseExamplesButtonDown = false; - - return handled; - } - - that.cleanup = function() { - toolBar.cleanup(); - }; - - initialize(); - return that; -}()); - -Controller.mousePressEvent.connect(toolBar.mousePressEvent) -Script.scriptEnding.connect(toolBar.cleanup); +Script.scriptEnding.connect(function () { + browseExamplesButton.clicked.disconnect(); +}); diff --git a/scripts/system/goto.js b/scripts/system/goto.js index 00b5e912c0..a2ade02a78 100644 --- a/scripts/system/goto.js +++ b/scripts/system/goto.js @@ -9,39 +9,21 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("libraries/toolBars.js"); +var toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); -function initialPosition(windowDimensions, toolbar) { - return { - x: (windowDimensions.x / 2) - (Tool.IMAGE_WIDTH * 1), - y: windowDimensions.y - }; -} -var toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.goto.toolbar", initialPosition, { - x: -Tool.IMAGE_WIDTH / 2, - y: -Tool.IMAGE_HEIGHT -}); -var button = toolBar.addTool({ + +var button = toolBar.addButton({ + objectName: "goto", imageURL: Script.resolvePath("assets/images/tools/directory-01.svg"), - subImage: { - x: 0, - y: Tool.IMAGE_WIDTH, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT - }, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT, - alpha: 0.9, visible: true, - showButtonDown: true + yOffset: 50, + alpha: 0.9, }); -function onMousePress (event) { - if (event.isLeftButton && button === toolBar.clicked(Overlays.getOverlayAtPoint(event))) { - DialogsManager.toggleAddressBar(); - } -}; -Controller.mousePressEvent.connect(onMousePress) + +button.clicked.connect(function(){ + DialogsManager.toggleAddressBar(); +}); + Script.scriptEnding.connect(function () { - Controller.mousePressEvent.disconnect(onMousePress) - toolBar.cleanup(); + button.clicked.disconnect(); }); diff --git a/scripts/system/hmd.js b/scripts/system/hmd.js index 8b91e45676..2965c0d254 100644 --- a/scripts/system/hmd.js +++ b/scripts/system/hmd.js @@ -9,8 +9,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("libraries/toolBars.js"); - var headset; // The preferred headset. Default to the first one found in the following list. var displayMenuName = "Display"; var desktopMenuItemName = "Desktop"; @@ -20,38 +18,25 @@ var desktopMenuItemName = "Desktop"; } }); -function initialPosition(windowDimensions, toolbar) { - return { - x: (windowDimensions.x / 2) - (Tool.IMAGE_WIDTH * 2.5), - y: windowDimensions.y - }; -} -var toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.hmd.toolbar", initialPosition, { - x: -Tool.IMAGE_WIDTH / 2, - y: -Tool.IMAGE_HEIGHT -}); -var button = toolBar.addTool({ - imageURL: Script.resolvePath("assets/images/tools/hmd-switch-01.svg"), - subImage: { - x: 0, - y: Tool.IMAGE_WIDTH, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT - }, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT, - alpha: 0.9, - visible: true, - showButtonDown: true -}); -function onMousePress (event) { - if (event.isLeftButton && button === toolBar.clicked(Overlays.getOverlayAtPoint(event))) { +var toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); +var button; + +if (headset) { + button = toolBar.addButton({ + objectName: "hmdToggle", + imageURL: Script.resolvePath("assets/images/tools/hmd-switch-01.svg"), + visible: true, + yOffset: 50, + alpha: 0.9, + }); + + button.clicked.connect(function(){ var isDesktop = Menu.isOptionChecked(desktopMenuItemName); Menu.setIsOptionChecked(isDesktop ? headset : desktopMenuItemName, true); - } -}; -Controller.mousePressEvent.connect(onMousePress) -Script.scriptEnding.connect(function () { - Controller.mousePressEvent.disconnect(onMousePress) - toolBar.cleanup(); -}); + }); + + Script.scriptEnding.connect(function () { + button.clicked.disconnect(); + }); +} + diff --git a/scripts/system/mute.js b/scripts/system/mute.js new file mode 100644 index 0000000000..f66b6852ea --- /dev/null +++ b/scripts/system/mute.js @@ -0,0 +1,29 @@ +// +// goto.js +// scripts/system/ +// +// Created by Howard Stearns on 2 Jun 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); + + +var button = toolBar.addButton({ + objectName: "mute", + imageURL: Script.resolvePath("assets/images/tools/microphone.svg"), + visible: true, + alpha: 0.9, +}); + +button.clicked.connect(function(){ + var menuItem = "Mute Microphone"; + Menu.setIsOptionChecked(menuItem, !Menu.isOptionChecked(menuItem)); +}); + +Script.scriptEnding.connect(function () { + button.clicked.disconnect(); +}); diff --git a/tests/ui/qml/Stubs.qml b/tests/ui/qml/Stubs.qml index 8f828a0186..8c1465d54c 100644 --- a/tests/ui/qml/Stubs.qml +++ b/tests/ui/qml/Stubs.qml @@ -23,11 +23,23 @@ Item { function getUsername() { return "Jherico"; } } + Item { + objectName: "GL" + property string vendor: "" + } + Item { objectName: "ApplicationCompositor" property bool reticleOverDesktop: true } + Item { + objectName: "Controller" + function getRecommendedOverlayRect() { + return Qt.rect(0, 0, 1920, 1080); + } + } + Item { objectName: "Preferences" // List of categories obtained by logging categories as they are added in Interface in Preferences::addPreference(). diff --git a/tests/ui/qml/main.qml b/tests/ui/qml/main.qml index 33408fb821..19f6a55bfd 100644 --- a/tests/ui/qml/main.qml +++ b/tests/ui/qml/main.qml @@ -25,35 +25,67 @@ ApplicationWindow { property var tabs: []; property var urls: []; + property var toolbar; + property var lastButton; // Window visibility - - Button { - text: "restore all" - onClicked: { - for (var i = 0; i < desktop.windows.length; ++i) { - desktop.windows[i].shown = true - } - } - } - Button { - text: "toggle blue visible" - onClicked: { - blue.shown = !blue.shown - } - } - Button { - text: "toggle blue enabled" - onClicked: { - blue.enabled = !blue.enabled - } - } - Button { text: "toggle desktop" onClicked: desktop.togglePinned() } + Button { + text: "Create Toolbar" + onClicked: testButtons.toolbar = desktop.getToolbar("com.highfidelity.interface.toolbar.system"); + } + + Button { + text: "Toggle Toolbar Direction" + onClicked: testButtons.toolbar.horizontal = !testButtons.toolbar.horizontal + } + + Button { + readonly property var icons: [ + "edit-01.svg", + "model-01.svg", + "cube-01.svg", + "sphere-01.svg", + "light-01.svg", + "text-01.svg", + "web-01.svg", + "zone-01.svg", + "particle-01.svg", + ] + property int iconIndex: 0 + readonly property string toolIconUrl: "file:///C:/Users/bdavi/git/hifi/scripts/system/assets/images/tools/" + text: "Create Button" + onClicked: { + var name = icons[iconIndex]; + var url = toolIconUrl + name; + iconIndex = (iconIndex + 1) % icons.length; + var button = testButtons.lastButton = testButtons.toolbar.addButton({ + imageURL: url, + objectName: name, + subImage: { + y: 50, + }, + alpha: 0.9 + }); + + button.clicked.connect(function(){ + console.log("Clicked on button " + button.imageURL + " alpha " + button.alpha) + }); + } + } + + Button { + text: "Toggle Button Visible" + onClicked: testButtons.lastButton.visible = !testButtons.lastButton.visible + } + + + + // Error alerts /* Button { @@ -106,7 +138,7 @@ ApplicationWindow { */ // Browser - + /* Button { text: "Open Browser" onClicked: builder.createObject(desktop); @@ -114,8 +146,11 @@ ApplicationWindow { Browser {} } } + */ + // file dialog + /* Button { text: "Open Directory" @@ -153,7 +188,6 @@ ApplicationWindow { }) } } - /* */ // tabs @@ -306,6 +340,7 @@ ApplicationWindow { } */ + /* Window { id: blue closable: true @@ -350,6 +385,8 @@ ApplicationWindow { height: green.height; } } + */ + /* Window { id: yellow @@ -379,3 +416,7 @@ ApplicationWindow { } } } + + + + diff --git a/tests/ui/qmlscratch.pro b/tests/ui/qmlscratch.pro index 95be6a480e..151893de2f 100644 --- a/tests/ui/qmlscratch.pro +++ b/tests/ui/qmlscratch.pro @@ -29,6 +29,7 @@ DISTFILES += \ ../../interface/resources/qml/styles-uit/*.qml \ ../../interface/resources/qml/windows/*.qml \ ../../interface/resources/qml/hifi/*.qml \ + ../../interface/resources/qml/hifi/toolbars/*.qml \ ../../interface/resources/qml/hifi/dialogs/*.qml \ ../../interface/resources/qml/hifi/dialogs/preferences/*.qml \ ../../interface/resources/qml/hifi/overlays/*.qml diff --git a/tests/ui/src/main.cpp b/tests/ui/src/main.cpp index 0cabfe28f5..e3cf37ba04 100644 --- a/tests/ui/src/main.cpp +++ b/tests/ui/src/main.cpp @@ -86,9 +86,11 @@ int main(int argc, char *argv[]) { setChild(engine, "offscreenFlags"); setChild(engine, "Account"); setChild(engine, "ApplicationCompositor"); + setChild(engine, "Controller"); setChild(engine, "Desktop"); setChild(engine, "ScriptDiscoveryService"); setChild(engine, "HMD"); + setChild(engine, "GL"); setChild(engine, "MenuHelper"); setChild(engine, "Preferences"); setChild(engine, "urlHandler"); @@ -101,3 +103,4 @@ int main(int argc, char *argv[]) { } #include "main.moc" + From 6196c657f8e5fe8263bcd9bce09b4a4f0c860985 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Sat, 18 Jun 2016 14:17:47 -0700 Subject: [PATCH 069/171] Minimize triggering distance to HUD. --- interface/src/ui/OverlayConductor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index 74e27122e4..003de23030 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -32,7 +32,7 @@ bool OverlayConductor::headOutsideOverlay() const { glm::vec3 uiPos = uiTransform.getTranslation(); glm::vec3 uiForward = uiTransform.getRotation() * glm::vec3(0.0f, 0.0f, -1.0f); - const float MAX_COMPOSITOR_DISTANCE = 0.6f; + const float MAX_COMPOSITOR_DISTANCE = 0.99f; // If you're 1m from center of ui sphere, you're at the surface. const float MAX_COMPOSITOR_ANGLE = 180.0f; // rotation check is effectively disabled if (glm::distance(uiPos, hmdPos) > MAX_COMPOSITOR_DISTANCE || glm::dot(uiForward, hmdForward) < cosf(glm::radians(MAX_COMPOSITOR_ANGLE))) { From 81300ec1270f1d11eda3d5a530be5d59a4ad3041 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Sat, 18 Jun 2016 14:35:37 -0700 Subject: [PATCH 070/171] turn hud reset behavior on by default --- interface/src/avatar/MyAvatar.cpp | 4 ++-- interface/src/avatar/MyAvatar.h | 6 +++--- interface/src/ui/OverlayConductor.cpp | 2 +- interface/src/ui/PreferencesDialog.cpp | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 5d1efca902..6a69ee9a9a 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -722,7 +722,7 @@ void MyAvatar::saveData() { settings.setValue("displayName", _displayName); settings.setValue("collisionSoundURL", _collisionSoundURL); settings.setValue("useSnapTurn", _useSnapTurn); - settings.setValue("clearOverlayWhenDriving", _clearOverlayWhenDriving); + settings.setValue("clearOverlayWhenMoving", _clearOverlayWhenMoving); settings.endGroup(); } @@ -842,7 +842,7 @@ void MyAvatar::loadData() { setDisplayName(settings.value("displayName").toString()); setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString()); setSnapTurn(settings.value("useSnapTurn", _useSnapTurn).toBool()); - setClearOverlayWhenDriving(settings.value("clearOverlayWhenDriving", _clearOverlayWhenDriving).toBool()); + setClearOverlayWhenMoving(settings.value("clearOverlayWhenMoving", _clearOverlayWhenMoving).toBool()); settings.endGroup(); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 6fa6e1fd19..96fa999de5 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -159,8 +159,8 @@ public: Q_INVOKABLE bool getSnapTurn() const { return _useSnapTurn; } Q_INVOKABLE void setSnapTurn(bool on) { _useSnapTurn = on; } - Q_INVOKABLE bool getClearOverlayWhenDriving() const { return _clearOverlayWhenDriving; } - Q_INVOKABLE void setClearOverlayWhenDriving(bool on) { _clearOverlayWhenDriving = on; } + Q_INVOKABLE bool getClearOverlayWhenMoving() const { return _clearOverlayWhenMoving; } + Q_INVOKABLE void setClearOverlayWhenMoving(bool on) { _clearOverlayWhenMoving = on; } Q_INVOKABLE void setHMDLeanRecenterEnabled(bool value) { _hmdLeanRecenterEnabled = value; } Q_INVOKABLE bool getHMDLeanRecenterEnabled() const { return _hmdLeanRecenterEnabled; } @@ -405,7 +405,7 @@ private: QString _fullAvatarModelName; QUrl _animGraphUrl {""}; bool _useSnapTurn { true }; - bool _clearOverlayWhenDriving { false }; + bool _clearOverlayWhenMoving { true }; // cache of the current HMD sensor position and orientation // in sensor space. diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index 74e27122e4..91046c4cc6 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -124,7 +124,7 @@ void OverlayConductor::update(float dt) { _flags &= ~SuppressedByDrive; } } else { - if (myAvatar->getClearOverlayWhenDriving() && drivingChanged && isDriving) { + if (myAvatar->getClearOverlayWhenMoving() && drivingChanged && isDriving) { _flags |= SuppressedByDrive; } } diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 6decef3240..c1705da206 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -62,9 +62,9 @@ void setupPreferences() { preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Snap turn when in HMD", getter, setter)); } { - auto getter = [=]()->bool {return myAvatar->getClearOverlayWhenDriving(); }; - auto setter = [=](bool value) { myAvatar->setClearOverlayWhenDriving(value); }; - preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Clear overlays when driving", getter, setter)); + auto getter = [=]()->bool {return myAvatar->getClearOverlayWhenMoving(); }; + auto setter = [=](bool value) { myAvatar->setClearOverlayWhenMoving(value); }; + preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Clear overlays when moving", getter, setter)); } { auto getter = []()->QString { return Snapshot::snapshotsLocation.get(); }; From 17591323777d46f350221e8d34b6bc6d831ca054 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 14 Jun 2016 18:01:10 -0700 Subject: [PATCH 071/171] Use trigger to both grab and equip objects * Removed entry into HOLD state via secondary aka grip buttons. * Changed equip logic to prioritize equip over near over far grabs. * Added drop gesture, upside down controller + slight shake + trigger press * Bug fix for near grab snapping to an incorrect offset after a far grab (this bug is present in master) --- .../system/controllers/handControllerGrab.js | 743 +++++++++++------- 1 file changed, 466 insertions(+), 277 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 4ec1675ede..8c5d48667d 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -14,12 +14,11 @@ Script.include("/~/system/libraries/utils.js"); - // // add lines where the hand ray picking is happening // var WANT_DEBUG = false; -var WANT_DEBUG_STATE = false; +var WANT_DEBUG_STATE = true; var WANT_DEBUG_SEARCH_NAME = null; // @@ -126,8 +125,6 @@ var DEFAULT_GRABBABLE_DATA = { disableReleaseVelocity: false }; - - // sometimes we want to exclude objects from being picked var USE_BLACKLIST = true; var blacklist = []; @@ -140,19 +137,20 @@ var USE_ENTITY_LINES_FOR_MOVING = false; var USE_OVERLAY_LINES_FOR_MOVING = false; var USE_PARTICLE_BEAM_FOR_MOVING = true; - var USE_SPOTLIGHT = false; var USE_POINTLIGHT = false; +var FORBIDDEN_GRAB_NAMES = ["Grab Debug Entity", "grab pointer"]; +var FORBIDDEN_GRAB_TYPES = ['Unknown', 'Light', 'PolyLine', 'Zone']; + // states for the state machine var STATE_OFF = 0; var STATE_SEARCHING = 1; -var STATE_HOLD_SEARCHING = 2; -var STATE_DISTANCE_HOLDING = 3; -var STATE_NEAR_GRABBING = 4; -var STATE_NEAR_TRIGGER = 5; -var STATE_FAR_TRIGGER = 6; -var STATE_HOLD = 7; +var STATE_DISTANCE_HOLDING = 2; +var STATE_NEAR_GRABBING = 3; +var STATE_NEAR_TRIGGER = 4; +var STATE_FAR_TRIGGER = 5; +var STATE_HOLD = 6; // "collidesWith" is specified by comma-separated list of group names // the possible group names are: static, dynamic, kinematic, myAvatar, otherAvatar @@ -175,10 +173,6 @@ CONTROLLER_STATE_MACHINE[STATE_SEARCHING] = { enterMethod: "searchEnter", exitMethod: "searchExit" }; -CONTROLLER_STATE_MACHINE[STATE_HOLD_SEARCHING] = { - name: "hold_searching", - updateMethod: "search" -}; CONTROLLER_STATE_MACHINE[STATE_DISTANCE_HOLDING] = { name: "distance_holding", enterMethod: "distanceHoldingEnter", @@ -259,6 +253,58 @@ function restore2DMode() { } } +function filter(array, predicate) { + var i, l = array.length; + var result = []; + for (i = 0; i < l; i++) { + if (predicate(array[i])) { + result.push(array[i]); + } + } + return result; +} + +// constructor +function EntityPropertiesCache() { + this.cache = {}; +} +EntityPropertiesCache.prototype.clear = function() { + this.cache = {}; +}; +EntityPropertiesCache.prototype.findEntities = function(position, radius) { + var entities = Entities.findEntities(position, radius); + var i, l = entities.length; + for (i = 0; i < l; i++) { + this.addEntity(entities[i]); + } +}; +EntityPropertiesCache.prototype.addEntity = function(entityID) { + var props = Entities.getEntityProperties(entityID, GRABBABLE_PROPERTIES); + var grabbableProps = getEntityCustomData(GRABBABLE_DATA_KEY, entityID, DEFAULT_GRABBABLE_DATA); + var grabProps = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); + var wearableProps = getEntityCustomData("wearable", entityID, {}); + this.cache[entityID] = { props: props, grabbableProps: grabbableProps, grabProps: grabProps, wearableProps: wearableProps }; +}; +EntityPropertiesCache.prototype.getEntities = function() { + return Object.keys(this.cache); +} +EntityPropertiesCache.prototype.getProps = function(entityID) { + var obj = this.cache[entityID] + return obj ? obj.props : undefined; +}; +EntityPropertiesCache.prototype.getGrabbableProps = function(entityID) { + var obj = this.cache[entityID] + return obj ? obj.grabbableProps : undefined; +}; +EntityPropertiesCache.prototype.getGrabProps = function(entityID) { + var obj = this.cache[entityID] + return obj ? obj.grabProps : undefined; +}; +EntityPropertiesCache.prototype.getWearableProps = function(entityID) { + var obj = this.cache[entityID] + return obj ? obj.wearableProps : undefined; +}; + function MyController(hand) { this.hand = hand; if (this.hand === RIGHT_HAND) { @@ -295,6 +341,8 @@ function MyController(hand) { this.overlayLine = null; this.searchSphere = null; + this.waitForTriggerRelease = false; + // how far from camera to search intersection? var DEFAULT_SEARCH_SPHERE_DISTANCE = 1000; this.intersectionDistance = 0.0; @@ -307,16 +355,18 @@ function MyController(hand) { this.lastPickTime = 0; this.lastUnequipCheckTime = 0; + this.entityPropertyCache = new EntityPropertiesCache(); + var _this = this; - var suppressedIn2D = [STATE_OFF, STATE_SEARCHING, STATE_HOLD_SEARCHING]; + var suppressedIn2D = [STATE_OFF, STATE_SEARCHING]; this.ignoreInput = function () { // We've made the decision to use 'this' for new code, even though it is fragile, // in order to keep/ the code uniform without making any no-op line changes. return (-1 !== suppressedIn2D.indexOf(this.state)) && isIn2DMode(); }; - this.update = function() { + this.update = function(deltaTime) { this.updateSmoothedTrigger(); @@ -329,7 +379,7 @@ function MyController(hand) { var updateMethodName = CONTROLLER_STATE_MACHINE[this.state].updateMethod; var updateMethod = this[updateMethodName]; if (updateMethod) { - updateMethod.call(this); + updateMethod.call(this, deltaTime); } else { print("WARNING: could not find updateMethod for state " + stateToName(this.state)); } @@ -343,12 +393,12 @@ function MyController(hand) { Entities.callEntityMethod(this.grabbedEntity, entityMethodName, args); } - this.setState = function(newState) { + this.setState = function(newState, reason) { this.grabSphereOff(); if (WANT_DEBUG || WANT_DEBUG_STATE) { var oldStateName = stateToName(this.state); var newStateName = stateToName(newState); - print("STATE (" + this.hand + "): " + newStateName + " <-- " + oldStateName); + print("STATE (" + this.hand + "): " + newStateName + " <-- " + oldStateName + ", reason = " + reason); } // exit the old state @@ -819,14 +869,16 @@ function MyController(hand) { }; this.off = function() { - if (this.triggerSmoothedSqueezed() || this.secondarySqueezed()) { + + if (this.triggerSmoothedReleased()) { + this.waitForTriggerRelease = false; + } + if (!this.waitForTriggerRelease && this.triggerSmoothedSqueezed()) { this.lastPickTime = 0; var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; this.startingHandRotation = Controller.getPoseValue(controllerHandInput).rotation; if (this.triggerSmoothedSqueezed()) { - this.setState(STATE_SEARCHING); - } else if (this.secondarySqueezed()) { - this.setState(STATE_HOLD_SEARCHING); + this.setState(STATE_SEARCHING, "trigger squeeze detected"); } } }; @@ -875,265 +927,345 @@ function MyController(hand) { this.equipHotspotOverlays = []; }; + /** + * Performs ray pick test from the hand controller into the world + * @param {number} which hand to use, RIGHT_HAND or LEFT_HAND + * @returns {object} returns object with two keys entityID and distance + */ + this.calcRayPickInfo = function(hand) { + + var pose = Controller.getPoseValue((hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand); + var worldHandPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation), MyAvatar.position); + var worldHandRotation = Quat.multiply(MyAvatar.orientation, pose.rotation); + + var pickRay = { + origin: PICK_WITH_HAND_RAY ? worldHandPosition : Camera.position, + direction: PICK_WITH_HAND_RAY ? Quat.getUp(worldHandRotation) : Vec3.mix(Quat.getUp(worldHandRotation), + Quat.getFront(Camera.orientation), + HAND_HEAD_MIX_RATIO), + length: PICK_MAX_DISTANCE + }; + + var result = { entityID: null, + searchRay: pickRay, + distance: PICK_MAX_DISTANCE }; + + var now = Date.now(); + if (now - this.lastPickTime < MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { + return result; + } + this.lastPickTime = now; + + var directionNormalized = Vec3.normalize(pickRay.direction); + var directionBacked = Vec3.multiply(directionNormalized, PICK_BACKOFF_DISTANCE); + var pickRayBacked = { + origin: Vec3.subtract(pickRay.origin, directionBacked), + direction: pickRay.direction + }; + + var intersection; + if (USE_BLACKLIST === true && blacklist.length !== 0) { + intersection = Entities.findRayIntersection(pickRayBacked, true, [], blacklist); + } else { + intersection = Entities.findRayIntersection(pickRayBacked, true); + } + + var overlayIntersection = Overlays.findRayIntersection(pickRayBacked); + if (!intersection.intersects || (overlayIntersection.intersects && (intersection.distance > overlayIntersection.distance))) { + intersection = overlayIntersection; + } + + if (intersection.intersects) { + return { entityID: intersection.entityID, + searchRay: pickRay, + distance: Vec3.distance(pickRay.origin, intersection.intersection) } + } else { + return result; + } + }; + + this.entityWantsTrigger = function (entityID) { + var grabbableProps = this.entityPropertyCache.getGrabbableProps(entityID); + return grabbableProps && grabbableProps.wantsTrigger; + }; + + this.entityIsEquippable = function (entityID, handPosition) { + var props = this.entityPropertyCache.getProps(entityID); + var distance = Vec3.distance(props.position, handPosition); + var grabProps = this.entityPropertyCache.getGrabProps(entityID); + var debug = true;//(WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); + + var refCount = ("refCount" in grabProps) ? grabProps.refCount : 0; + if (refCount > 0) { + if (debug) { + print("equip is skipping '" + props.name + "': it is already grabbed"); + } + return false; + } + + if (distance > NEAR_PICK_MAX_DISTANCE) { + if (debug) { + print("equip is skipping '" + props.name + "': too far away."); + } + return false; + } + + var wearableProps = this.entityPropertyCache.getWearableProps(entityID); + if (!wearableProps || !wearableProps.joints) { + if (debug) { + print("equip is skipping '" + props.name + "': no wearable attach-point"); + } + return false; + } + + var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"; + if (!wearableProps.joints[handJointName]) { + if (debug) { + print("equip is skipping '" + props.name + "': no wearable joint for " + handJointName); + } + return false; + } + + return true; + }; + + this.entityIsGrabbable = function (entityID) { + var grabbableProps = this.entityPropertyCache.getGrabbableProps(entityID); + var grabProps = this.entityPropertyCache.getGrabProps(entityID); + var props = this.entityPropertyCache.getProps(entityID); + var physical = propsArePhysical(props); + var grabbable = false; + var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); + + if (physical) { + // physical things default to grabbable + grabbable = true; + } else { + // non-physical things default to non-grabbable unless they are already grabbed + if ("refCount" in grabProps && grabProps.refCount > 0) { + grabbable = true; + } else { + grabbable = false; + } + } + + if (grabbableProps.hasOwnProperty("grabbable")) { + grabbable = grabbableProps.grabbable; + } + + if (!grabbable && !grabbableProps.wantsTrigger) { + if (debug) { + print("grab is skipping '" + props.name + "': not grabbable."); + } + return false; + } + if (FORBIDDEN_GRAB_TYPES.indexOf(props.type) >= 0) { + if (debug) { + print("grab is skipping '" + props.name + "': forbidden entity type."); + } + return false; + } + if (props.locked && !grabbableProps.wantsTrigger) { + if (debug) { + print("grab is skipping '" + props.name + "': locked and not triggerable."); + } + return false; + } + if (FORBIDDEN_GRAB_NAMES.indexOf(props.name) >= 0) { + if (debug) { + print("grab is skipping '" + props.name + "': forbidden name."); + } + return false; + } + + return true; + }; + + this.entityIsDistanceGrabbable = function(entityID, handPosition) { + if (!this.entityIsGrabbable(entityID)) { + return false; + } + + var props = this.entityPropertyCache.getProps(entityID); + var distance = Vec3.distance(props.position, handPosition); + var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); + + // we can't distance-grab non-physical + var isPhysical = propsArePhysical(props); + if (!isPhysical) { + if (debug) { + print("distance grab is skipping '" + props.name + "': not physical"); + } + return false; + } + + if (distance > PICK_MAX_DISTANCE) { + // too far away, don't grab + if (debug) { + print("distance grab is skipping '" + props.name + "': too far away."); + } + return false; + } + + if (entityIsGrabbedByOther(entityID)) { + // don't distance grab something that is already grabbed. + if (debug) { + print("distance grab is skipping '" + props.name + "': already grabbed by another."); + } + return false; + } + + return true; + }; + + this.entityIsNearGrabbable = function(entityID, handPosition) { + + if (!this.entityIsGrabbable(entityID)) { + return false; + } + + var props = this.entityPropertyCache.getProps(entityID); + var distance = Vec3.distance(props.position, handPosition); + var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); + + if (distance > NEAR_PICK_MAX_DISTANCE) { + // too far away, don't grab + if (debug) { + print(" grab is skipping '" + props.name + "': too far away."); + } + return false; + } + + return true; + }; + this.search = function() { + var _this = this; + var name; + this.grabbedEntity = null; this.isInitialGrab = false; this.shouldResetParentOnRelease = false; this.checkForStrayChildren(); - if (this.state == STATE_SEARCHING && this.triggerSmoothedReleased()) { - this.setState(STATE_OFF); + if (this.triggerSmoothedReleased()) { + this.setState(STATE_OFF, "trigger released"); return; } - if (this.state == STATE_HOLD_SEARCHING && this.secondaryReleased()) { - this.setState(STATE_OFF); - return; - } - - // the trigger is being pressed, so do a ray test to see what we are hitting var handPosition = this.getHandPosition(); if (SHOW_GRAB_SPHERE) { this.grabSphereOn(); } - var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - var currentControllerPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, - Controller.getPoseValue(controllerHandInput).translation), - MyAvatar.position); - var avatarControllerPose = Controller.getPoseValue((this.hand === RIGHT_HAND) ? - Controller.Standard.RightHand : Controller.Standard.LeftHand); - var controllerRotation = Quat.multiply(MyAvatar.orientation, avatarControllerPose.rotation); + this.entityPropertyCache.clear(); + this.entityPropertyCache.findEntities(handPosition, GRAB_RADIUS); + var candidateEntities = this.entityPropertyCache.getEntities(); - var distantPickRay = { - origin: PICK_WITH_HAND_RAY ? currentControllerPosition : Camera.position, - direction: PICK_WITH_HAND_RAY ? Quat.getUp(controllerRotation) : Vec3.mix(Quat.getUp(controllerRotation), - Quat.getFront(Camera.orientation), - HAND_HEAD_MIX_RATIO), - length: PICK_MAX_DISTANCE - }; + var equippableEntities = filter(candidateEntities, function (entity) { + return _this.entityIsEquippable(entity, handPosition); + }); - // Pick at some maximum rate, not always - var pickRays = []; - var now = Date.now(); - if (now - this.lastPickTime > MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { - pickRays = [distantPickRay]; - this.lastPickTime = now; - } - - var rayPickedCandidateEntities = []; // the list of candidates to consider grabbing - - this.intersectionDistance = 0.0; - for (var index = 0; index < pickRays.length; ++index) { - var pickRay = pickRays[index]; - var directionNormalized = Vec3.normalize(pickRay.direction); - var directionBacked = Vec3.multiply(directionNormalized, PICK_BACKOFF_DISTANCE); - var pickRayBacked = { - origin: Vec3.subtract(pickRay.origin, directionBacked), - direction: pickRay.direction - }; - - var intersection; - - if (USE_BLACKLIST === true && blacklist.length !== 0) { - intersection = Entities.findRayIntersection(pickRayBacked, true, [], blacklist); - } else { - intersection = Entities.findRayIntersection(pickRayBacked, true); - } - var overlayIntersection = Overlays.findRayIntersection(pickRayBacked); - if (!intersection.intersects || (overlayIntersection.intersects && (intersection.distance > overlayIntersection.distance))) { - intersection = overlayIntersection; - } - // If we want to share results with other scripts, this is where we would do it. - - if (intersection.intersects) { - if (intersection.entityID) { - rayPickedCandidateEntities.push(intersection.entityID); - } - this.intersectionDistance = Vec3.distance(pickRay.origin, intersection.intersection); - } - } - - var nearPickedCandidateEntities = Entities.findEntities(handPosition, GRAB_RADIUS); - var candidateEntities = rayPickedCandidateEntities.concat(nearPickedCandidateEntities); - - var forbiddenNames = ["Grab Debug Entity", "grab pointer"]; - var forbiddenTypes = ['Unknown', 'Light', 'PolyLine', 'Zone']; - - var minDistance = PICK_MAX_DISTANCE; - var i, props, distance, grabbableData; - this.grabbedEntity = null; - for (i = 0; i < candidateEntities.length; i++) { - var grabbableDataForCandidate = - getEntityCustomData(GRABBABLE_DATA_KEY, candidateEntities[i], DEFAULT_GRABBABLE_DATA); - var grabDataForCandidate = getEntityCustomData(GRAB_USER_DATA_KEY, candidateEntities[i], {}); - var propsForCandidate = Entities.getEntityProperties(candidateEntities[i], GRABBABLE_PROPERTIES); - var near = (nearPickedCandidateEntities.indexOf(candidateEntities[i]) >= 0); - - var physical = propsArePhysical(propsForCandidate); - var grabbable; - if (physical) { - // physical things default to grabbable - grabbable = true; - } else { - // non-physical things default to non-grabbable unless they are already grabbed - if ("refCount" in grabDataForCandidate && grabDataForCandidate.refCount > 0) { - grabbable = true; - } else { - grabbable = false; - } - } - - if ("grabbable" in grabbableDataForCandidate) { - // if userData indicates that this is grabbable or not, override the default. - grabbable = grabbableDataForCandidate.grabbable; - } - - if (!grabbable && !grabbableDataForCandidate.wantsTrigger) { - if (WANT_DEBUG_SEARCH_NAME && propsForCandidate.name == WANT_DEBUG_SEARCH_NAME) { - print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': not grabbable."); - } - continue; - } - if (forbiddenTypes.indexOf(propsForCandidate.type) >= 0) { - if (WANT_DEBUG_SEARCH_NAME && propsForCandidate.name == WANT_DEBUG_SEARCH_NAME) { - print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': forbidden entity type."); - } - continue; - } - if (propsForCandidate.locked && !grabbableDataForCandidate.wantsTrigger) { - if (WANT_DEBUG_SEARCH_NAME && propsForCandidate.name == WANT_DEBUG_SEARCH_NAME) { - print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': locked and not triggerable."); - } - continue; - } - if (forbiddenNames.indexOf(propsForCandidate.name) >= 0) { - if (WANT_DEBUG_SEARCH_NAME && propsForCandidate.name == WANT_DEBUG_SEARCH_NAME) { - print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': forbidden name."); - } - continue; - } - - distance = Vec3.distance(propsForCandidate.position, handPosition); - if (distance > PICK_MAX_DISTANCE) { - // too far away, don't grab - if (WANT_DEBUG_SEARCH_NAME && propsForCandidate.name == WANT_DEBUG_SEARCH_NAME) { - print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': too far away."); - } - continue; - } - if (propsForCandidate.parentID != NULL_UUID && this.state == STATE_HOLD_SEARCHING) { - // don't allow a double-equip - if (WANT_DEBUG_SEARCH_NAME && propsForCandidate.name == WANT_DEBUG_SEARCH_NAME) { - print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': it's a child"); - } - continue; - } - - if (this.state == STATE_SEARCHING && - !physical && distance > NEAR_PICK_MAX_DISTANCE && !near && !grabbableDataForCandidate.wantsTrigger) { - // we can't distance-grab non-physical - if (WANT_DEBUG_SEARCH_NAME && propsForCandidate.name == WANT_DEBUG_SEARCH_NAME) { - print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': not physical and too far for near-grab"); - } - continue; - } - - if (distance < minDistance) { - this.grabbedEntity = candidateEntities[i]; - minDistance = distance; - props = propsForCandidate; - grabbableData = grabbableDataForCandidate; - } - } - if ((this.grabbedEntity !== null) && (this.triggerSmoothedGrab() || this.secondarySqueezed())) { - // We are squeezing enough to grab, and we've found an entity that we'll try to do something with. - var isNear = (nearPickedCandidateEntities.indexOf(this.grabbedEntity) >= 0) || minDistance <= NEAR_PICK_MAX_DISTANCE; - var isPhysical = propsArePhysical(props); - - // near or far trigger - if (grabbableData.wantsTrigger) { - this.setState(isNear ? STATE_NEAR_TRIGGER : STATE_FAR_TRIGGER); + var entity; + if (equippableEntities.length > 0) { + // sort by distance + equippableEntities.sort(function (a, b) { + var aDistance = Vec3.distance(_this.entityPropertyCache.getProps(a).position, handPosition); + var bDistance = Vec3.distance(_this.entityPropertyCache.getProps(b).position, handPosition); + return aDistance - bDistance; + }); + entity = equippableEntities[0]; + if (this.triggerSmoothedGrab()) { + this.grabbedEntity = entity; + this.setState(STATE_HOLD, "eqipping '" + this.entityPropertyCache.getProps(entity).name + "'"); return; + } else { + // TODO: highlight the equippable object? } - // near grab with action or equip - var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, {}); - var refCount = ("refCount" in grabData) ? grabData.refCount : 0; - if (isNear && (refCount < 1 || entityHasActions(this.grabbedEntity))) { - if (this.state == STATE_SEARCHING) { - this.setState(STATE_NEAR_GRABBING); - } else { // (this.state == STATE_HOLD_SEARCHING) - // if there was already an action, we'll need to set the parent back to null once we release - this.shouldResetParentOnRelease = true; - this.previousParentID = props.parentID; - this.previousParentJointIndex = props.parentJointIndex; - this.setState(STATE_HOLD); - } - return; - } - // far grab - if (isPhysical && !isNear) { - if (entityIsGrabbedByOther(this.grabbedEntity)) { - // don't distance grab something that is already grabbed. - if (WANT_DEBUG_SEARCH_NAME && props.name == WANT_DEBUG_SEARCH_NAME) { - print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': already grabbed by another."); - } + } + + var rayPickInfo = this.calcRayPickInfo(this.hand); + this.intersectionDistance = rayPickInfo.distance; + if (rayPickInfo.entityID) { + candidateEntities.push(rayPickInfo.entityID); + this.entityPropertyCache.addEntity(rayPickInfo.entityID); + } + + var grabbableEntities = filter(candidateEntities, function (entity) { + return _this.entityIsNearGrabbable(entity, handPosition); + }); + + if (grabbableEntities.length > 0) { + // sort by distance + grabbableEntities.sort(function (a, b) { + var aDistance = Vec3.distance(_this.entityPropertyCache.getProps(a).position, handPosition); + var bDistance = Vec3.distance(_this.entityPropertyCache.getProps(b).position, handPosition); + return aDistance - bDistance; + }); + entity = grabbableEntities[0]; + name = this.entityPropertyCache.getProps(entity).name; + this.grabbedEntity = entity; + if (this.entityWantsTrigger(entity)) { + if (this.triggerSmoothedGrab()) { + this.setState(STATE_NEAR_TRIGGER, "near trigger '" + name + "'"); return; + } else { + // TODO: highlight the near-triggerable object? } - this.temporaryPositionOffset = null; - if (!this.hasPresetOffsets()) { - // We want to give a temporary position offset to this object so it is pulled close to hand - var intersectionPointToCenterDistance = Vec3.length(Vec3.subtract(intersection.intersection, - intersection.properties.position)); - var handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); - var handJointPosition = MyAvatar.getJointPosition(handJointIndex); - this.temporaryPositionOffset = - Vec3.normalize(Vec3.subtract(intersection.properties.position, handJointPosition)); - this.temporaryPositionOffset = Vec3.multiply(this.temporaryPositionOffset, - intersectionPointToCenterDistance * - FAR_TO_NEAR_GRAB_PADDING_FACTOR); - } - this.setState(STATE_DISTANCE_HOLDING); - - this.searchSphereOff(); - return; - } - - // else this thing isn't physical. grab it by reparenting it (but not if we've already - // grabbed it). - if (refCount < 1) { - if (this.state == STATE_SEARCHING) { - this.setState(STATE_NEAR_GRABBING); - } else { // this.state == STATE_HOLD_SEARCHING) - this.setState(STATE_HOLD); - } - return; } else { - // it's not physical and it's already held via parenting. go ahead and grab it, but - // save off the current parent and joint. this wont always be right if there are more than - // two grabs and the order of release isn't opposite of the order of grabs. - this.shouldResetParentOnRelease = true; - this.previousParentID = props.parentID; - this.previousParentJointIndex = props.parentJointIndex; - if (this.state == STATE_SEARCHING) { - this.setState(STATE_NEAR_GRABBING); - } else { // (this.state == STATE_HOLD_SEARCHING) - this.setState(STATE_HOLD); + if (this.triggerSmoothedGrab()) { + + var props = this.entityPropertyCache.getProps(entity); + var grabProps = this.entityPropertyCache.getGrabProps(entity); + var refCount = grabProps.refCount ? grabProps.refCount : 0; + if (refCount >= 1) { + // if another person is holding the object, remember to restore the + // parent info, when we are finished grabbing it. + this.shouldResetParentOnRelease = true; + this.previousParentID = props.parentID; + this.previousParentJointIndex = props.parentJointIndex; + } + + this.setState(STATE_NEAR_GRABBING, "near grab '" + name + "'"); + return; + } else { + // TODO: highlight the grabbable object? + } + } + return; + } + + if (rayPickInfo.entityID) { + entity = rayPickInfo.entityID; + name = this.entityPropertyCache.getProps(entity).name; + if (this.entityWantsTrigger(entity)) { + if (this.triggerSmoothedGrab()) { + this.grabbedEntity = entity; + this.setState(STATE_FAR_TRIGGER, "far trigger '" + name + "'"); + return; + } else { + // TODO: highlight the far-triggerable object? + } + } else if (this.entityIsDistanceGrabbable(rayPickInfo.entityID, handPosition)) { + if (this.triggerSmoothedGrab()) { + this.grabbedEntity = entity; + this.setState(STATE_DISTANCE_HOLDING, "distance hold '" + name + "'"); + return; + } else { + // TODO: highlight the far-grabbable object? } - return; } } //search line visualizations if (USE_ENTITY_LINES_FOR_SEARCHING === true) { - this.lineOn(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); + this.lineOn(rayPickInfo.searchRay.origin, Vec3.multiply(rayPickInfo.searchRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); } - this.searchIndicatorOn(distantPickRay); + this.searchIndicatorOn(rayPickInfo.searchRay); Reticle.setVisible(false); - }; this.distanceGrabTimescale = function(mass, distance) { @@ -1210,8 +1342,8 @@ function MyController(hand) { }; this.distanceHolding = function() { - if (this.triggerSmoothedReleased() && this.secondaryReleased()) { - this.setState(STATE_OFF); + if (this.triggerSmoothedReleased()) { + this.setState(STATE_OFF, "trigger released"); this.callEntityMethodOnGrabbed("releaseGrab"); return; } @@ -1230,7 +1362,7 @@ function MyController(hand) { var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); var now = Date.now(); - var deltaTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds + var deltaObjectTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds this.currentObjectTime = now; // the action was set up when this.distanceHolding was called. update the targets. @@ -1267,17 +1399,17 @@ function MyController(hand) { // Update radialVelocity var lastVelocity = Vec3.subtract(controllerPositionVSAvatar, this.previousControllerPositionVSAvatar); - lastVelocity = Vec3.multiply(lastVelocity, 1.0 / deltaTime); + lastVelocity = Vec3.multiply(lastVelocity, 1.0 / deltaObjectTime); var newRadialVelocity = Vec3.dot(lastVelocity, Vec3.normalize(Vec3.subtract(grabbedProperties.position, controllerPosition))); var VELOCITY_AVERAGING_TIME = 0.016; - this.grabRadialVelocity = (deltaTime / VELOCITY_AVERAGING_TIME) * newRadialVelocity + - (1.0 - (deltaTime / VELOCITY_AVERAGING_TIME)) * this.grabRadialVelocity; + this.grabRadialVelocity = (deltaObjectTime / VELOCITY_AVERAGING_TIME) * newRadialVelocity + + (1.0 - (deltaObjectTime / VELOCITY_AVERAGING_TIME)) * this.grabRadialVelocity; var RADIAL_GRAB_AMPLIFIER = 10.0; if (Math.abs(this.grabRadialVelocity) > 0.0) { - this.grabRadius = this.grabRadius + (this.grabRadialVelocity * deltaTime * this.grabRadius * RADIAL_GRAB_AMPLIFIER); + this.grabRadius = this.grabRadius + (this.grabRadialVelocity * deltaObjectTime * this.grabRadius * RADIAL_GRAB_AMPLIFIER); } var newTargetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(controllerRotation)); @@ -1431,11 +1563,56 @@ function MyController(hand) { } } + this.dropGestureReset = function() { + this.fastHandMoveDetected = false; + this.fastHandMoveTimer = 0; + }; + + this.dropGestureProcess = function(deltaTime) { + var pose = Controller.getPoseValue((this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand); + var worldHandVelocity = Vec3.multiplyQbyV(MyAvatar.orientation, pose.velocity); + var worldHandRotation = Quat.multiply(MyAvatar.orientation, pose.rotation); + + if (this.fastHandMoveDetected) { + this.fastHandMoveTimer -= deltaTime; + } + if (this.fastHandMoveTimer < 0) { + this.fastHandMoveDetected = false; + } + var FAST_HAND_SPEED_REST_TIME = 1; // sec + var FAST_HAND_SPEED_THRESHOLD = 0.4; // m/sec + if (Vec3.length(worldHandVelocity) > FAST_HAND_SPEED_THRESHOLD) { + this.fastHandMoveDetected = true; + this.fastHandMoveTimer = FAST_HAND_SPEED_REST_TIME; + } + + var localHandUpAxis = this.hand === RIGHT_HAND ? {x: 1, y: 0, z: 0} : {x: -1, y: 0, z: 0}; + var worldHandUpAxis = Vec3.multiplyQbyV(worldHandRotation, localHandUpAxis); + var DOWN = {x: 0, y: -1, z: 0}; + var ROTATION_THRESHOLD = Math.cos(Math.PI / 8); + + var handIsUpsideDown = false; + if (Vec3.dot(worldHandUpAxis, DOWN) > ROTATION_THRESHOLD) { + handIsUpsideDown = true; + } + + var WANT_DEBUG = false; + if (WANT_DEBUG) { + print("zAxis = " + worldHandUpAxis.x + ", " + worldHandUpAxis.y + ", " + worldHandUpAxis.z); + print("dot = " + Vec3.dot(worldHandUpAxis, DOWN) + ", ROTATION_THRESHOLD = " + ROTATION_THRESHOLD); + print("handMove = " + this.fastHandMoveDetected + ", handIsUpsideDown = " + handIsUpsideDown); + } + + return this.fastHandMoveDetected && handIsUpsideDown; + }; + this.nearGrabbingEnter = function() { this.lineOff(); this.overlayLineOff(); + this.dropGestureReset(); + if (this.entityActivated) { var saveGrabbedID = this.grabbedEntity; this.release(); @@ -1524,14 +1701,18 @@ function MyController(hand) { this.currentAngularVelocity = ZERO_VEC; }; - this.nearGrabbing = function() { + this.nearGrabbing = function(deltaTime) { + + var dropDetected = this.dropGestureProcess(deltaTime); + if (this.state == STATE_NEAR_GRABBING && this.triggerSmoothedReleased()) { - this.setState(STATE_OFF); + this.setState(STATE_OFF, "trigger released"); this.callEntityMethodOnGrabbed("releaseGrab"); return; } - if (this.state == STATE_HOLD && this.secondaryReleased()) { - this.setState(STATE_OFF); + + if (this.state == STATE_HOLD && dropDetected && this.triggerSmoothedGrab()) { + this.setState(STATE_OFF, "drop detected"); this.callEntityMethodOnGrabbed("releaseEquip"); return; } @@ -1541,7 +1722,7 @@ function MyController(hand) { var props = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "parentID", "position", "rotation"]); if (!props.position) { // server may have reset, taking our equipped entity with it. move back to "off" stte - this.setState(STATE_OFF); + this.setState(STATE_OFF, "entity has no position property"); this.callEntityMethodOnGrabbed("releaseGrab"); return; } @@ -1561,7 +1742,7 @@ function MyController(hand) { // for whatever reason, the held/equipped entity has been pulled away. ungrab or unequip. print("handControllerGrab -- autoreleasing held or equipped item because it is far from hand." + props.parentID + " " + vec3toStr(props.position)); - this.setState(STATE_OFF); + this.setState(STATE_OFF, "held object too far away"); if (this.state == STATE_NEAR_GRABBING) { this.callEntityMethodOnGrabbed("releaseGrab"); } else { // this.state == STATE_HOLD @@ -1581,17 +1762,17 @@ function MyController(hand) { var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; - var deltaTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds + var deltaObjectTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds - if (deltaTime > 0.0) { + if (deltaObjectTime > 0.0) { var worldDeltaPosition = Vec3.subtract(props.position, this.currentObjectPosition); var previousEulers = Quat.safeEulerAngles(this.currentObjectRotation); var newEulers = Quat.safeEulerAngles(props.rotation); var worldDeltaRotation = Vec3.subtract(newEulers, previousEulers); - this.currentVelocity = Vec3.multiply(worldDeltaPosition, 1.0 / deltaTime); - this.currentAngularVelocity = Vec3.multiply(worldDeltaRotation, Math.PI / (deltaTime * 180.0)); + this.currentVelocity = Vec3.multiply(worldDeltaPosition, 1.0 / deltaObjectTime); + this.currentAngularVelocity = Vec3.multiply(worldDeltaRotation, Math.PI / (deltaObjectTime * 180.0)); this.currentObjectPosition = props.position; this.currentObjectRotation = props.rotation; @@ -1638,8 +1819,8 @@ function MyController(hand) { }; this.nearTrigger = function() { - if (this.triggerSmoothedReleased() && this.secondaryReleased()) { - this.setState(STATE_OFF); + if (this.triggerSmoothedReleased()) { + this.setState(STATE_OFF, "trigger released"); this.callEntityMethodOnGrabbed("stopNearTrigger"); return; } @@ -1647,8 +1828,8 @@ function MyController(hand) { }; this.farTrigger = function() { - if (this.triggerSmoothedReleased() && this.secondaryReleased()) { - this.setState(STATE_OFF); + if (this.triggerSmoothedReleased()) { + this.setState(STATE_OFF, "trigger released"); this.callEntityMethodOnGrabbed("stopFarTrigger"); return; } @@ -1665,7 +1846,7 @@ function MyController(hand) { if (intersection.accurate) { this.lastPickTime = now; if (intersection.entityID != this.grabbedEntity) { - this.setState(STATE_OFF); + this.setState(STATE_OFF, "laser moved off of entity"); this.callEntityMethodOnGrabbed("stopFarTrigger"); return; } @@ -1680,6 +1861,10 @@ function MyController(hand) { }; this.offEnter = function() { + this.release(); + }; + + this.release = function() { this.turnLightsOff(); this.turnOffVisualizations(); @@ -1710,6 +1895,10 @@ function MyController(hand) { })); this.grabbedEntity = null; + + if (this.triggerSmoothedGrab()) { + this.waitForTriggerRelease = true; + } }; this.cleanup = function() { @@ -1918,12 +2107,12 @@ Controller.enableMapping(MAPPING_NAME); //the section below allows the grab script to listen for messages that disable either one or both hands. useful for two handed items var handToDisable = 'none'; -function update() { +function update(deltaTime) { if (handToDisable !== LEFT_HAND && handToDisable !== 'both') { - leftController.update(); + leftController.update(deltaTime); } if (handToDisable !== RIGHT_HAND && handToDisable !== 'both') { - rightController.update(); + rightController.update(deltaTime); } } @@ -1950,7 +2139,7 @@ var handleHandMessages = function(channel, message, sender) { data = JSON.parse(message); var selectedController = (data.hand === 'left') ? leftController : rightController; selectedController.release(); - selectedController.setState(STATE_HOLD); + selectedController.setState(STATE_HOLD, "Hifi-Hand-Grab msg received"); selectedController.grabbedEntity = data.entityID; } catch (e) { From 14efd5dc127480f41007d3359de3c1251035c818 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 20 Jun 2016 10:44:09 -0700 Subject: [PATCH 072/171] Disable WANT_DEBUG_STATE --- scripts/system/controllers/handControllerGrab.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 8c5d48667d..8727bbdd2d 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -18,7 +18,7 @@ Script.include("/~/system/libraries/utils.js"); // add lines where the hand ray picking is happening // var WANT_DEBUG = false; -var WANT_DEBUG_STATE = true; +var WANT_DEBUG_STATE = false; var WANT_DEBUG_SEARCH_NAME = null; // @@ -1711,7 +1711,7 @@ function MyController(hand) { return; } - if (this.state == STATE_HOLD && dropDetected && this.triggerSmoothedGrab()) { + if (this.state == STATE_HOLD && dropDetected && this.triggerSmoothedGrab()) { this.setState(STATE_OFF, "drop detected"); this.callEntityMethodOnGrabbed("releaseEquip"); return; From 7ccbc9e6eb6c3ea8efee260e4bd4d7dd56afa038 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 20 Jun 2016 11:22:56 -0700 Subject: [PATCH 073/171] Primary thumb press can be used to drop an equipped object --- scripts/system/controllers/handControllerGrab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 8727bbdd2d..1471decbfa 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -1711,7 +1711,7 @@ function MyController(hand) { return; } - if (this.state == STATE_HOLD && dropDetected && this.triggerSmoothedGrab()) { + if ((this.state == STATE_HOLD && dropDetected && this.triggerSmoothedGrab()) || this.thumbPressed()) { this.setState(STATE_OFF, "drop detected"); this.callEntityMethodOnGrabbed("releaseEquip"); return; From 35276c38932a153785f6888ec72582834e75fdce Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 20 Jun 2016 11:30:40 -0700 Subject: [PATCH 074/171] Use Array.prototype.filter instead of my hand rolled filter function --- scripts/system/controllers/handControllerGrab.js | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 1471decbfa..bbf9dcb793 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -253,17 +253,6 @@ function restore2DMode() { } } -function filter(array, predicate) { - var i, l = array.length; - var result = []; - for (i = 0; i < l; i++) { - if (predicate(array[i])) { - result.push(array[i]); - } - } - return result; -} - // constructor function EntityPropertiesCache() { this.cache = {}; @@ -1164,7 +1153,7 @@ function MyController(hand) { this.entityPropertyCache.findEntities(handPosition, GRAB_RADIUS); var candidateEntities = this.entityPropertyCache.getEntities(); - var equippableEntities = filter(candidateEntities, function (entity) { + var equippableEntities = candidateEntities.filter(function (entity) { return _this.entityIsEquippable(entity, handPosition); }); @@ -1193,7 +1182,7 @@ function MyController(hand) { this.entityPropertyCache.addEntity(rayPickInfo.entityID); } - var grabbableEntities = filter(candidateEntities, function (entity) { + var grabbableEntities = candidateEntities.filter(function (entity) { return _this.entityIsNearGrabbable(entity, handPosition); }); From a14bbe4e682e18c38d5c9a8fd21420d5bf2f99b7 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 20 Jun 2016 11:40:59 -0700 Subject: [PATCH 075/171] Remove /* style multi-line comments --- .../system/controllers/handControllerGrab.js | 66 +++++++++---------- 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index bbf9dcb793..76e13ce992 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -916,11 +916,11 @@ function MyController(hand) { this.equipHotspotOverlays = []; }; - /** - * Performs ray pick test from the hand controller into the world - * @param {number} which hand to use, RIGHT_HAND or LEFT_HAND - * @returns {object} returns object with two keys entityID and distance - */ + /// + // Performs ray pick test from the hand controller into the world + // @param {number} which hand to use, RIGHT_HAND or LEFT_HAND + // @returns {object} returns object with two keys entityID and distance + // this.calcRayPickInfo = function(hand) { var pose = Controller.getPoseValue((hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand); @@ -1365,14 +1365,12 @@ function MyController(hand) { var handMoved = Vec3.multiply(Vec3.subtract(controllerPositionVSAvatar, this.previousControllerPositionVSAvatar), radius); - // double delta controller rotation - /* - var DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR = 2.0; // object rotates this much more than hand did - var handChange = Quat.multiply(Quat.slerp(this.previousControllerRotation, - controllerRotation, - DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR), - Quat.inverse(this.previousControllerRotation)); - */ + /// double delta controller rotation + // var DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR = 2.0; // object rotates this much more than hand did + // var handChange = Quat.multiply(Quat.slerp(this.previousControllerRotation, + // controllerRotation, + // DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR), + // Quat.inverse(this.previousControllerRotation)); // update the currentObject position and rotation. this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, handMoved); @@ -1426,28 +1424,26 @@ function MyController(hand) { } } - /* - var defaultConstraintData = { - axisStart: false, - axisEnd: false - } - - var constraintData = getEntityCustomData('lightModifierKey', this.grabbedEntity, defaultConstraintData); - var clampedVector; - var targetPosition; - if (constraintData.axisStart !== false) { - clampedVector = this.projectVectorAlongAxis(this.currentObjectPosition, - constraintData.axisStart, - constraintData.axisEnd); - targetPosition = clampedVector; - } else { - targetPosition = { - x: this.currentObjectPosition.x, - y: this.currentObjectPosition.y, - z: this.currentObjectPosition.z - } - } - */ + // var defaultConstraintData = { + // axisStart: false, + // axisEnd: false + // } + // + // var constraintData = getEntityCustomData('lightModifierKey', this.grabbedEntity, defaultConstraintData); + // var clampedVector; + // var targetPosition; + // if (constraintData.axisStart !== false) { + // clampedVector = this.projectVectorAlongAxis(this.currentObjectPosition, + // constraintData.axisStart, + // constraintData.axisEnd); + // targetPosition = clampedVector; + // } else { + // targetPosition = { + // x: this.currentObjectPosition.x, + // y: this.currentObjectPosition.y, + // z: this.currentObjectPosition.z + // } + // } var handPosition = this.getHandPosition(); From 359483d9ba617637b01ad7f18e168d0476f8a5e2 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 20 Jun 2016 11:43:42 -0700 Subject: [PATCH 076/171] coding standard fix --- scripts/system/controllers/handControllerGrab.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 76e13ce992..8cef751e07 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -935,9 +935,11 @@ function MyController(hand) { length: PICK_MAX_DISTANCE }; - var result = { entityID: null, - searchRay: pickRay, - distance: PICK_MAX_DISTANCE }; + var result = { + entityID: null, + searchRay: pickRay, + distance: PICK_MAX_DISTANCE + }; var now = Date.now(); if (now - this.lastPickTime < MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { From cd1780efefc1f0ad1a8cbad53e98f08631ecfbb0 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Mon, 20 Jun 2016 13:21:06 -0700 Subject: [PATCH 077/171] brush clearing --- .../controllers/handControllerPointer.js | 115 ++++-------------- 1 file changed, 27 insertions(+), 88 deletions(-) diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index 0f1e23b45c..0fe199098d 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -14,15 +14,11 @@ // // Control the "mouse" using hand controller. (HMD and desktop.) -// For now: -// Hydra thumb button 3 is left-mouse, button 4 is right-mouse. -// A click in the center of the vive thumb pad is left mouse. Vive menu button is context menu (right mouse). // First-person only. // Starts right handed, but switches to whichever is free: Whichever hand was NOT most recently squeezed. // (For now, the thumb buttons on both controllers are always on.) -// When over a HUD element, the reticle is shown where the active hand controller beam intersects the HUD. -// Otherwise, the active hand controller shows a red ball where a click will act. - +// When partially squeezing over a HUD element, a laser or the reticle is shown where the active hand +// controller beam intersects the HUD. // UTILITIES ------------- @@ -102,7 +98,7 @@ function Trigger(label) { } that.state = state; }; - // Answer a controller source function (answering either 0.0 or 1.0), with hysteresis. + // Answer a controller source function (answering either 0.0 or 1.0). that.partial = function () { return that.state ? 1.0 : 0.0; // either 'partial' or 'full' }; @@ -369,24 +365,9 @@ clickMapping.enable(); // VISUAL AID ----------- // Same properties as handControllerGrab search sphere -var BALL_SIZE = 0.011; -var BALL_ALPHA = 0.5; -var LASER_SEARCH_COLOR_XYZW = {x: 10 / 255, y: 10 / 255, z: 255 / 255, w: BALL_ALPHA}; -var LASER_TRIGGER_COLOR_XYZW = {x: 250 / 255, y: 10 / 255, z: 10 / 255, w: BALL_ALPHA}; -var fakeProjectionBall = Overlays.addOverlay("sphere", { - size: 5 * BALL_SIZE, - color: {red: 255, green: 10, blue: 10}, - ignoreRayIntersection: true, - alpha: BALL_ALPHA, - visible: false, - solid: true, - drawInFront: true // Even when burried inside of something, show it. -}); -var overlays = [fakeProjectionBall]; // If we want to try showing multiple balls and lasers. -Script.scriptEnding.connect(function () { - overlays.forEach(Overlays.deleteOverlay); -}); -var visualizationIsShowing = false; // Not whether it desired, but simply whether it is. Just an optimziation. +var LASER_ALPHA = 0.5; +var LASER_SEARCH_COLOR_XYZW = {x: 10 / 255, y: 10 / 255, z: 255 / 255, w: LASER_ALPHA}; +var LASER_TRIGGER_COLOR_XYZW = {x: 250 / 255, y: 10 / 255, z: 10 / 255, w: LASER_ALPHA}; var SYSTEM_LASER_DIRECTION = {x: 0, y: 0, z: -1}; var systemLaserOn = false; function clearSystemLaser() { @@ -401,81 +382,30 @@ function setColoredLaser() { // answer trigger state if lasers supported, else f return HMD.setHandLasers(activeHudLaser, true, color, SYSTEM_LASER_DIRECTION) && activeTrigger.state; } -function turnOffVisualization(optionalEnableClicks) { // because we're showing cursor on HUD - if (!optionalEnableClicks) { - expireMouseCursor(); - clearSystemLaser(); - } else if (activeTrigger.state && (!systemLaserOn || (systemLaserOn !== activeTrigger.state))) { // last=>wrong color - // If the active plugin doesn't implement hand lasers, show the mouse reticle instead. - systemLaserOn = setColoredLaser(); - Reticle.visible = !systemLaserOn; - } else if ((systemLaserOn || Reticle.visible) && !activeTrigger.state) { - clearSystemLaser(); - Reticle.visible = false; - } - - if (!visualizationIsShowing) { - return; - } - visualizationIsShowing = false; - overlays.forEach(function (overlay) { - Overlays.editOverlay(overlay, {visible: false}); - }); -} -var MAX_RAY_SCALE = 32000; // Anything large. It's a scale, not a distance. -function updateVisualization(controllerPosition, controllerDirection, hudPosition3d, hudPosition2d) { - ignore(controllerPosition, controllerDirection, hudPosition2d); - clearSystemLaser(); - // Show an indication of where the cursor will appear when crossing a HUD element, - // and where in-world clicking will occur. - // - // There are a number of ways we could do this, but for now, it's a blue sphere that rolls along - // the HUD surface, and a red sphere that rolls along the 3d objects that will receive the click. - // We'll leave it to other scripts (like handControllerGrab) to show a search beam when desired. - - function intersection3d(position, direction) { - // Answer in-world intersection (entity or 3d overlay), or way-out point - var pickRay = {origin: position, direction: direction}; - var result = findRayIntersection(pickRay); - return result.intersects ? result.intersection : Vec3.sum(position, Vec3.multiply(MAX_RAY_SCALE, direction)); - } - - visualizationIsShowing = true; - // We'd rather in-world interactions be done at the termination of the hand beam - // -- intersection3d(controllerPosition, controllerDirection). Maybe have handControllerGrab - // direclty manipulate both entity and 3d overlay objects. - // For now, though, we present a false projection of the cursor onto whatever is below it. This is - // different from the hand beam termination because the false projection is from the camera, while - // the hand beam termination is from the hand. - /* // FIXME: We can tighten this up later, once we know what will and won't be included. - var eye = Camera.getPosition(); - var falseProjection = intersection3d(eye, Vec3.subtract(hudPosition3d, eye)); - Overlays.editOverlay(fakeProjectionBall, {visible: true, position: falseProjection}); - */ - Reticle.visible = false; - - return visualizationIsShowing; // In case we change caller to act conditionally. -} // MAIN OPERATIONS ----------- // function update() { var now = Date.now(); + function off() { + expireMouseCursor(); + clearSystemLaser(); + } if (!handControllerLockOut.expired(now)) { - return turnOffVisualization(); // Let them use mouse it in peace. + return off(); // Let them use mouse it in peace. } if (!Menu.isOptionChecked("First Person")) { - return turnOffVisualization(); // What to do? menus can be behind hand! + return off(); // What to do? menus can be behind hand! } if (!Window.hasFocus() || !Reticle.allowMouseCapture) { - return turnOffVisualization(); // Don't mess with other apps or paused mouse activity + return off(); // Don't mess with other apps or paused mouse activity } leftTrigger.update(); rightTrigger.update(); var controllerPose = Controller.getPoseValue(activeHand); // Valid if any plugged-in hand controller is "on". (uncradled Hydra, green-lighted Vive...) if (!controllerPose.valid) { - return turnOffVisualization(); // Controller is cradled. + return off(); // Controller is cradled. } var controllerPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, controllerPose.translation), MyAvatar.position); @@ -487,7 +417,7 @@ function update() { if (Menu.isOptionChecked("Overlays")) { // With our hud resetting strategy, hudPoint3d should be valid here print('Controller is parallel to HUD'); // so let us know that our assumptions are wrong. } - return turnOffVisualization(); + return off(); } var hudPoint2d = overlayFromWorldPoint(hudPoint3d); @@ -499,13 +429,22 @@ function update() { if (HMD.active) { Reticle.depth = hudReticleDistance(); } - return turnOffVisualization(true); + if (activeTrigger.state && (!systemLaserOn || (systemLaserOn !== activeTrigger.state))) { // last=>wrong color + // If the active plugin doesn't implement hand lasers, show the mouse reticle instead. + systemLaserOn = setColoredLaser(); + Reticle.visible = !systemLaserOn; + } else if ((systemLaserOn || Reticle.visible) && !activeTrigger.state) { + clearSystemLaser(); + Reticle.visible = false; + } + return; } // We are not pointing at a HUD element (but it could be a 3d overlay). if (!activeTrigger.state) { - return turnOffVisualization(); // No trigger (with hysteresis). + return off(); // No trigger } - updateVisualization(controllerPosition, controllerDirection, hudPoint3d, hudPoint2d); + clearSystemLaser(); + Reticle.visible = false; } var UPDATE_INTERVAL = 50; // milliseconds. Script.update is too frequent. From 8792812884814237bff55fcded366d88bc5362fa Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 20 Jun 2016 13:26:41 -0700 Subject: [PATCH 078/171] Change raw for loop to Array.prototype.forEach instead --- scripts/system/controllers/handControllerGrab.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 8cef751e07..4143cdd403 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -47,7 +47,6 @@ var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to thei var DISTANCE_HOLDING_UNITY_MASS = 1200; // The mass at which the distance holding action timeframe is unmodified var DISTANCE_HOLDING_UNITY_DISTANCE = 6; // The distance at which the distance holding action timeframe is unmodified var MOVE_WITH_HEAD = true; // experimental head-control of distantly held objects -var FAR_TO_NEAR_GRAB_PADDING_FACTOR = 1.2; var NO_INTERSECT_COLOR = { red: 10, @@ -262,10 +261,10 @@ EntityPropertiesCache.prototype.clear = function() { }; EntityPropertiesCache.prototype.findEntities = function(position, radius) { var entities = Entities.findEntities(position, radius); - var i, l = entities.length; - for (i = 0; i < l; i++) { - this.addEntity(entities[i]); - } + var _this = this; + entities.forEach(function (x) { + _this.addEntity(x); + }); }; EntityPropertiesCache.prototype.addEntity = function(entityID) { var props = Entities.getEntityProperties(entityID, GRABBABLE_PROPERTIES); From 408f65100be61ec3eae0e651b61a1217f778b12f Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 20 Jun 2016 13:34:05 -0700 Subject: [PATCH 079/171] Bug fix for dropping near-grabbed entities via thump press. --- scripts/system/controllers/handControllerGrab.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 4143cdd403..4300c32171 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -1697,10 +1697,18 @@ function MyController(hand) { return; } - if ((this.state == STATE_HOLD && dropDetected && this.triggerSmoothedGrab()) || this.thumbPressed()) { - this.setState(STATE_OFF, "drop detected"); - this.callEntityMethodOnGrabbed("releaseEquip"); - return; + if (this.state == STATE_HOLD) { + if (dropDetected && this.triggerSmoothedGrab()) { + this.setState(STATE_OFF, "drop gesture detected"); + this.callEntityMethodOnGrabbed("releaseEquip"); + return; + } + + if (this.thumbPressed()) { + this.setState(STATE_OFF, "drop via thumb press"); + this.callEntityMethodOnGrabbed("releaseEquip"); + return; + } } this.heartBeat(this.grabbedEntity); From e5159ad213a437e63feb509796f3961ce9bd8399 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 20 Jun 2016 14:50:15 -0700 Subject: [PATCH 080/171] Added "Developer > Hands > Drop Without Shake" menu option --- .../system/controllers/handControllerGrab.js | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 4300c32171..c85e113bc4 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -37,6 +37,7 @@ var THUMB_ON_VALUE = 0.5; var HAND_HEAD_MIX_RATIO = 0.0; // 0 = only use hands for search/move. 1 = only use head for search/move. var PICK_WITH_HAND_RAY = true; +var DROP_WITHOUT_SHAKE = false; // // distant manipulation @@ -1589,7 +1590,7 @@ function MyController(hand) { print("handMove = " + this.fastHandMoveDetected + ", handIsUpsideDown = " + handIsUpsideDown); } - return this.fastHandMoveDetected && handIsUpsideDown; + return (DROP_WITHOUT_SHAKE || this.fastHandMoveDetected) && handIsUpsideDown; }; this.nearGrabbingEnter = function() { @@ -2170,6 +2171,24 @@ function cleanup() { leftController.cleanup(); Controller.disableMapping(MAPPING_NAME); Reticle.setVisible(true); + Menu.removeMenuItem("Developer > Hands", "Drop Without Shake"); } + Script.scriptEnding.connect(cleanup); Script.update.connect(update); + +Menu.addMenuItem({ + menuName: "Developer > Hands", + menuItemName: "Drop Without Shake", + isCheckable: true, + isChecked: DROP_WITHOUT_SHAKE +}); + +function handleMenuItemEvent(menuItem) { + if (menuItem === "Drop Without Shake") { + DROP_WITHOUT_SHAKE = Menu.isOptionChecked("Drop Without Shake"); + } +} + +Menu.menuItemEvent.connect(handleMenuItemEvent); + From 37e9f666390c6f92dcdd0d731d2404b5fa2b4781 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 20 Jun 2016 15:41:21 -0700 Subject: [PATCH 081/171] Additional toolbar work --- interface/resources/qml/hifi/Desktop.qml | 9 +- .../resources/qml/hifi/toolbars/Toolbar.qml | 128 +++++++++++------- .../qml/hifi/toolbars/ToolbarButton.qml | 34 ++++- interface/resources/qml/windows/Frame.qml | 2 +- interface/resources/qml/windows/ToolFrame.qml | 63 +++++++-- .../scripting/ToolbarScriptingInterface.cpp | 9 -- scripts/developer/tests/toolbarTest.js | 2 +- tests/ui/qml/main.qml | 2 +- 8 files changed, 174 insertions(+), 75 deletions(-) diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index c14d55cb00..169542c0f0 100644 --- a/interface/resources/qml/hifi/Desktop.qml +++ b/interface/resources/qml/hifi/Desktop.qml @@ -3,12 +3,12 @@ import QtQuick.Controls 1.4 import QtWebEngine 1.1; import Qt.labs.settings 1.0 -import "../desktop" +import "../desktop" as OriginalDesktop import ".." import "." import "./toolbars" -Desktop { +OriginalDesktop.Desktop { id: desktop MouseArea { @@ -54,12 +54,13 @@ Desktop { WebEngine.settings.localContentCanAccessRemoteUrls = true; var sysToolbar = desktop.getToolbar("com.highfidelity.interface.toolbar.system"); - //toolbars[sysToolbar.objectName] = sysToolbar var toggleHudButton = sysToolbar.addButton({ + objectName: "hudToggle", imageURL: "../../../icons/hud-01.svg", visible: true, - + pinned: true, }); + toggleHudButton.yOffset = Qt.binding(function(){ return desktop.pinned ? 50 : 0 }); diff --git a/interface/resources/qml/hifi/toolbars/Toolbar.qml b/interface/resources/qml/hifi/toolbars/Toolbar.qml index 35c816569b..75c06e4199 100644 --- a/interface/resources/qml/hifi/toolbars/Toolbar.qml +++ b/interface/resources/qml/hifi/toolbars/Toolbar.qml @@ -7,14 +7,16 @@ import "." Window { id: window - frame: ToolFrame { } + frame: ToolFrame { + horizontalSpacers: horizontal + verticalSpacers: !horizontal + } hideBackground: true resizable: false destroyOnCloseButton: false destroyOnHidden: false closable: false shown: true - pinned: true width: content.width height: content.height visible: true @@ -32,54 +34,77 @@ Window { } onHorizontalChanged: { - var oldParent = horizontal ? column : row; var newParent = horizontal ? row : column; - var move = []; - - var i; - for (i in oldParent.children) { - var child = oldParent.children[i]; - if (child.spacer) { - continue; - } - move.push(oldParent.children[i]); - } - for (i in move) { - move[i].parent = newParent; + for (var i in buttons) { + var child = buttons[i]; + child.parent = newParent; if (horizontal) { - move[i].y = 0 + child.y = 0 } else { - move[i].x = 0 + child.x = 0 } } - fixSpacers(); } Item { id: content implicitHeight: horizontal ? row.height : column.height implicitWidth: horizontal ? row.width : column.width + Row { id: row spacing: 6 - visible: window.horizontal - Rectangle{ readonly property bool spacer: true; id: rowSpacer1; width: 1; height: row.height } - Rectangle{ readonly property bool spacer: true; id: rowSpacer2; width: 1; height: row.height } - Rectangle{ readonly property bool spacer: true; id: rowSpacer3; width: 1; height: row.height } - Rectangle{ readonly property bool spacer: true; id: rowSpacer4; width: 1; height: row.height } } Column { id: column spacing: 6 - visible: !window.horizontal - Rectangle{ readonly property bool spacer: true; id: colSpacer1; width: column.width; height: 1 } - Rectangle{ readonly property bool spacer: true; id: colSpacer2; width: column.width; height: 1 } - Rectangle{ readonly property bool spacer: true; id: colSpacer3; width: column.width; height: 1 } - Rectangle{ readonly property bool spacer: true; id: colSpacer4; width: column.width; height: 1 } } Component { id: toolbarButtonBuilder; ToolbarButton { } } + + Connections { + target: desktop + onPinnedChanged: { + if (!window.pinned) { + return; + } + var newPinned = desktop.pinned; + for (var i in buttons) { + var child = buttons[i]; + if (desktop.pinned) { + if (!child.pinned) { + child.visible = false; + } + } else { + child.visible = true; + } + } + } + } + } + + + function findButtonIndex(name) { + if (!name) { + return -1; + } + + for (var i in buttons) { + var child = buttons[i]; + if (child.objectName === name) { + return i; + } + } + return -1; + } + + function findButton(name) { + var index = findButtonIndex(name); + if (index < 0) { + return; + } + return buttons[index]; } function addButton(properties) { @@ -88,30 +113,39 @@ Window { // If a name is specified, then check if there's an existing button with that name // and return it if so. This will allow multiple clients to listen to a single button, // and allow scripts to be idempotent so they don't duplicate buttons if they're reloaded - if (properties.objectName) { - for (var i in buttons) { - var child = buttons[i]; - if (child.objectName === properties.objectName) { - return child; - } - } + var result = findButton(properties.objectName); + if (result) { + return result; } - properties.toolbar = this; - var result = toolbarButtonBuilder.createObject(container, properties); + properties.opacity = 0; + result = toolbarButtonBuilder.createObject(container, properties); buttons.push(result); - fixSpacers(); + result.opacity = 1; + updatePinned(); return result; } - function fixSpacers() { - colSpacer3.parent = null - colSpacer4.parent = null - rowSpacer3.parent = null - rowSpacer4.parent = null - colSpacer3.parent = column - colSpacer4.parent = column - rowSpacer3.parent = row - rowSpacer4.parent = row + function removeButton(name) { + var index = findButtonIndex(name); + if (index < -1) { + console.warn("Tried to remove non-existent button " + name); + return; + } + buttons[index].destroy(); + buttons.splice(index, 1); + updatePinned(); + } + + function updatePinned() { + var newPinned = false; + for (var i in buttons) { + var child = buttons[i]; + if (child.pinned) { + newPinned = true; + break; + } + } + pinned = newPinned; } } diff --git a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml index a8514689e8..a3be4533d2 100644 --- a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml +++ b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml @@ -7,11 +7,40 @@ Item { property alias alpha: button.opacity property var subImage; property int yOffset: 0 + property int buttonState: 0 property var toolbar; property real size: 50 // toolbar ? toolbar.buttonSize : 50 width: size; height: size + property bool pinned: false clip: true + Behavior on opacity { + NumberAnimation { + duration: 150 + easing.type: Easing.InOutCubic + } + } + + property alias fadeTargetProperty: button.opacity + + onFadeTargetPropertyChanged: { + visible = (fadeTargetProperty !== 0.0); + } + + onVisibleChanged: { + if ((!visible && fadeTargetProperty != 0.0) || (visible && fadeTargetProperty == 0.0)) { + var target = visible; + visible = !visible; + fadeTargetProperty = target ? 1.0 : 0.0; + return; + } + } + + + onButtonStateChanged: { + yOffset = size * buttonState + } + Component.onCompleted: { if (subImage) { if (subImage.y) { @@ -30,10 +59,7 @@ Item { MouseArea { anchors.fill: parent - onClicked: { - console.log("Clicked on button " + image.source + " named " + button.objectName) - button.clicked(); - } + onClicked: button.clicked(); } } diff --git a/interface/resources/qml/windows/Frame.qml b/interface/resources/qml/windows/Frame.qml index bc8ecc35ec..88d8c3ad41 100644 --- a/interface/resources/qml/windows/Frame.qml +++ b/interface/resources/qml/windows/Frame.qml @@ -59,7 +59,7 @@ Item { height: 1.66 * window.height x: (window.width - width) / 2 y: window.height / 2 - 0.375 * height - visible: gradientsSupported && window && window.focus && pane.visible + visible: gradientsSupported && window && window.focus && window.content.visible gradient: Gradient { // GradientStop position 0.5 is at full circumference of circle that fits inside the square. GradientStop { position: 0.0; color: "#ff000000" } // black, 100% opacity diff --git a/interface/resources/qml/windows/ToolFrame.qml b/interface/resources/qml/windows/ToolFrame.qml index ac1093092e..eff5fc0377 100644 --- a/interface/resources/qml/windows/ToolFrame.qml +++ b/interface/resources/qml/windows/ToolFrame.qml @@ -16,20 +16,67 @@ import "../styles-uit" Frame { HifiConstants { id: hifi } + property bool horizontalSpacers: false + property bool verticalSpacers: false Rectangle { // Dialog frame id: frameContent readonly property int frameMargin: 6 - readonly property int frameMarginLeft: frameMargin - readonly property int frameMarginRight: frameMargin - readonly property int frameMarginTop: frameMargin - readonly property int frameMarginBottom: frameMargin + readonly property int frameMarginLeft: frameMargin + (horizontalSpacers ? 12 : 0) + readonly property int frameMarginRight: frameMargin + (horizontalSpacers ? 12 : 0) + readonly property int frameMarginTop: frameMargin + (verticalSpacers ? 12 : 0) + readonly property int frameMarginBottom: frameMargin + (verticalSpacers ? 12 : 0) + + Rectangle { + visible: horizontalSpacers + anchors.left: parent.left + anchors.leftMargin: 6 + anchors.verticalCenter: parent.verticalCenter + width: 8 + height: window.height + color: "gray"; + radius: 4 + } + + Rectangle { + visible: horizontalSpacers + anchors.right: parent.right + anchors.rightMargin: 6 + anchors.verticalCenter: parent.verticalCenter + width: 8 + height: window.height + color: "gray"; + radius: 4 + } + + Rectangle { + visible: verticalSpacers + anchors.top: parent.top + anchors.topMargin: 6 + anchors.horizontalCenter: parent.horizontalCenter + height: 8 + width: window.width + color: "gray"; + radius: 4 + } + + Rectangle { + visible: verticalSpacers + anchors.bottom: parent.bottom + anchors.bottomMargin: 6 + anchors.horizontalCenter: parent.horizontalCenter + height: 8 + width: window.width + color: "gray"; + radius: 4 + } + anchors { - topMargin: -frameMargin - leftMargin: -frameMargin - rightMargin: -frameMargin - bottomMargin: -frameMargin + leftMargin: -frameMarginLeft + rightMargin: -frameMarginRight + topMargin: -frameMarginTop + bottomMargin: -frameMarginBottom } anchors.fill: parent color: hifi.colors.baseGrayHighlight40 diff --git a/interface/src/scripting/ToolbarScriptingInterface.cpp b/interface/src/scripting/ToolbarScriptingInterface.cpp index fd96b6a809..82332b3187 100644 --- a/interface/src/scripting/ToolbarScriptingInterface.cpp +++ b/interface/src/scripting/ToolbarScriptingInterface.cpp @@ -15,15 +15,6 @@ class QmlWrapper : public QObject { public: QmlWrapper(QObject* qmlObject, QObject* parent = nullptr) : QObject(parent), _qmlObject(qmlObject) { - - const QMetaObject *metaobject = qmlObject->metaObject(); - int count = metaobject->propertyCount(); - qDebug() << "Scanning properties for " << qmlObject; - for (int i = 0; i < count; ++i) { - QMetaProperty metaproperty = metaobject->property(i); - const char *name = metaproperty.name(); - qDebug() << "Property " << name; - } } Q_INVOKABLE void writeProperty(QString propertyName, QVariant propertyValue) { diff --git a/scripts/developer/tests/toolbarTest.js b/scripts/developer/tests/toolbarTest.js index 20f349929f..e21fbd8e19 100644 --- a/scripts/developer/tests/toolbarTest.js +++ b/scripts/developer/tests/toolbarTest.js @@ -13,7 +13,7 @@ var toolBar = (function() { newZoneButton, newParticleButton - var toolIconUrl = Script.resolvePath("assets/images/tools/"); + var toolIconUrl = Script.resolvePath("../../system/assets/images/tools/"); function initialize() { print("Toolbars: " + Toolbars); diff --git a/tests/ui/qml/main.qml b/tests/ui/qml/main.qml index 19f6a55bfd..47d0f6d601 100644 --- a/tests/ui/qml/main.qml +++ b/tests/ui/qml/main.qml @@ -57,7 +57,7 @@ ApplicationWindow { "particle-01.svg", ] property int iconIndex: 0 - readonly property string toolIconUrl: "file:///C:/Users/bdavi/git/hifi/scripts/system/assets/images/tools/" + readonly property string toolIconUrl: "../../../../../scripts/system/assets/images/tools/" text: "Create Button" onClicked: { var name = icons[iconIndex]; From 8817bbea0921af134dd522eb21f593ed5ba058d6 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 20 Jun 2016 15:53:33 -0700 Subject: [PATCH 082/171] reduce log spam --- scripts/system/controllers/handControllerGrab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index c85e113bc4..4afbae451f 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -984,7 +984,7 @@ function MyController(hand) { var props = this.entityPropertyCache.getProps(entityID); var distance = Vec3.distance(props.position, handPosition); var grabProps = this.entityPropertyCache.getGrabProps(entityID); - var debug = true;//(WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); + var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); var refCount = ("refCount" in grabProps) ? grabProps.refCount : 0; if (refCount > 0) { From a52fab00a9852b2e0f4560920820df8ecf09ce2d Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 20 Jun 2016 16:04:39 -0700 Subject: [PATCH 083/171] commit possible fix for testing --- interface/src/ui/ApplicationOverlay.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 2395f62468..41d249b635 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -166,13 +166,11 @@ void ApplicationOverlay::renderRearView(RenderArgs* renderArgs) { batch.setViewTransform(Transform()); float screenRatio = ((float)qApp->getDevicePixelRatio()); - float renderRatio = ((float)screenRatio * qApp->getRenderResolutionScale()); + float renderRatio = ((float)qApp->getRenderResolutionScale()); auto viewport = qApp->getMirrorViewRect(); - glm::vec2 bottomLeft(viewport.left(), viewport.top() + viewport.height()); - glm::vec2 topRight(viewport.left() + viewport.width(), viewport.top()); - bottomLeft *= screenRatio; - topRight *= screenRatio; + glm::vec2 bottomLeft(viewport.left(), viewport.top() + screenRatio * viewport.height()); + glm::vec2 topRight(viewport.left() + screenRatio * viewport.width(), viewport.top()); glm::vec2 texCoordMinCorner(0.0f, 0.0f); glm::vec2 texCoordMaxCorner(viewport.width() * renderRatio / float(selfieTexture->getWidth()), viewport.height() * renderRatio / float(selfieTexture->getHeight())); From bfb697bc7789f5cd6dbb073be47f846fc39a66da Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Mon, 20 Jun 2016 17:04:23 -0700 Subject: [PATCH 084/171] No hysteresis. --- scripts/system/controllers/handControllerPointer.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index 0fe199098d..c94325d3c0 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -85,15 +85,10 @@ function Trigger(label) { state = 'full'; } else if (that.triggerSmoothedReleased()) { state = null; - // These depend on previous state: - // null -> squeezed ==> partial - // full -> !squeezed ==> partial - // Otherwise no change. } else if (that.triggerSmoothedSqueezed()) { - if (!state) { - state = 'partial'; - } - } else if (state === 'full') { + // Another way to do this would be to have hysteresis in this branch, but that seems to make things harder to use. + // In particular, the vive has a nice detent as you release off of full, and we want that to be a transition from + // full to partial. state = 'partial'; } that.state = state; From 0bc4beeffd4e85d353ece1219cdd3697a2b14ce6 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 20 Jun 2016 17:09:30 -0700 Subject: [PATCH 085/171] Fix initial toolbar positioning --- interface/resources/qml/desktop/Desktop.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index e269a63cde..58d1cba1b8 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -65,7 +65,7 @@ FocusScope { var oldChildren = expectedChildren; var newChildren = d.getRepositionChildren(); - if (oldRecommendedRect != Qt.rect(0,0,0,0) + if (oldRecommendedRect != Qt.rect(0,0,0,0) && oldRecommendedRect != Qt.rect(0,0,1,1) && (oldRecommendedRect != newRecommendedRect || oldChildren != newChildren) ) { From 2ed88bca6d569db24f49fe59a6afa9a280b99f60 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 20 Jun 2016 17:24:36 -0700 Subject: [PATCH 086/171] Bug fix for bow and arrow. * before this change the releaseEquip message was not getting sent to the bow, which it would use to re-enable the grab script via the 'Hifi-Hand-Disabler' msg. --- .../system/controllers/handControllerGrab.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 4afbae451f..f1ecc15392 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -1334,8 +1334,8 @@ function MyController(hand) { this.distanceHolding = function() { if (this.triggerSmoothedReleased()) { - this.setState(STATE_OFF, "trigger released"); this.callEntityMethodOnGrabbed("releaseGrab"); + this.setState(STATE_OFF, "trigger released"); return; } @@ -1693,21 +1693,21 @@ function MyController(hand) { var dropDetected = this.dropGestureProcess(deltaTime); if (this.state == STATE_NEAR_GRABBING && this.triggerSmoothedReleased()) { - this.setState(STATE_OFF, "trigger released"); this.callEntityMethodOnGrabbed("releaseGrab"); + this.setState(STATE_OFF, "trigger released"); return; } if (this.state == STATE_HOLD) { if (dropDetected && this.triggerSmoothedGrab()) { - this.setState(STATE_OFF, "drop gesture detected"); this.callEntityMethodOnGrabbed("releaseEquip"); + this.setState(STATE_OFF, "drop gesture detected"); return; } if (this.thumbPressed()) { - this.setState(STATE_OFF, "drop via thumb press"); this.callEntityMethodOnGrabbed("releaseEquip"); + this.setState(STATE_OFF, "drop via thumb press"); return; } } @@ -1717,8 +1717,8 @@ function MyController(hand) { var props = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "parentID", "position", "rotation"]); if (!props.position) { // server may have reset, taking our equipped entity with it. move back to "off" stte - this.setState(STATE_OFF, "entity has no position property"); this.callEntityMethodOnGrabbed("releaseGrab"); + this.setState(STATE_OFF, "entity has no position property"); return; } @@ -1737,12 +1737,13 @@ function MyController(hand) { // for whatever reason, the held/equipped entity has been pulled away. ungrab or unequip. print("handControllerGrab -- autoreleasing held or equipped item because it is far from hand." + props.parentID + " " + vec3toStr(props.position)); - this.setState(STATE_OFF, "held object too far away"); + if (this.state == STATE_NEAR_GRABBING) { this.callEntityMethodOnGrabbed("releaseGrab"); } else { // this.state == STATE_HOLD this.callEntityMethodOnGrabbed("releaseEquip"); } + this.setState(STATE_OFF, "held object too far away"); return; } } @@ -1815,8 +1816,8 @@ function MyController(hand) { this.nearTrigger = function() { if (this.triggerSmoothedReleased()) { - this.setState(STATE_OFF, "trigger released"); this.callEntityMethodOnGrabbed("stopNearTrigger"); + this.setState(STATE_OFF, "trigger released"); return; } this.callEntityMethodOnGrabbed("continueNearTrigger"); @@ -1824,8 +1825,8 @@ function MyController(hand) { this.farTrigger = function() { if (this.triggerSmoothedReleased()) { - this.setState(STATE_OFF, "trigger released"); this.callEntityMethodOnGrabbed("stopFarTrigger"); + this.setState(STATE_OFF, "trigger released"); return; } @@ -1841,8 +1842,8 @@ function MyController(hand) { if (intersection.accurate) { this.lastPickTime = now; if (intersection.entityID != this.grabbedEntity) { - this.setState(STATE_OFF, "laser moved off of entity"); this.callEntityMethodOnGrabbed("stopFarTrigger"); + this.setState(STATE_OFF, "laser moved off of entity"); return; } if (intersection.intersects) { From 57760bca7d1f3de23af220b2a10cdecee747d659 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 8 Jun 2016 16:35:38 -0700 Subject: [PATCH 087/171] sort ShapeInfo data members --- libraries/shared/src/ShapeInfo.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/shared/src/ShapeInfo.h b/libraries/shared/src/ShapeInfo.h index c853666d90..ad7a76c6a4 100644 --- a/libraries/shared/src/ShapeInfo.h +++ b/libraries/shared/src/ShapeInfo.h @@ -74,12 +74,12 @@ public: const DoubleHashKey& getHash() const; protected: - ShapeType _type = SHAPE_TYPE_NONE; + QUrl _url; // url for model of convex collision hulls + QVector> _points; // points for convex collision hulls glm::vec3 _halfExtents = glm::vec3(0.0f); glm::vec3 _offset = glm::vec3(0.0f); DoubleHashKey _doubleHashKey; - QVector> _points; // points for convex collision hulls - QUrl _url; // url for model of convex collision hulls + ShapeType _type = SHAPE_TYPE_NONE; }; #endif // hifi_ShapeInfo_h From d64729372a1cedc9cfc99464e5b15787bd063650 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 9 Jun 2016 16:14:49 -0700 Subject: [PATCH 088/171] ShapeInfo name changes --- .../src/RenderableModelEntityItem.cpp | 22 ++++---- .../src/RenderablePolyVoxEntityItem.cpp | 18 +++---- .../src/RenderablePolyVoxEntityItem.h | 2 +- .../physics/src/PhysicalEntitySimulation.cpp | 2 +- libraries/physics/src/ShapeFactory.cpp | 6 +-- libraries/shared/src/ShapeInfo.cpp | 50 +++++++++++-------- libraries/shared/src/ShapeInfo.h | 19 ++++--- tests/physics/src/ShapeManagerTests.cpp | 10 ++-- 8 files changed, 70 insertions(+), 59 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 7b3b3c3efe..07bcc05572 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -608,8 +608,8 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { const FBXGeometry& renderGeometry = _model->getFBXGeometry(); const FBXGeometry& collisionGeometry = _model->getCollisionFBXGeometry(); - QVector>& points = info.getPoints(); - points.clear(); + ShapeInfo::PointCollection& pointCollection = info.getPointCollection(); + pointCollection.clear(); uint32_t i = 0; // the way OBJ files get read, each section under a "g" line is its own meshPart. We only expect @@ -619,8 +619,8 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { foreach (const FBXMesh& mesh, collisionGeometry.meshes) { // each meshPart is a convex hull foreach (const FBXMeshPart &meshPart, mesh.parts) { - points.push_back(QVector()); - QVector& pointsInPart = points[i]; + pointCollection.push_back(QVector()); + ShapeInfo::PointList& pointsInPart = pointCollection[i]; // run through all the triangles and (uniquely) add each point to the hull uint32_t numIndices = (uint32_t)meshPart.triangleIndices.size(); @@ -664,7 +664,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { if (pointsInPart.size() == 0) { qCDebug(entitiesrenderer) << "Warning -- meshPart has no faces"; - points.pop_back(); + pointCollection.pop_back(); continue; } ++i; @@ -681,16 +681,16 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { // multiply each point by scale before handing the point-set off to the physics engine. // also determine the extents of the collision model. AABox box; - for (int i = 0; i < points.size(); i++) { - for (int j = 0; j < points[i].size(); j++) { + for (int i = 0; i < pointCollection.size(); i++) { + for (int j = 0; j < pointCollection[i].size(); j++) { // compensate for registration - points[i][j] += _model->getOffset(); + pointCollection[i][j] += _model->getOffset(); // scale so the collision points match the model points - points[i][j] *= scale; + pointCollection[i][j] *= scale; // this next subtraction is done so we can give info the offset, which will cause // the shape-key to change. - points[i][j] -= _model->getOffset(); - box += points[i][j]; + pointCollection[i][j] -= _model->getOffset(); + box += pointCollection[i][j]; } } diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index ad35a1a00c..7d5f227558 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -1198,7 +1198,7 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() { QtConcurrent::run([entity, voxelSurfaceStyle, voxelVolumeSize, mesh] { auto polyVoxEntity = std::static_pointer_cast(entity); - QVector> points; + QVector> pointCollection; AABox box; glm::mat4 vtoM = std::static_pointer_cast(entity)->voxelToLocalMatrix(); @@ -1241,9 +1241,9 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() { pointsInPart << p3Model; // add next convex hull QVector newMeshPoints; - points << newMeshPoints; + pointCollection << newMeshPoints; // add points to the new convex hull - points[i++] << pointsInPart; + pointCollection[i++] << pointsInPart; } } else { unsigned int i = 0; @@ -1299,19 +1299,19 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() { // add next convex hull QVector newMeshPoints; - points << newMeshPoints; + pointCollection << newMeshPoints; // add points to the new convex hull - points[i++] << pointsInPart; + pointCollection[i++] << pointsInPart; } }); } - polyVoxEntity->setCollisionPoints(points, box); + polyVoxEntity->setCollisionPoints(pointCollection, box); }); } -void RenderablePolyVoxEntityItem::setCollisionPoints(const QVector> points, AABox box) { +void RenderablePolyVoxEntityItem::setCollisionPoints(ShapeInfo::PointCollection pointCollection, AABox box) { // this catches the payload from computeShapeInfoWorker - if (points.isEmpty()) { + if (pointCollection.isEmpty()) { EntityItem::computeShapeInfo(_shapeInfo); return; } @@ -1325,7 +1325,7 @@ void RenderablePolyVoxEntityItem::setCollisionPoints(const QVector thunk); void setMesh(model::MeshPointer mesh); - void setCollisionPoints(const QVector> points, AABox box); + void setCollisionPoints(ShapeInfo::PointCollection points, AABox box); PolyVox::SimpleVolume* getVolData() { return _volData; } uint8_t getVoxelInternal(int x, int y, int z); diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index 6806b3a398..c5134fc027 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -217,7 +217,7 @@ void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& re } else if (entity->isReadyToComputeShape()) { ShapeInfo shapeInfo; entity->computeShapeInfo(shapeInfo); - int numPoints = shapeInfo.getMaxNumPoints(); + int numPoints = shapeInfo.getLargestSubshapePointCount(); if (numPoints > MAX_HULL_POINTS) { qWarning() << "convex hull with" << numPoints << "points for entity" << entity->getName() diff --git a/libraries/physics/src/ShapeFactory.cpp b/libraries/physics/src/ShapeFactory.cpp index d667d1075d..a23ef97007 100644 --- a/libraries/physics/src/ShapeFactory.cpp +++ b/libraries/physics/src/ShapeFactory.cpp @@ -179,15 +179,15 @@ btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) { } break; case SHAPE_TYPE_COMPOUND: { - const QVector>& points = info.getPoints(); + const ShapeInfo::PointCollection& pointCollection = info.getPointCollection(); uint32_t numSubShapes = info.getNumSubShapes(); if (numSubShapes == 1) { - shape = createConvexHull(info.getPoints()[0]); + shape = createConvexHull(pointCollection[0]); } else { auto compound = new btCompoundShape(); btTransform trans; trans.setIdentity(); - foreach (QVector hullPoints, points) { + foreach (const ShapeInfo::PointList& hullPoints, pointCollection) { btConvexHullShape* hull = createConvexHull(hullPoints); compound->addChildShape (trans, hull); } diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index 9c1e5c3816..cd0cb6fe8a 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -16,9 +16,13 @@ #include "NumericalConstants.h" // for MILLIMETERS_PER_METER void ShapeInfo::clear() { - _type = SHAPE_TYPE_NONE; - _halfExtents = _offset = glm::vec3(0.0f); + _url.clear(); + _pointCollection.clear(); + _triangleIndices.clear(); + _halfExtents = glm::vec3(0.0f); + _offset = glm::vec3(0.0f); _doubleHashKey.clear(); + _type = SHAPE_TYPE_NONE; } void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString url) { @@ -61,9 +65,9 @@ void ShapeInfo::setSphere(float radius) { _doubleHashKey.clear(); } -void ShapeInfo::setConvexHulls(const QVector>& points) { - _points = points; - _type = (_points.size() > 0) ? SHAPE_TYPE_COMPOUND : SHAPE_TYPE_NONE; +void ShapeInfo::setPointCollection(const ShapeInfo::PointCollection& pointCollection) { + _pointCollection = pointCollection; + _type = (_pointCollection.size() > 0) ? SHAPE_TYPE_COMPOUND : SHAPE_TYPE_NONE; _doubleHashKey.clear(); } @@ -83,15 +87,15 @@ uint32_t ShapeInfo::getNumSubShapes() const { if (_type == SHAPE_TYPE_NONE) { return 0; } else if (_type == SHAPE_TYPE_COMPOUND) { - return _points.size(); + return _pointCollection.size(); } return 1; } -int ShapeInfo::getMaxNumPoints() const { +int ShapeInfo::getLargestSubshapePointCount() const { int numPoints = 0; - for (int i = 0; i < _points.size(); ++i) { - int n = _points[i].size(); + for (int i = 0; i < _pointCollection.size(); ++i) { + int n = _pointCollection[i].size(); if (n > numPoints) { numPoints = n; } @@ -187,23 +191,23 @@ const DoubleHashKey& ShapeInfo::getHash() const { // TODO?: provide lookup table for hash/hash2 of _type rather than recompute? uint32_t primeIndex = 0; key.computeHash((uint32_t)_type, primeIndex++); - - // compute hash1 + + // compute hash1 uint32_t hash = key.getHash(); for (int j = 0; j < 3; ++j) { // NOTE: 0.49f is used to bump the float up almost half a millimeter // so the cast to int produces a round() effect rather than a floor() hash ^= DoubleHashKey::hashFunction( - (uint32_t)(_halfExtents[j] * MILLIMETERS_PER_METER + copysignf(1.0f, _halfExtents[j]) * 0.49f), + (uint32_t)(_halfExtents[j] * MILLIMETERS_PER_METER + copysignf(1.0f, _halfExtents[j]) * 0.49f), primeIndex++); if (useOffset) { hash ^= DoubleHashKey::hashFunction( - (uint32_t)(_offset[j] * MILLIMETERS_PER_METER + copysignf(1.0f, _offset[j]) * 0.49f), + (uint32_t)(_offset[j] * MILLIMETERS_PER_METER + copysignf(1.0f, _offset[j]) * 0.49f), primeIndex++); } } key.setHash(hash); - + // compute hash2 hash = key.getHash2(); for (int j = 0; j < 3; ++j) { @@ -224,14 +228,16 @@ const DoubleHashKey& ShapeInfo::getHash() const { } key.setHash2(hash); - QString url = _url.toString(); - if (!url.isEmpty()) { - // fold the urlHash into both parts - QByteArray baUrl = url.toLocal8Bit(); - const char *cUrl = baUrl.data(); - uint32_t urlHash = qChecksum(cUrl, baUrl.count()); - key.setHash(key.getHash() ^ urlHash); - key.setHash2(key.getHash2() ^ urlHash); + if (_type == SHAPE_TYPE_COMPOUND || _type == SHAPE_TYPE_MESH) { + QString url = _url.toString(); + if (!url.isEmpty()) { + // fold the urlHash into both parts + QByteArray baUrl = url.toLocal8Bit(); + const char *cUrl = baUrl.data(); + uint32_t urlHash = qChecksum(cUrl, baUrl.count()); + key.setHash(key.getHash() ^ urlHash); + key.setHash2(key.getHash2() ^ urlHash); + } } } return _doubleHashKey; diff --git a/libraries/shared/src/ShapeInfo.h b/libraries/shared/src/ShapeInfo.h index ad7a76c6a4..7f178bb53a 100644 --- a/libraries/shared/src/ShapeInfo.h +++ b/libraries/shared/src/ShapeInfo.h @@ -38,18 +38,23 @@ enum ShapeType { SHAPE_TYPE_CYLINDER_X, SHAPE_TYPE_CYLINDER_Y, SHAPE_TYPE_CYLINDER_Z, - SHAPE_TYPE_STATIC_MESH + SHAPE_TYPE_MESH }; class ShapeInfo { public: + + using PointList = QVector; + using PointCollection = QVector; + using TriangleIndices = QVector; + void clear(); void setParams(ShapeType type, const glm::vec3& halfExtents, QString url=""); void setBox(const glm::vec3& halfExtents); void setSphere(float radius); - void setConvexHulls(const QVector>& points); + void setPointCollection(const PointCollection& pointCollection); void setCapsuleY(float radius, float halfHeight); void setOffset(const glm::vec3& offset); @@ -58,12 +63,11 @@ public: const glm::vec3& getHalfExtents() const { return _halfExtents; } const glm::vec3& getOffset() const { return _offset; } - QVector>& getPoints() { return _points; } - const QVector>& getPoints() const { return _points; } + PointCollection& getPointCollection() { return _pointCollection; } + const PointCollection& getPointCollection() const { return _pointCollection; } uint32_t getNumSubShapes() const; - void appendToPoints (const QVector& newPoints) { _points << newPoints; } - int getMaxNumPoints() const; + int getLargestSubshapePointCount() const; float computeVolume() const; @@ -75,7 +79,8 @@ public: protected: QUrl _url; // url for model of convex collision hulls - QVector> _points; // points for convex collision hulls + PointCollection _pointCollection; + TriangleIndices _triangleIndices; glm::vec3 _halfExtents = glm::vec3(0.0f); glm::vec3 _offset = glm::vec3(0.0f); DoubleHashKey _doubleHashKey; diff --git a/tests/physics/src/ShapeManagerTests.cpp b/tests/physics/src/ShapeManagerTests.cpp index 66ac9d0c4a..c8805132fa 100644 --- a/tests/physics/src/ShapeManagerTests.cpp +++ b/tests/physics/src/ShapeManagerTests.cpp @@ -194,23 +194,23 @@ void ShapeManagerTests::addCompoundShape() { int numHullPoints = tetrahedron.size(); // compute the points of the hulls - QVector< QVector > hulls; + ShapeInfo::PointCollection pointCollection; int numHulls = 5; glm::vec3 offsetNormal(1.0f, 0.0f, 0.0f); for (int i = 0; i < numHulls; ++i) { glm::vec3 offset = (float)(i - numHulls/2) * offsetNormal; - QVector hull; + ShapeInfo::PointList pointList; float radius = (float)(i + 1); for (int j = 0; j < numHullPoints; ++j) { glm::vec3 point = radius * tetrahedron[j] + offset; - hull.push_back(point); + pointList.push_back(point); } - hulls.push_back(hull); + pointCollection.push_back(pointList); } // create the ShapeInfo ShapeInfo info; - info.setConvexHulls(hulls); + info.setPointCollection(hulls); // create the shape ShapeManager shapeManager; From 5d5dc2837b09984a9d7bcfb324cea0d632812f36 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 14 Jun 2016 13:58:53 -0700 Subject: [PATCH 089/171] fix comment --- libraries/render-utils/src/Model.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index ded1184c24..0470a238fc 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1002,7 +1002,7 @@ void Model::scaleToFit() { Extents modelMeshExtents = getUnscaledMeshExtents(); // size is our "target size in world space" - // we need to set our model scale so that the extents of the mesh, fit in a cube that size... + // we need to set our model scale so that the extents of the mesh, fit in a box that size... glm::vec3 meshDimensions = modelMeshExtents.maximum - modelMeshExtents.minimum; glm::vec3 rescaleDimensions = _scaleToFitDimensions / meshDimensions; setScaleInternal(rescaleDimensions); From 13bb174b8b6e84e4fb6ac735dfb6cfc6dec1dee8 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 14 Jun 2016 14:16:49 -0700 Subject: [PATCH 090/171] small change to commented out debug code --- libraries/physics/src/PhysicalEntitySimulation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index c5134fc027..cdf33a6edb 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -231,7 +231,7 @@ void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& re result.push_back(motionState); entityItr = _entitiesToAddToPhysics.erase(entityItr); } else { - //qDebug() << "Warning! Failed to generate new shape for entity." << entity->getName(); + //qWarning() << "Failed to generate new shape for entity." << entity->getName(); ++entityItr; } } else { From 9fc77ccfa2f048c61b956b5cd64796097c32a5ea Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 14 Jun 2016 14:19:45 -0700 Subject: [PATCH 091/171] use reference to avoid big copy --- libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 7d5f227558..eb6db2874f 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -1207,7 +1207,7 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() { // pull each triangle in the mesh into a polyhedron which can be collided with unsigned int i = 0; - const gpu::BufferView vertexBufferView = mesh->getVertexBuffer(); + const gpu::BufferView& vertexBufferView = mesh->getVertexBuffer(); const gpu::BufferView& indexBufferView = mesh->getIndexBuffer(); gpu::BufferView::Iterator it = indexBufferView.cbegin(); From a519b77ae7f2ca513bbc6f8456b4dd144dcbe56b Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 14 Jun 2016 14:22:28 -0700 Subject: [PATCH 092/171] add SHAPE_TYPE_MESH and build mesh shapes --- .../src/RenderableModelEntityItem.cpp | 102 +++++++++++++++++- libraries/physics/src/ShapeFactory.cpp | 97 ++++++++++++++++- libraries/physics/src/ShapeFactory.h | 15 ++- libraries/physics/src/ShapeManager.cpp | 18 ++-- libraries/shared/src/ShapeInfo.cpp | 23 ++-- libraries/shared/src/ShapeInfo.h | 12 ++- 6 files changed, 235 insertions(+), 32 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 07bcc05572..e7991eb638 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -599,13 +599,13 @@ bool RenderableModelEntityItem::isReadyToComputeShape() { void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { ShapeType type = getShapeType(); + glm::vec3 dimensions = getDimensions(); if (type == SHAPE_TYPE_COMPOUND) { updateModelBounds(); // should never fall in here when collision model not fully loaded // hence we assert that all geometries exist and are loaded assert(_model->isLoaded() && _model->isCollisionLoaded()); - const FBXGeometry& renderGeometry = _model->getFBXGeometry(); const FBXGeometry& collisionGeometry = _model->getCollisionFBXGeometry(); ShapeInfo::PointCollection& pointCollection = info.getPointCollection(); @@ -677,7 +677,8 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { // to the visual model and apply them to the collision model (without regard for the // collision model's extents). - glm::vec3 scale = getDimensions() / renderGeometry.getUnscaledMeshExtents().size(); + const FBXGeometry& renderGeometry = _model->getFBXGeometry(); + glm::vec3 scale = dimensions / renderGeometry.getUnscaledMeshExtents().size(); // multiply each point by scale before handing the point-set off to the physics engine. // also determine the extents of the collision model. AABox box; @@ -697,9 +698,104 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { glm::vec3 collisionModelDimensions = box.getDimensions(); info.setParams(type, collisionModelDimensions, _compoundShapeURL); info.setOffset(_model->getOffset()); + } else if (type == SHAPE_TYPE_MESH) { + updateModelBounds(); + + // should never fall in here when collision model not fully loaded + assert(_model->isLoaded()); + + ShapeInfo::PointCollection& pointCollection = info.getPointCollection(); + pointCollection.clear(); + + ShapeInfo::PointList points; + ShapeInfo::TriangleIndices& triangleIndices = info.getTriangleIndices(); + auto& meshes = _model->getGeometry()->getGeometry()->getMeshes(); + + glm::vec3 modelOffset = _model->getOffset(); + for (auto& mesh : meshes) { + const gpu::BufferView& vertices = mesh->getVertexBuffer(); + const gpu::BufferView& indices = mesh->getIndexBuffer(); + const gpu::BufferView& parts = mesh->getPartBuffer(); + + // copy points + uint32_t meshIndexOffset = (uint32_t)points.size(); + gpu::BufferView::Iterator vertexItr = vertices.cbegin(); + points.reserve(points.size() + vertices.getNumElements()); + Extents extents; + while (vertexItr != vertices.cend()) { + points.push_back(*vertexItr); + extents.addPoint(*vertexItr); + ++vertexItr; + } + + // scale points and shift by modelOffset + glm::vec3 extentsSize = extents.size(); + glm::vec3 scale = dimensions / extents.size(); + for (int i = 0; i < 3; ++i) { + if (extentsSize[i] < 1.0e-6f) { + scale[i] = 1.0f; + } + } + for (int i = 0; i < points.size(); ++i) { + points[i] = (points[i] * scale) + modelOffset; + } + + // copy triangleIndices + triangleIndices.reserve(triangleIndices.size() + indices.getNumElements()); + gpu::BufferView::Iterator partItr = parts.cbegin(); + while (partItr != parts.cend()) { + + if (partItr->_topology == model::Mesh::TRIANGLES) { + assert(partItr->_numIndices % 3 == 0); + auto indexItr = indices.cbegin() + partItr->_startIndex; + auto indexEnd = indexItr + partItr->_numIndices; + while (indexItr != indexEnd) { + triangleIndices.push_back(*indexItr + meshIndexOffset); + ++indexItr; + } + } else if (partItr->_topology == model::Mesh::TRIANGLE_STRIP) { + assert(partItr->_numIndices > 2); + uint32_t approxNumIndices = 3 * partItr->_numIndices; + if (approxNumIndices > (uint32_t)(triangleIndices.capacity() - triangleIndices.size())) { + // we underestimated the final size of triangleIndices so we pre-emptively expand it + triangleIndices.reserve(triangleIndices.size() + approxNumIndices); + } + + auto indexItr = indices.cbegin() + partItr->_startIndex; + auto indexEnd = indexItr + (partItr->_numIndices - 2); + + // first triangle uses the first three indices + triangleIndices.push_back(*indexItr + meshIndexOffset); + triangleIndices.push_back(*(++indexItr) + meshIndexOffset); + triangleIndices.push_back(*(indexItr + 1) + meshIndexOffset); + + // the rest use previous and next index + uint32_t triangleCount = 1; + while (indexItr != indexEnd) { + if (triangleCount % 2 == 0) { + // even triangles use first two indices in order + triangleIndices.push_back(*(indexItr) + meshIndexOffset); + triangleIndices.push_back(*(++indexItr) + meshIndexOffset); // yes pre-increment + } else { + // odd triangles swap order of first two indices + triangleIndices.push_back(*(indexItr + 1) + meshIndexOffset); + triangleIndices.push_back(*(indexItr++) + meshIndexOffset); // yes post-increment + } + triangleIndices.push_back(*(indexItr + 1) + meshIndexOffset); + ++triangleCount; + } + } else if (partItr->_topology == model::Mesh::QUADS) { + // TODO: support model::Mesh::QUADS + } + // TODO? support model::Mesh::QUAD_STRIP? + ++partItr; + } + } + pointCollection.push_back(points); + info.setParams(SHAPE_TYPE_MESH, 0.5f * dimensions, _modelURL); } else { ModelEntityItem::computeShapeInfo(info); - info.setParams(type, 0.5f * getDimensions()); + info.setParams(type, 0.5f * dimensions); adjustShapeInfoByRegistration(info); } } diff --git a/libraries/physics/src/ShapeFactory.cpp b/libraries/physics/src/ShapeFactory.cpp index a23ef97007..4d5f56853d 100644 --- a/libraries/physics/src/ShapeFactory.cpp +++ b/libraries/physics/src/ShapeFactory.cpp @@ -67,7 +67,8 @@ static const btVector3 _unitSphereDirections[NUM_UNIT_SPHERE_DIRECTIONS] = { }; -btConvexHullShape* ShapeFactory::createConvexHull(const QVector& points) { +// util method +btConvexHullShape* createConvexHull(const ShapeInfo::PointList& points) { assert(points.size() > 0); btConvexHullShape* hull = new btConvexHullShape(); @@ -158,6 +159,84 @@ btConvexHullShape* ShapeFactory::createConvexHull(const QVector& poin return hull; } +// util method +btTriangleIndexVertexArray* createStaticMeshArray(const ShapeInfo& info) { + assert(info.getType() == SHAPE_TYPE_MESH); // should only get here for mesh shapes + + const ShapeInfo::PointCollection& pointCollection = info.getPointCollection(); + assert(pointCollection.size() == 1); // should only have one mesh + + const ShapeInfo::PointList& pointList = pointCollection[0]; + assert(pointList.size() > 2); // should have at least one triangle's worth of points + + const ShapeInfo::TriangleIndices& triangleIndices = info.getTriangleIndices(); + assert(triangleIndices.size() > 2); // should have at least one triangle's worth of indices + + // allocate mesh buffers + btIndexedMesh mesh; + int32_t numIndices = triangleIndices.size(); + const int32_t VERTICES_PER_TRIANGLE = 3; + mesh.m_numTriangles = numIndices / VERTICES_PER_TRIANGLE; + if (numIndices < INT16_MAX) { + // small number of points so we can use 16-bit indices + mesh.m_triangleIndexBase = new unsigned char[sizeof(int16_t) * (size_t)numIndices]; + mesh.m_indexType = PHY_SHORT; + mesh.m_triangleIndexStride = VERTICES_PER_TRIANGLE * sizeof(int16_t); + } else { + mesh.m_triangleIndexBase = new unsigned char[sizeof(int32_t) * (size_t)numIndices]; + mesh.m_indexType = PHY_INTEGER; + mesh.m_triangleIndexStride = VERTICES_PER_TRIANGLE * sizeof(int32_t); + } + mesh.m_numVertices = pointList.size(); + mesh.m_vertexBase = new unsigned char[VERTICES_PER_TRIANGLE * sizeof(btScalar) * (size_t)mesh.m_numVertices]; + mesh.m_vertexStride = VERTICES_PER_TRIANGLE * sizeof(btScalar); + mesh.m_vertexType = PHY_FLOAT; + + // copy data into buffers + btScalar* vertexData = static_cast((void*)(mesh.m_vertexBase)); + for (int32_t i = 0; i < mesh.m_numVertices; ++i) { + int32_t j = i * VERTICES_PER_TRIANGLE; + const glm::vec3& point = pointList[i]; + vertexData[j] = point.x; + vertexData[j + 1] = point.y; + vertexData[j + 2] = point.z; + } + if (numIndices < INT16_MAX) { + int16_t* indices = static_cast((void*)(mesh.m_triangleIndexBase)); + for (int32_t i = 0; i < numIndices; ++i) { + indices[i] = triangleIndices[i]; + } + } else { + int32_t* indices = static_cast((void*)(mesh.m_triangleIndexBase)); + for (int32_t i = 0; i < numIndices; ++i) { + indices[i] = triangleIndices[i]; + } + } + + // store buffers in a new dataArray and return the pointer + // (external StaticMeshShape will own all of the data that was allocated here) + btTriangleIndexVertexArray* dataArray = new btTriangleIndexVertexArray; + dataArray->addIndexedMesh(mesh, mesh.m_indexType); + return dataArray; +} + +// util method +void deleteStaticMeshArray(btTriangleIndexVertexArray* dataArray) { + assert(dataArray); + IndexedMeshArray& meshes = dataArray->getIndexedMeshArray(); + for (int32_t i = 0; i < meshes.size(); ++i) { + btIndexedMesh mesh = meshes[i]; + mesh.m_numTriangles = 0; + delete [] mesh.m_triangleIndexBase; + mesh.m_triangleIndexBase = nullptr; + mesh.m_numVertices = 0; + delete [] mesh.m_vertexBase; + mesh.m_vertexBase = nullptr; + } + meshes.clear(); + delete dataArray; +} + btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) { btCollisionShape* shape = NULL; int type = info.getType(); @@ -195,6 +274,11 @@ btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) { } } break; + case SHAPE_TYPE_MESH: { + btTriangleIndexVertexArray* dataArray = createStaticMeshArray(info); + shape = new StaticMeshShape(dataArray); + } + break; } if (shape) { if (glm::length2(info.getOffset()) > MIN_SHAPE_OFFSET * MIN_SHAPE_OFFSET) { @@ -228,3 +312,14 @@ void ShapeFactory::deleteShape(btCollisionShape* shape) { } delete shape; } + +// the dataArray must be created before we create the StaticMeshShape +ShapeFactory::StaticMeshShape::StaticMeshShape(btTriangleIndexVertexArray* dataArray) +: btBvhTriangleMeshShape(dataArray, true), _dataArray(dataArray) { + assert(dataArray); +} + +ShapeFactory::StaticMeshShape::~StaticMeshShape() { + deleteStaticMeshArray(_dataArray); + _dataArray = nullptr; +} diff --git a/libraries/physics/src/ShapeFactory.h b/libraries/physics/src/ShapeFactory.h index 1ba2bdb619..6202612eb9 100644 --- a/libraries/physics/src/ShapeFactory.h +++ b/libraries/physics/src/ShapeFactory.h @@ -20,9 +20,22 @@ // translates between ShapeInfo and btShape namespace ShapeFactory { - btConvexHullShape* createConvexHull(const QVector& points); btCollisionShape* createShapeFromInfo(const ShapeInfo& info); void deleteShape(btCollisionShape* shape); + + //btTriangleIndexVertexArray* createStaticMeshArray(const ShapeInfo& info); + //void deleteStaticMeshArray(btTriangleIndexVertexArray* dataArray); + + class StaticMeshShape : public btBvhTriangleMeshShape { + public: + StaticMeshShape() = delete; + StaticMeshShape(btTriangleIndexVertexArray* dataArray); + ~StaticMeshShape(); + + private: + // the StaticMeshShape owns its vertex/index data + btTriangleIndexVertexArray* _dataArray; + }; }; #endif // hifi_ShapeFactory_h diff --git a/libraries/physics/src/ShapeManager.cpp b/libraries/physics/src/ShapeManager.cpp index 4231d1eb60..4fa660239c 100644 --- a/libraries/physics/src/ShapeManager.cpp +++ b/libraries/physics/src/ShapeManager.cpp @@ -32,15 +32,13 @@ btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) { if (info.getType() == SHAPE_TYPE_NONE) { return NULL; } - if (info.getType() != SHAPE_TYPE_COMPOUND) { - // Very small or large non-compound objects are not supported. - float diagonal = 4.0f * glm::length2(info.getHalfExtents()); - const float MIN_SHAPE_DIAGONAL_SQUARED = 3.0e-4f; // 1 cm cube - if (diagonal < MIN_SHAPE_DIAGONAL_SQUARED) { - // qCDebug(physics) << "ShapeManager::getShape -- not making shape due to size" << diagonal; - return NULL; - } + const float MIN_SHAPE_DIAGONAL_SQUARED = 3.0e-4f; // 1 cm cube + if (4.0f * glm::length2(info.getHalfExtents()) < MIN_SHAPE_DIAGONAL_SQUARED) { + // tiny shapes are not supported + // qCDebug(physics) << "ShapeManager::getShape -- not making shape due to size" << diagonal; + return NULL; } + DoubleHashKey key = info.getHash(); ShapeReference* shapeRef = _shapeMap.find(key); if (shapeRef) { @@ -66,8 +64,8 @@ bool ShapeManager::releaseShapeByKey(const DoubleHashKey& key) { shapeRef->refCount--; if (shapeRef->refCount == 0) { _pendingGarbage.push_back(key); - const int MAX_GARBAGE_CAPACITY = 255; - if (_pendingGarbage.size() > MAX_GARBAGE_CAPACITY) { + const int MAX_SHAPE_GARBAGE_CAPACITY = 255; + if (_pendingGarbage.size() > MAX_SHAPE_GARBAGE_CAPACITY) { collectGarbage(); } } diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index cd0cb6fe8a..ed1a76ef99 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -41,9 +41,9 @@ void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString break; } case SHAPE_TYPE_COMPOUND: + case SHAPE_TYPE_MESH: _url = QUrl(url); - _halfExtents = halfExtents; - break; + // yes, fall through default: _halfExtents = halfExtents; break; @@ -182,18 +182,15 @@ const DoubleHashKey& ShapeInfo::getHash() const { // NOTE: we cache the key so we only ever need to compute it once for any valid ShapeInfo instance. if (_doubleHashKey.isNull() && _type != SHAPE_TYPE_NONE) { bool useOffset = glm::length2(_offset) > MIN_SHAPE_OFFSET * MIN_SHAPE_OFFSET; - // The key is not yet cached therefore we must compute it! To this end we bypass the const-ness - // of this method by grabbing a non-const pointer to "this" and a non-const reference to _doubleHashKey. - ShapeInfo* thisPtr = const_cast(this); - DoubleHashKey& key = thisPtr->_doubleHashKey; + // The key is not yet cached therefore we must compute it. // compute hash1 // TODO?: provide lookup table for hash/hash2 of _type rather than recompute? uint32_t primeIndex = 0; - key.computeHash((uint32_t)_type, primeIndex++); + _doubleHashKey.computeHash((uint32_t)_type, primeIndex++); // compute hash1 - uint32_t hash = key.getHash(); + uint32_t hash = _doubleHashKey.getHash(); for (int j = 0; j < 3; ++j) { // NOTE: 0.49f is used to bump the float up almost half a millimeter // so the cast to int produces a round() effect rather than a floor() @@ -206,10 +203,10 @@ const DoubleHashKey& ShapeInfo::getHash() const { primeIndex++); } } - key.setHash(hash); + _doubleHashKey.setHash(hash); // compute hash2 - hash = key.getHash2(); + hash = _doubleHashKey.getHash2(); for (int j = 0; j < 3; ++j) { // NOTE: 0.49f is used to bump the float up almost half a millimeter // so the cast to int produces a round() effect rather than a floor() @@ -226,7 +223,7 @@ const DoubleHashKey& ShapeInfo::getHash() const { hash += ~(floatHash << 10); hash = (hash << 16) | (hash >> 16); } - key.setHash2(hash); + _doubleHashKey.setHash2(hash); if (_type == SHAPE_TYPE_COMPOUND || _type == SHAPE_TYPE_MESH) { QString url = _url.toString(); @@ -235,8 +232,8 @@ const DoubleHashKey& ShapeInfo::getHash() const { QByteArray baUrl = url.toLocal8Bit(); const char *cUrl = baUrl.data(); uint32_t urlHash = qChecksum(cUrl, baUrl.count()); - key.setHash(key.getHash() ^ urlHash); - key.setHash2(key.getHash2() ^ urlHash); + _doubleHashKey.setHash(_doubleHashKey.getHash() ^ urlHash); + _doubleHashKey.setHash2(_doubleHashKey.getHash2() ^ urlHash); } } } diff --git a/libraries/shared/src/ShapeInfo.h b/libraries/shared/src/ShapeInfo.h index 7f178bb53a..794f31a987 100644 --- a/libraries/shared/src/ShapeInfo.h +++ b/libraries/shared/src/ShapeInfo.h @@ -30,14 +30,15 @@ enum ShapeType { SHAPE_TYPE_NONE, SHAPE_TYPE_BOX, SHAPE_TYPE_SPHERE, - SHAPE_TYPE_PLANE, - SHAPE_TYPE_COMPOUND, SHAPE_TYPE_CAPSULE_X, SHAPE_TYPE_CAPSULE_Y, SHAPE_TYPE_CAPSULE_Z, SHAPE_TYPE_CYLINDER_X, SHAPE_TYPE_CYLINDER_Y, SHAPE_TYPE_CYLINDER_Z, + SHAPE_TYPE_HULL, + SHAPE_TYPE_PLANE, + SHAPE_TYPE_COMPOUND, SHAPE_TYPE_MESH }; @@ -62,10 +63,13 @@ public: const glm::vec3& getHalfExtents() const { return _halfExtents; } const glm::vec3& getOffset() const { return _offset; } + uint32_t getNumSubShapes() const; PointCollection& getPointCollection() { return _pointCollection; } const PointCollection& getPointCollection() const { return _pointCollection; } - uint32_t getNumSubShapes() const; + + TriangleIndices& getTriangleIndices() { return _triangleIndices; } + const TriangleIndices& getTriangleIndices() const { return _triangleIndices; } int getLargestSubshapePointCount() const; @@ -83,7 +87,7 @@ protected: TriangleIndices _triangleIndices; glm::vec3 _halfExtents = glm::vec3(0.0f); glm::vec3 _offset = glm::vec3(0.0f); - DoubleHashKey _doubleHashKey; + mutable DoubleHashKey _doubleHashKey; ShapeType _type = SHAPE_TYPE_NONE; }; From f41fb30aceb4f6c9a66013397fa899c17c84ad11 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 14 Jun 2016 14:59:52 -0700 Subject: [PATCH 093/171] add "Static Mesh" option to edit.js --- .../entities/src/EntityItemProperties.cpp | 23 +++++++++++++++---- scripts/system/html/entityProperties.html | 1 + 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 89bf9f1a21..c521962976 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -88,8 +88,21 @@ void EntityItemProperties::setLastEdited(quint64 usecTime) { _lastEdited = usecTime > _created ? usecTime : _created; } -const char* shapeTypeNames[] = {"none", "box", "sphere", "plane", "compound", "capsule-x", - "capsule-y", "capsule-z", "cylinder-x", "cylinder-y", "cylinder-z"}; +const char* shapeTypeNames[] = { + "none", + "box", + "sphere", + "capsule-x", + "capsule-y", + "capsule-z", + "cylinder-x", + "cylinder-y", + "cylinder-z", + "hull", + "plane", + "compound", + "static-mesh" +}; QHash stringToShapeTypeLookup; @@ -101,14 +114,16 @@ void buildStringToShapeTypeLookup() { addShapeType(SHAPE_TYPE_NONE); addShapeType(SHAPE_TYPE_BOX); addShapeType(SHAPE_TYPE_SPHERE); - addShapeType(SHAPE_TYPE_PLANE); - addShapeType(SHAPE_TYPE_COMPOUND); addShapeType(SHAPE_TYPE_CAPSULE_X); addShapeType(SHAPE_TYPE_CAPSULE_Y); addShapeType(SHAPE_TYPE_CAPSULE_Z); addShapeType(SHAPE_TYPE_CYLINDER_X); addShapeType(SHAPE_TYPE_CYLINDER_Y); addShapeType(SHAPE_TYPE_CYLINDER_Z); + addShapeType(SHAPE_TYPE_HULL); + addShapeType(SHAPE_TYPE_PLANE); + addShapeType(SHAPE_TYPE_COMPOUND); + addShapeType(SHAPE_TYPE_MESH); } QString getCollisionGroupAsString(uint8_t group) { diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 0af199ef56..121e38c340 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -1646,6 +1646,7 @@ +
From 2f6e5ab2ee3bfcf30b1b98194e2ca2b60d39c849 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 14 Jun 2016 15:26:13 -0700 Subject: [PATCH 094/171] remove fall-through in switch/case logic --- libraries/shared/src/ShapeInfo.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index ed1a76ef99..af81a0f96b 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -27,12 +27,12 @@ void ShapeInfo::clear() { void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString url) { _type = type; + _halfExtents = halfExtents; switch(type) { case SHAPE_TYPE_NONE: _halfExtents = glm::vec3(0.0f); break; case SHAPE_TYPE_BOX: - _halfExtents = halfExtents; break; case SHAPE_TYPE_SPHERE: { // sphere radius is max of halfExtents @@ -43,9 +43,8 @@ void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString case SHAPE_TYPE_COMPOUND: case SHAPE_TYPE_MESH: _url = QUrl(url); - // yes, fall through + break; default: - _halfExtents = halfExtents; break; } _doubleHashKey.clear(); From d1752211e6181c29a4814151f29d10256e31e032 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 14 Jun 2016 15:52:41 -0700 Subject: [PATCH 095/171] make implicit casts explict --- libraries/entities-renderer/src/RenderableModelEntityItem.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index e7991eb638..73c3c2e50c 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -720,7 +720,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { // copy points uint32_t meshIndexOffset = (uint32_t)points.size(); gpu::BufferView::Iterator vertexItr = vertices.cbegin(); - points.reserve(points.size() + vertices.getNumElements()); + points.reserve((int32_t)((gpu::Size)points.size() + vertices.getNumElements())); Extents extents; while (vertexItr != vertices.cend()) { points.push_back(*vertexItr); @@ -741,7 +741,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { } // copy triangleIndices - triangleIndices.reserve(triangleIndices.size() + indices.getNumElements()); + triangleIndices.reserve((int32_t)((gpu::Size)(triangleIndices.size()) + indices.getNumElements())); gpu::BufferView::Iterator partItr = parts.cbegin(); while (partItr != parts.cend()) { From ab3548cac089189db0f4bf38c15b98f8872e4cf4 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 15 Jun 2016 14:56:57 -0700 Subject: [PATCH 096/171] fix crash --- libraries/entities-renderer/src/RenderableModelEntityItem.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 73c3c2e50c..8f0c9f0a02 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -593,6 +593,8 @@ bool RenderableModelEntityItem::isReadyToComputeShape() { // the model is still being downloaded. return false; + } else if (type == SHAPE_TYPE_MESH) { + return (_model && _model->isLoaded()); } return true; } From f444b70fdc6d018c2e634eaa90e06a6e9d80c123 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 15 Jun 2016 18:15:22 -0700 Subject: [PATCH 097/171] fix collision shape for scaled models --- .../src/RenderableModelEntityItem.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 8f0c9f0a02..c01ad8b92a 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -713,7 +713,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { ShapeInfo::TriangleIndices& triangleIndices = info.getTriangleIndices(); auto& meshes = _model->getGeometry()->getGeometry()->getMeshes(); - glm::vec3 modelOffset = _model->getOffset(); + glm::vec3 scaledModelOffset = _model->getOffset() * _model->getScale(); for (auto& mesh : meshes) { const gpu::BufferView& vertices = mesh->getVertexBuffer(); const gpu::BufferView& indices = mesh->getIndexBuffer(); @@ -730,16 +730,16 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { ++vertexItr; } - // scale points and shift by modelOffset + // scale and shift glm::vec3 extentsSize = extents.size(); - glm::vec3 scale = dimensions / extents.size(); + glm::vec3 scaleToFit = dimensions / extents.size(); for (int i = 0; i < 3; ++i) { if (extentsSize[i] < 1.0e-6f) { - scale[i] = 1.0f; + scaleToFit[i] = 1.0f; } } for (int i = 0; i < points.size(); ++i) { - points[i] = (points[i] * scale) + modelOffset; + points[i] = (points[i] * scaleToFit) + scaledModelOffset; } // copy triangleIndices From c0c77e90272a74afeea6556013468a12afe89d39 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 16 Jun 2016 09:26:33 -0700 Subject: [PATCH 098/171] optimize/cleanup compound/mesh shape computation --- .../src/RenderableModelEntityItem.cpp | 61 +++++++++++-------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index c01ad8b92a..13d757e9b4 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -618,6 +618,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { // to find one actual "mesh" (with one or more meshParts in it), but we loop over the meshes, just in case. const uint32_t TRIANGLE_STRIDE = 3; const uint32_t QUAD_STRIDE = 4; + Extents extents; foreach (const FBXMesh& mesh, collisionGeometry.meshes) { // each meshPart is a convex hull foreach (const FBXMeshPart &meshPart, mesh.parts) { @@ -631,14 +632,18 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { glm::vec3 p0 = mesh.vertices[meshPart.triangleIndices[j]]; glm::vec3 p1 = mesh.vertices[meshPart.triangleIndices[j + 1]]; glm::vec3 p2 = mesh.vertices[meshPart.triangleIndices[j + 2]]; + if (!pointsInPart.contains(p0)) { pointsInPart << p0; + extents.addPoint(p0); } if (!pointsInPart.contains(p1)) { pointsInPart << p1; + extents.addPoint(p1); } if (!pointsInPart.contains(p2)) { pointsInPart << p2; + extents.addPoint(p2); } } @@ -652,15 +657,19 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { glm::vec3 p3 = mesh.vertices[meshPart.quadIndices[j + 3]]; if (!pointsInPart.contains(p0)) { pointsInPart << p0; + extents.addPoint(p0); } if (!pointsInPart.contains(p1)) { pointsInPart << p1; + extents.addPoint(p1); } if (!pointsInPart.contains(p2)) { pointsInPart << p2; + extents.addPoint(p2); } if (!pointsInPart.contains(p3)) { pointsInPart << p3; + extents.addPoint(p3); } } @@ -673,33 +682,30 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { } } + glm::vec3 extentsSize = extents.size(); + glm::vec3 scaleToFit = dimensions / extentsSize; + for (int i = 0; i < 3; ++i) { + if (extentsSize[i] < 1.0e-6f) { + scaleToFit[i] = 1.0f; + } + } + // We expect that the collision model will have the same units and will be displaced // from its origin in the same way the visual model is. The visual model has // been centered and probably scaled. We take the scaling and offset which were applied // to the visual model and apply them to the collision model (without regard for the // collision model's extents). - const FBXGeometry& renderGeometry = _model->getFBXGeometry(); - glm::vec3 scale = dimensions / renderGeometry.getUnscaledMeshExtents().size(); // multiply each point by scale before handing the point-set off to the physics engine. // also determine the extents of the collision model. - AABox box; + glm::vec3 scaledModelOffset = _model->getOffset() * _model->getScale(); for (int i = 0; i < pointCollection.size(); i++) { for (int j = 0; j < pointCollection[i].size(); j++) { - // compensate for registration - pointCollection[i][j] += _model->getOffset(); - // scale so the collision points match the model points - pointCollection[i][j] *= scale; - // this next subtraction is done so we can give info the offset, which will cause - // the shape-key to change. - pointCollection[i][j] -= _model->getOffset(); - box += pointCollection[i][j]; + pointCollection[i][j] = (pointCollection[i][j] * scaleToFit) + scaledModelOffset; } } - glm::vec3 collisionModelDimensions = box.getDimensions(); - info.setParams(type, collisionModelDimensions, _compoundShapeURL); - info.setOffset(_model->getOffset()); + info.setParams(type, dimensions, _compoundShapeURL); } else if (type == SHAPE_TYPE_MESH) { updateModelBounds(); @@ -713,6 +719,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { ShapeInfo::TriangleIndices& triangleIndices = info.getTriangleIndices(); auto& meshes = _model->getGeometry()->getGeometry()->getMeshes(); + Extents extents; glm::vec3 scaledModelOffset = _model->getOffset() * _model->getScale(); for (auto& mesh : meshes) { const gpu::BufferView& vertices = mesh->getVertexBuffer(); @@ -723,25 +730,12 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { uint32_t meshIndexOffset = (uint32_t)points.size(); gpu::BufferView::Iterator vertexItr = vertices.cbegin(); points.reserve((int32_t)((gpu::Size)points.size() + vertices.getNumElements())); - Extents extents; while (vertexItr != vertices.cend()) { points.push_back(*vertexItr); extents.addPoint(*vertexItr); ++vertexItr; } - // scale and shift - glm::vec3 extentsSize = extents.size(); - glm::vec3 scaleToFit = dimensions / extents.size(); - for (int i = 0; i < 3; ++i) { - if (extentsSize[i] < 1.0e-6f) { - scaleToFit[i] = 1.0f; - } - } - for (int i = 0; i < points.size(); ++i) { - points[i] = (points[i] * scaleToFit) + scaledModelOffset; - } - // copy triangleIndices triangleIndices.reserve((int32_t)((gpu::Size)(triangleIndices.size()) + indices.getNumElements())); gpu::BufferView::Iterator partItr = parts.cbegin(); @@ -793,6 +787,19 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { ++partItr; } } + + // scale and shift + glm::vec3 extentsSize = extents.size(); + glm::vec3 scaleToFit = dimensions / extentsSize; + for (int i = 0; i < 3; ++i) { + if (extentsSize[i] < 1.0e-6f) { + scaleToFit[i] = 1.0f; + } + } + for (int i = 0; i < points.size(); ++i) { + points[i] = (points[i] * scaleToFit) + scaledModelOffset; + } + pointCollection.push_back(points); info.setParams(SHAPE_TYPE_MESH, 0.5f * dimensions, _modelURL); } else { From 5484b6fbb7e9a545007275a2da0129000c285529 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 16 Jun 2016 12:15:47 -0700 Subject: [PATCH 099/171] more correct extraction from triangle strips --- .../src/RenderableModelEntityItem.cpp | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 13d757e9b4..1adbd4fe1a 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -761,24 +761,27 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { auto indexEnd = indexItr + (partItr->_numIndices - 2); // first triangle uses the first three indices - triangleIndices.push_back(*indexItr + meshIndexOffset); - triangleIndices.push_back(*(++indexItr) + meshIndexOffset); - triangleIndices.push_back(*(indexItr + 1) + meshIndexOffset); + triangleIndices.push_back(*(indexItr++) + meshIndexOffset); + triangleIndices.push_back(*(indexItr++) + meshIndexOffset); + triangleIndices.push_back(*(indexItr++) + meshIndexOffset); // the rest use previous and next index uint32_t triangleCount = 1; while (indexItr != indexEnd) { - if (triangleCount % 2 == 0) { - // even triangles use first two indices in order - triangleIndices.push_back(*(indexItr) + meshIndexOffset); - triangleIndices.push_back(*(++indexItr) + meshIndexOffset); // yes pre-increment - } else { - // odd triangles swap order of first two indices - triangleIndices.push_back(*(indexItr + 1) + meshIndexOffset); - triangleIndices.push_back(*(indexItr++) + meshIndexOffset); // yes post-increment + if ((*indexItr) != model::Mesh::PRIMITIVE_RESTART_INDEX) { + if (triangleCount % 2 == 0) { + // even triangles use first two indices in order + triangleIndices.push_back(*(indexItr - 2) + meshIndexOffset); + triangleIndices.push_back(*(indexItr - 1) + meshIndexOffset); + } else { + // odd triangles swap order of first two indices + triangleIndices.push_back(*(indexItr - 1) + meshIndexOffset); + triangleIndices.push_back(*(indexItr - 2) + meshIndexOffset); + } + triangleIndices.push_back(*indexItr + meshIndexOffset); + ++triangleCount; } - triangleIndices.push_back(*(indexItr + 1) + meshIndexOffset); - ++triangleCount; + ++indexItr; } } else if (partItr->_topology == model::Mesh::QUADS) { // TODO: support model::Mesh::QUADS From f22a5613bd1b7a2ba0b86de3424e92edda0bd4e7 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 20 Jun 2016 17:27:39 -0700 Subject: [PATCH 100/171] fix static mesh for model with per-mesh transforms --- .../src/RenderableModelEntityItem.cpp | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 1adbd4fe1a..7b1dddfcab 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -707,6 +707,22 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { info.setParams(type, dimensions, _compoundShapeURL); } else if (type == SHAPE_TYPE_MESH) { + // compute meshPart local transforms + QVector localTransforms; + const FBXGeometry& geometry = _model->getFBXGeometry(); + int numberOfMeshes = geometry.meshes.size(); + for (int i = 0; i < numberOfMeshes; i++) { + const FBXMesh& mesh = geometry.meshes.at(i); + if (mesh.clusters.size() > 0) { + const FBXCluster& cluster = mesh.clusters.at(0); + auto jointMatrix = _model->getRig()->getJointTransform(cluster.jointIndex); + localTransforms.push_back(jointMatrix * cluster.inverseBindMatrix); + } else { + glm::mat4 identity; + localTransforms.push_back(identity); + } + } + updateModelBounds(); // should never fall in here when collision model not fully loaded @@ -720,19 +736,21 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { auto& meshes = _model->getGeometry()->getGeometry()->getMeshes(); Extents extents; - glm::vec3 scaledModelOffset = _model->getOffset() * _model->getScale(); + int meshCount = 0; for (auto& mesh : meshes) { const gpu::BufferView& vertices = mesh->getVertexBuffer(); const gpu::BufferView& indices = mesh->getIndexBuffer(); const gpu::BufferView& parts = mesh->getPartBuffer(); // copy points + const glm::mat4& localTransform = localTransforms[meshCount]; uint32_t meshIndexOffset = (uint32_t)points.size(); gpu::BufferView::Iterator vertexItr = vertices.cbegin(); points.reserve((int32_t)((gpu::Size)points.size() + vertices.getNumElements())); while (vertexItr != vertices.cend()) { - points.push_back(*vertexItr); - extents.addPoint(*vertexItr); + glm::vec3 point = extractTranslation(localTransform * glm::translate(*vertexItr)); + points.push_back(point); + extents.addPoint(point); ++vertexItr; } @@ -783,12 +801,10 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { } ++indexItr; } - } else if (partItr->_topology == model::Mesh::QUADS) { - // TODO: support model::Mesh::QUADS } - // TODO? support model::Mesh::QUAD_STRIP? ++partItr; } + ++meshCount; } // scale and shift @@ -800,7 +816,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { } } for (int i = 0; i < points.size(); ++i) { - points[i] = (points[i] * scaleToFit) + scaledModelOffset; + points[i] = (points[i] * scaleToFit); } pointCollection.push_back(points); From 2ac6dc8798995490cf4a9f02cf83aac85c773ab8 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Tue, 21 Jun 2016 09:48:01 -0700 Subject: [PATCH 101/171] do not tilt virtual keyboard --- plugins/openvr/src/OpenVrHelpers.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index dc38aa0a0a..4f02c0384d 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -138,7 +138,7 @@ void showOpenVrKeyboard(bool show = true) { if (vr::VROverlayError_None == showKeyboardResult) { _keyboardShown = true; // Try to position the keyboard slightly below where the user is looking. - mat4 headPose = toGlm(_trackedDevicePose[0].mDeviceToAbsoluteTracking); + mat4 headPose = cancelOutRollAndPitch(toGlm(_trackedDevicePose[0].mDeviceToAbsoluteTracking)); mat4 keyboardTransform = glm::translate(headPose, vec3(0, -0.5, -1)); keyboardTransform = keyboardTransform * glm::rotate(mat4(), 3.14159f / 4.0f, vec3(-1, 0, 0)); auto keyboardTransformVr = toOpenVr(keyboardTransform); From 972c292857d76ccdd92c38cdedbb500b369772c4 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 21 Jun 2016 11:59:11 -0700 Subject: [PATCH 102/171] Fix for equip-hotspot being visible for whiteboard markers --- .../system/controllers/handControllerGrab.js | 37 ++++++++----------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index f1ecc15392..39d85f1224 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -879,28 +879,23 @@ function MyController(hand) { var entities = Entities.findEntities(MyAvatar.position, HOTSPOT_DRAW_DISTANCE); var i, l = entities.length; for (i = 0; i < l; i++) { - - // is this entity equipable? - var grabData = getEntityCustomData(GRABBABLE_DATA_KEY, entities[i], undefined); var grabProps = Entities.getEntityProperties(entities[i], GRABBABLE_PROPERTIES); - if (grabData) { - // does this entity have an attach point? - var wearableData = getEntityCustomData("wearable", entities[i], undefined); - if (wearableData && wearableData.joints) { - var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"; - if (wearableData.joints[handJointName]) { - // draw the hotspot - this.equipHotspotOverlays.push(Overlays.addOverlay("sphere", { - position: grabProps.position, - size: 0.2, - color: { red: 90, green: 255, blue: 90 }, - alpha: 0.7, - solid: true, - visible: true, - ignoreRayIntersection: false, - drawInFront: false - })); - } + // does this entity have an attach point? + var wearableData = getEntityCustomData("wearable", entities[i], undefined); + if (wearableData && wearableData.joints) { + var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"; + if (wearableData.joints[handJointName]) { + // draw the hotspot + this.equipHotspotOverlays.push(Overlays.addOverlay("sphere", { + position: grabProps.position, + size: 0.2, + color: { red: 90, green: 255, blue: 90 }, + alpha: 0.7, + solid: true, + visible: true, + ignoreRayIntersection: false, + drawInFront: false + })); } } } From 937bd0c1bea53ed3cd3954b91f138a8cce46f16c Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 21 Jun 2016 13:54:21 -0700 Subject: [PATCH 103/171] SHAPE_TYPE_MESH --> SHAPE_TYPE_STATIC_MESH --- .../entities-renderer/src/RenderableModelEntityItem.cpp | 6 +++--- libraries/entities/src/EntityItemProperties.cpp | 2 +- libraries/physics/src/ShapeFactory.cpp | 4 ++-- libraries/shared/src/ShapeInfo.cpp | 4 ++-- libraries/shared/src/ShapeInfo.h | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 7b1dddfcab..ab91edd294 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -593,7 +593,7 @@ bool RenderableModelEntityItem::isReadyToComputeShape() { // the model is still being downloaded. return false; - } else if (type == SHAPE_TYPE_MESH) { + } else if (type == SHAPE_TYPE_STATIC_MESH) { return (_model && _model->isLoaded()); } return true; @@ -706,7 +706,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { } info.setParams(type, dimensions, _compoundShapeURL); - } else if (type == SHAPE_TYPE_MESH) { + } else if (type == SHAPE_TYPE_STATIC_MESH) { // compute meshPart local transforms QVector localTransforms; const FBXGeometry& geometry = _model->getFBXGeometry(); @@ -820,7 +820,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { } pointCollection.push_back(points); - info.setParams(SHAPE_TYPE_MESH, 0.5f * dimensions, _modelURL); + info.setParams(SHAPE_TYPE_STATIC_MESH, 0.5f * dimensions, _modelURL); } else { ModelEntityItem::computeShapeInfo(info); info.setParams(type, 0.5f * dimensions); diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index c521962976..a62f4b182a 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -123,7 +123,7 @@ void buildStringToShapeTypeLookup() { addShapeType(SHAPE_TYPE_HULL); addShapeType(SHAPE_TYPE_PLANE); addShapeType(SHAPE_TYPE_COMPOUND); - addShapeType(SHAPE_TYPE_MESH); + addShapeType(SHAPE_TYPE_STATIC_MESH); } QString getCollisionGroupAsString(uint8_t group) { diff --git a/libraries/physics/src/ShapeFactory.cpp b/libraries/physics/src/ShapeFactory.cpp index 4d5f56853d..3afc170a8c 100644 --- a/libraries/physics/src/ShapeFactory.cpp +++ b/libraries/physics/src/ShapeFactory.cpp @@ -161,7 +161,7 @@ btConvexHullShape* createConvexHull(const ShapeInfo::PointList& points) { // util method btTriangleIndexVertexArray* createStaticMeshArray(const ShapeInfo& info) { - assert(info.getType() == SHAPE_TYPE_MESH); // should only get here for mesh shapes + assert(info.getType() == SHAPE_TYPE_STATIC_MESH); // should only get here for mesh shapes const ShapeInfo::PointCollection& pointCollection = info.getPointCollection(); assert(pointCollection.size() == 1); // should only have one mesh @@ -274,7 +274,7 @@ btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) { } } break; - case SHAPE_TYPE_MESH: { + case SHAPE_TYPE_STATIC_MESH: { btTriangleIndexVertexArray* dataArray = createStaticMeshArray(info); shape = new StaticMeshShape(dataArray); } diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index af81a0f96b..e0f4cc18b2 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -41,7 +41,7 @@ void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString break; } case SHAPE_TYPE_COMPOUND: - case SHAPE_TYPE_MESH: + case SHAPE_TYPE_STATIC_MESH: _url = QUrl(url); break; default: @@ -224,7 +224,7 @@ const DoubleHashKey& ShapeInfo::getHash() const { } _doubleHashKey.setHash2(hash); - if (_type == SHAPE_TYPE_COMPOUND || _type == SHAPE_TYPE_MESH) { + if (_type == SHAPE_TYPE_COMPOUND || _type == SHAPE_TYPE_STATIC_MESH) { QString url = _url.toString(); if (!url.isEmpty()) { // fold the urlHash into both parts diff --git a/libraries/shared/src/ShapeInfo.h b/libraries/shared/src/ShapeInfo.h index 794f31a987..96132a4b23 100644 --- a/libraries/shared/src/ShapeInfo.h +++ b/libraries/shared/src/ShapeInfo.h @@ -39,7 +39,7 @@ enum ShapeType { SHAPE_TYPE_HULL, SHAPE_TYPE_PLANE, SHAPE_TYPE_COMPOUND, - SHAPE_TYPE_MESH + SHAPE_TYPE_STATIC_MESH }; class ShapeInfo { From 4c9ec7ca432bfac30bf84c0bf829845d111c6191 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 21 Jun 2016 13:55:11 -0700 Subject: [PATCH 104/171] protect physics against bad entity properties --- libraries/physics/src/EntityMotionState.cpp | 5 +++ libraries/physics/src/ObjectMotionState.cpp | 48 +++++++++++---------- 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 8f22c576f0..e0d355911a 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -152,6 +152,11 @@ PhysicsMotionType EntityMotionState::computePhysicsMotionType() const { } assert(entityTreeIsLocked()); + if (_entity->getShapeType() == SHAPE_TYPE_STATIC_MESH + || (_body && _body->getCollisionShape()->getShapeType() == TRIANGLE_MESH_SHAPE_PROXYTYPE)) { + return MOTION_TYPE_STATIC; + } + if (_entity->getDynamic()) { if (!_entity->getParentID().isNull()) { // if something would have been dynamic but is a child of something else, force it to be kinematic, instead. diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp index de435e80da..f915121718 100644 --- a/libraries/physics/src/ObjectMotionState.cpp +++ b/libraries/physics/src/ObjectMotionState.cpp @@ -203,35 +203,37 @@ void ObjectMotionState::handleEasyChanges(uint32_t& flags) { } } - if (flags & Simulation::DIRTY_LINEAR_VELOCITY) { - btVector3 newLinearVelocity = glmToBullet(getObjectLinearVelocity()); - if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) { - float delta = (newLinearVelocity - _body->getLinearVelocity()).length(); - if (delta > ACTIVATION_LINEAR_VELOCITY_DELTA) { - flags |= Simulation::DIRTY_PHYSICS_ACTIVATION; + if (_body->getCollisionShape()->getShapeType() != TRIANGLE_MESH_SHAPE_PROXYTYPE) { + if (flags & Simulation::DIRTY_LINEAR_VELOCITY) { + btVector3 newLinearVelocity = glmToBullet(getObjectLinearVelocity()); + if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) { + float delta = (newLinearVelocity - _body->getLinearVelocity()).length(); + if (delta > ACTIVATION_LINEAR_VELOCITY_DELTA) { + flags |= Simulation::DIRTY_PHYSICS_ACTIVATION; + } } - } - _body->setLinearVelocity(newLinearVelocity); + _body->setLinearVelocity(newLinearVelocity); - btVector3 newGravity = glmToBullet(getObjectGravity()); - if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) { - float delta = (newGravity - _body->getGravity()).length(); - if (delta > ACTIVATION_GRAVITY_DELTA || - (delta > 0.0f && _body->getGravity().length2() == 0.0f)) { - flags |= Simulation::DIRTY_PHYSICS_ACTIVATION; + btVector3 newGravity = glmToBullet(getObjectGravity()); + if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) { + float delta = (newGravity - _body->getGravity()).length(); + if (delta > ACTIVATION_GRAVITY_DELTA || + (delta > 0.0f && _body->getGravity().length2() == 0.0f)) { + flags |= Simulation::DIRTY_PHYSICS_ACTIVATION; + } } + _body->setGravity(newGravity); } - _body->setGravity(newGravity); - } - if (flags & Simulation::DIRTY_ANGULAR_VELOCITY) { - btVector3 newAngularVelocity = glmToBullet(getObjectAngularVelocity()); - if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) { - float delta = (newAngularVelocity - _body->getAngularVelocity()).length(); - if (delta > ACTIVATION_ANGULAR_VELOCITY_DELTA) { - flags |= Simulation::DIRTY_PHYSICS_ACTIVATION; + if (flags & Simulation::DIRTY_ANGULAR_VELOCITY) { + btVector3 newAngularVelocity = glmToBullet(getObjectAngularVelocity()); + if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) { + float delta = (newAngularVelocity - _body->getAngularVelocity()).length(); + if (delta > ACTIVATION_ANGULAR_VELOCITY_DELTA) { + flags |= Simulation::DIRTY_PHYSICS_ACTIVATION; + } } + _body->setAngularVelocity(newAngularVelocity); } - _body->setAngularVelocity(newAngularVelocity); } if (flags & Simulation::DIRTY_MATERIAL) { From 702e83ba6a8a9d6db90eb4c40caf81caa1a005c4 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 21 Jun 2016 14:03:21 -0700 Subject: [PATCH 105/171] prevent incompatible entity properties combos --- libraries/entities/src/EntityItem.cpp | 50 ++++++++++++++++++--------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 64b6a2c655..2abb9f12e2 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1602,14 +1602,20 @@ void EntityItem::updateMass(float mass) { void EntityItem::updateVelocity(const glm::vec3& value) { glm::vec3 velocity = getLocalVelocity(); if (velocity != value) { - const float MIN_LINEAR_SPEED = 0.001f; - if (glm::length(value) < MIN_LINEAR_SPEED) { - velocity = ENTITY_ITEM_ZERO_VEC3; + if (getShapeType() == SHAPE_TYPE_STATIC_MESH) { + if (velocity != Vectors::ZERO) { + setLocalVelocity(Vectors::ZERO); + } } else { - velocity = value; + const float MIN_LINEAR_SPEED = 0.001f; + if (glm::length(value) < MIN_LINEAR_SPEED) { + velocity = ENTITY_ITEM_ZERO_VEC3; + } else { + velocity = value; + } + setLocalVelocity(velocity); + _dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY; } - setLocalVelocity(velocity); - _dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY; } } @@ -1630,22 +1636,30 @@ void EntityItem::updateDamping(float value) { void EntityItem::updateGravity(const glm::vec3& value) { if (_gravity != value) { - _gravity = value; - _dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY; + if (getShapeType() == SHAPE_TYPE_STATIC_MESH) { + _gravity = Vectors::ZERO; + } else { + _gravity = value; + _dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY; + } } } void EntityItem::updateAngularVelocity(const glm::vec3& value) { glm::vec3 angularVelocity = getLocalAngularVelocity(); if (angularVelocity != value) { - const float MIN_ANGULAR_SPEED = 0.0002f; - if (glm::length(value) < MIN_ANGULAR_SPEED) { - angularVelocity = ENTITY_ITEM_ZERO_VEC3; + if (getShapeType() == SHAPE_TYPE_STATIC_MESH) { + setLocalAngularVelocity(Vectors::ZERO); } else { - angularVelocity = value; + const float MIN_ANGULAR_SPEED = 0.0002f; + if (glm::length(value) < MIN_ANGULAR_SPEED) { + angularVelocity = ENTITY_ITEM_ZERO_VEC3; + } else { + angularVelocity = value; + } + setLocalAngularVelocity(angularVelocity); + _dirtyFlags |= Simulation::DIRTY_ANGULAR_VELOCITY; } - setLocalAngularVelocity(angularVelocity); - _dirtyFlags |= Simulation::DIRTY_ANGULAR_VELOCITY; } } @@ -1680,8 +1694,12 @@ void EntityItem::updateCollisionMask(uint8_t value) { void EntityItem::updateDynamic(bool value) { if (_dynamic != value) { - _dynamic = value; - _dirtyFlags |= Simulation::DIRTY_MOTION_TYPE; + if (getShapeType() == SHAPE_TYPE_STATIC_MESH) { + _dynamic = false; + } else { + _dynamic = value; + _dirtyFlags |= Simulation::DIRTY_MOTION_TYPE; + } } } From f3bad3a63b5df2f2622e539bb02ff2c6f17afa23 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 21 Jun 2016 12:43:02 -0700 Subject: [PATCH 106/171] Don't trigger keyboard on revealing the overlay layer --- interface/resources/qml/desktop/Desktop.qml | 5 ++++ plugins/openvr/src/OpenVrDisplayPlugin.cpp | 2 +- plugins/openvr/src/OpenVrHelpers.cpp | 28 +++++++++++++++++--- plugins/openvr/src/OpenVrHelpers.h | 3 ++- plugins/openvr/src/ViveControllerManager.cpp | 2 +- 5 files changed, 34 insertions(+), 6 deletions(-) diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index 58d1cba1b8..9f10cfc64a 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -297,6 +297,9 @@ FocusScope { onPinnedChanged: { if (pinned) { + nullFocus.focus = true; + nullFocus.forceActiveFocus(); + // recalculate our non-pinned children hiddenChildren = d.findMatchingChildren(desktop, function(child){ return !d.isTopLevelWindow(child) && child.visible && !child.pinned; @@ -478,6 +481,8 @@ FocusScope { FocusHack { id: focusHack; } + FocusScope { id: nullFocus; } + Rectangle { id: focusDebugger; objectName: "focusDebugger" diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 9c4313dc13..4f81f711e3 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -267,7 +267,7 @@ void OpenVrDisplayPlugin::unsuppressKeyboard() { return; } if (1 == _keyboardSupressionCount.fetch_sub(1)) { - enableOpenVrKeyboard(); + enableOpenVrKeyboard(_container); } } diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index 4f02c0384d..1bff0073f6 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -21,6 +21,9 @@ #include #include #include +#include +#include +#include "../../interface/src/Menu.h" Q_DECLARE_LOGGING_CATEGORY(displayplugins) Q_LOGGING_CATEGORY(displayplugins, "hifi.plugins.display") @@ -91,7 +94,7 @@ void releaseOpenVrSystem() { static char textArray[8192]; -static QMetaObject::Connection _focusConnection, _focusTextConnection; +static QMetaObject::Connection _focusConnection, _focusTextConnection, _overlayMenuConnection; extern bool _openVrDisplayActive; static vr::IVROverlay* _overlay { nullptr }; static QObject* _keyboardFocusObject { nullptr }; @@ -99,7 +102,8 @@ static QString _existingText; static Qt::InputMethodHints _currentHints; extern vr::TrackedDevicePose_t _trackedDevicePose[vr::k_unMaxTrackedDeviceCount]; static bool _keyboardShown { false }; -static const uint32_t SHOW_KEYBOARD_DELAY_MS = 100; +static bool _overlayRevealed { false }; +static const uint32_t SHOW_KEYBOARD_DELAY_MS = 400; void showOpenVrKeyboard(bool show = true) { if (!_overlay) { @@ -175,13 +179,25 @@ void finishOpenVrKeyboardInput() { static const QString DEBUG_FLAG("HIFI_DISABLE_STEAM_VR_KEYBOARD"); bool disableSteamVrKeyboard = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); -void enableOpenVrKeyboard() { +void enableOpenVrKeyboard(PluginContainer* container) { if (disableSteamVrKeyboard) { return; } auto offscreenUi = DependencyManager::get(); _overlay = vr::VROverlay(); + + auto menu = container->getPrimaryMenu(); + auto action = menu->getActionForOption(MenuOption::Overlays); + + // When the overlays are revealed, suppress the keyboard from appearing on text focus for a tenth of a second. + _overlayMenuConnection = QObject::connect(action, &QAction::triggered, [action] { + if (action->isChecked()) { + _overlayRevealed = true; + QTimer::singleShot(100, [&] { _overlayRevealed = false; }); + } + }); + _focusConnection = QObject::connect(offscreenUi->getWindow(), &QQuickWindow::focusObjectChanged, [](QObject* object) { if (object != _keyboardFocusObject) { showOpenVrKeyboard(false); @@ -190,6 +206,11 @@ void enableOpenVrKeyboard() { _focusTextConnection = QObject::connect(offscreenUi.data(), &OffscreenUi::focusTextChanged, [](bool focusText) { if (_openVrDisplayActive) { + if (_overlayRevealed) { + // suppress at most one text focus event + _overlayRevealed = false; + return; + } showOpenVrKeyboard(focusText); } }); @@ -200,6 +221,7 @@ void disableOpenVrKeyboard() { if (disableSteamVrKeyboard) { return; } + QObject::disconnect(_overlayMenuConnection); QObject::disconnect(_focusTextConnection); QObject::disconnect(_focusConnection); } diff --git a/plugins/openvr/src/OpenVrHelpers.h b/plugins/openvr/src/OpenVrHelpers.h index 131db517ab..19c9cbfff5 100644 --- a/plugins/openvr/src/OpenVrHelpers.h +++ b/plugins/openvr/src/OpenVrHelpers.h @@ -13,6 +13,7 @@ #include #include +#include bool openVrSupported(); @@ -20,7 +21,7 @@ vr::IVRSystem* acquireOpenVrSystem(); void releaseOpenVrSystem(); void handleOpenVrEvents(); bool openVrQuitRequested(); -void enableOpenVrKeyboard(); +void enableOpenVrKeyboard(PluginContainer* container); void disableOpenVrKeyboard(); bool isOpenVrKeyboardShown(); diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 7f78ab8553..6c272d2a27 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -63,7 +63,7 @@ bool ViveControllerManager::activate() { } Q_ASSERT(_system); - enableOpenVrKeyboard(); + enableOpenVrKeyboard(_container); // OpenVR provides 3d mesh representations of the controllers // Disabled controller rendering code From 9ca27f267d542d5fbe4ead577d3f8c4a4a3259b4 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 21 Jun 2016 16:48:04 -0700 Subject: [PATCH 107/171] cap lifetime rather than reject edits from nodes which only have tmp-rez rights --- .../resources/describe-settings.json | 6 +- libraries/entities/src/EntityTree.cpp | 61 +++++++++---------- libraries/entities/src/EntityTree.h | 1 - scripts/system/edit.js | 2 +- 4 files changed, 32 insertions(+), 38 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 59fa9c4f3d..7375a0f650 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -384,7 +384,7 @@ "name": "standard_permissions", "type": "table", "label": "Domain-Wide User Permissions", - "help": "Indicate which users or groups can have which domain-wide permissions.", + "help": "Indicate which users or groups can have which domain-wide permissions.", "caption": "Standard Permissions", "can_add_new_rows": false, @@ -394,7 +394,7 @@ "span": 1 }, { - "label": "Permissions ?", + "label": "Permissions ?", "span": 6 } ], @@ -463,7 +463,7 @@ "span": 1 }, { - "label": "Permissions ?", + "label": "Permissions ?", "span": 6 } ], diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index ec1f8a50bc..820d97c915 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -148,11 +148,11 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI return false; } - if (!canRezPermanentEntities && (entity->getLifetime() != properties.getLifetime())) { - // we don't allow a Node that can't create permanent entities to adjust lifetimes on existing ones - if (properties.lifetimeChanged()) { - qCDebug(entities) << "Refusing disallowed entity lifetime adjustment."; - return false; + if (!canRezPermanentEntities) { + // we don't allow a Node that can't create permanent entities to raise lifetimes on existing ones + if (properties.getLifetime() == ENTITY_ITEM_IMMORTAL_LIFETIME || properties.getLifetime() > _maxTmpEntityLifetime) { + qCDebug(entities) << "Capping disallowed entity lifetime adjustment."; + properties.setLifetime(_maxTmpEntityLifetime); } } @@ -321,26 +321,9 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI return true; } -bool EntityTree::permissionsAllowRez(const EntityItemProperties& properties, bool canRez, bool canRezTmp) { - float lifeTime = properties.getLifetime(); - - if (lifeTime == ENTITY_ITEM_IMMORTAL_LIFETIME || lifeTime > _maxTmpEntityLifetime) { - // this is an attempt to rez a permanent or non-temporary entity. - if (!canRez) { - return false; - } - } else { - // this is an attempt to rez a temporary entity. - if (!canRezTmp) { - return false; - } - } - - return true; -} - EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer result = NULL; + EntityItemProperties props = properties; auto nodeList = DependencyManager::get(); if (!nodeList) { @@ -348,16 +331,19 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti return nullptr; } - bool clientOnly = properties.getClientOnly(); + bool clientOnly = props.getClientOnly(); - if (!clientOnly && getIsClient() && - !permissionsAllowRez(properties, nodeList->getThisNodeCanRez(), nodeList->getThisNodeCanRezTmp())) { - // if our Node isn't allowed to create entities in this domain, don't try. - return nullptr; + if (!clientOnly && getIsClient() && !nodeList->getThisNodeCanRez() && nodeList->getThisNodeCanRezTmp()) { + // we are a client which is only allowed to rez temporary entities. cap the lifetime. + if (props.getLifetime() == ENTITY_ITEM_IMMORTAL_LIFETIME) { + props.setLifetime(_maxTmpEntityLifetime); + } else { + props.setLifetime(glm::min(props.getLifetime(), _maxTmpEntityLifetime)); + } } bool recordCreationTime = false; - if (properties.getCreated() == UNKNOWN_CREATED_TIME) { + if (props.getCreated() == UNKNOWN_CREATED_TIME) { // the entity's creation time was not specified in properties, which means this is a NEW entity // and we must record its creation time recordCreationTime = true; @@ -372,8 +358,8 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti } // construct the instance of the entity - EntityTypes::EntityType type = properties.getType(); - result = EntityTypes::constructEntityItem(type, entityID, properties); + EntityTypes::EntityType type = props.getType(); + result = EntityTypes::constructEntityItem(type, entityID, props); if (result) { if (recordCreationTime) { @@ -922,11 +908,20 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c EntityItemID entityItemID; EntityItemProperties properties; startDecode = usecTimestampNow(); - + bool validEditPacket = EntityItemProperties::decodeEntityEditPacket(editData, maxLength, processedBytes, entityItemID, properties); endDecode = usecTimestampNow(); + if (!senderNode->getCanRez() && senderNode->getCanRezTmp()) { + // this node is only allowed to rez temporary entities. cap the lifetime. + if (properties.getLifetime() == ENTITY_ITEM_IMMORTAL_LIFETIME) { + properties.setLifetime(_maxTmpEntityLifetime); + } else { + properties.setLifetime(glm::min(properties.getLifetime(), _maxTmpEntityLifetime)); + } + } + // If we got a valid edit packet, then it could be a new entity or it could be an update to // an existing entity... handle appropriately if (validEditPacket) { @@ -955,7 +950,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c endUpdate = usecTimestampNow(); _totalUpdates++; } else if (message.getType() == PacketType::EntityAdd) { - if (permissionsAllowRez(properties, senderNode->getCanRez(), senderNode->getCanRezTmp())) { + if (senderNode->getCanRez() || senderNode->getCanRezTmp()) { // this is a new entity... assign a new entityID properties.setCreated(properties.getLastEdited()); startCreate = usecTimestampNow(); diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 8afb8d878f..15daf3bf3c 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -64,7 +64,6 @@ public: void setEntityMaxTmpLifetime(float maxTmpEntityLifetime) { _maxTmpEntityLifetime = maxTmpEntityLifetime; } - bool permissionsAllowRez(const EntityItemProperties& properties, bool canRez, bool canRezTmp); /// Implements our type specific root element factory virtual OctreeElementPointer createNewElement(unsigned char* octalCode = NULL) override; diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 42eddf11c3..b439de2c9d 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -332,7 +332,7 @@ var toolBar = (function() { that.setActive = function(active) { if (active != isActive) { - if (active && !Entities.canAdjustLocks()) { + if (active && !Entities.canRez() && !Entities.canRezTmp()) { Window.alert(INSUFFICIENT_PERMISSIONS_ERROR_MSG); } else { Messages.sendLocalMessage("edit-events", JSON.stringify({ From f0b78eae478e69cbcefe78b1efc9ef75595f924f Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 21 Jun 2016 16:48:32 -0700 Subject: [PATCH 108/171] fix mini mirror --- interface/src/ui/ApplicationOverlay.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 41d249b635..9330f1f6c4 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -169,8 +169,10 @@ void ApplicationOverlay::renderRearView(RenderArgs* renderArgs) { float renderRatio = ((float)qApp->getRenderResolutionScale()); auto viewport = qApp->getMirrorViewRect(); - glm::vec2 bottomLeft(viewport.left(), viewport.top() + screenRatio * viewport.height()); - glm::vec2 topRight(viewport.left() + screenRatio * viewport.width(), viewport.top()); + glm::vec2 bottomLeft(viewport.left(), viewport.top() + viewport.height()); + glm::vec2 topRight(viewport.left() + viewport.width(), viewport.top()); + bottomLeft *= screenRatio; + topRight *= screenRatio; glm::vec2 texCoordMinCorner(0.0f, 0.0f); glm::vec2 texCoordMaxCorner(viewport.width() * renderRatio / float(selfieTexture->getWidth()), viewport.height() * renderRatio / float(selfieTexture->getHeight())); From 223f9bda2e6dcf92487ab4dac4cd3b44a762eef7 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Tue, 21 Jun 2016 17:03:49 -0700 Subject: [PATCH 109/171] gratuitous change to force things. --- scripts/system/controllers/handControllerPointer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index c94325d3c0..374be0d1a1 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -1,6 +1,6 @@ "use strict"; /*jslint vars: true, plusplus: true*/ -/*globals Script, Overlays, Controller, Reticle, HMD, Camera, Entities, MyAvatar, Settings, Menu, ScriptDiscoveryService, Window, Vec3, Quat, print */ +/*globals Script, Overlays, Controller, Reticle, HMD, Camera, Entities, MyAvatar, Settings, Menu, ScriptDiscoveryService, Window, Vec3, Quat, print*/ // // handControllerPointer.js From 2a82dddc2b019e53e2b6cfc1b9a7d9f2eb8932d7 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 21 Jun 2016 17:36:36 -0700 Subject: [PATCH 110/171] Draw attach points as if they were equip-hotspots --- .../system/controllers/handControllerGrab.js | 17 ++++++-- scripts/system/libraries/Xform.js | 40 +++++++++++++++++++ 2 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 scripts/system/libraries/Xform.js diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 39d85f1224..31f8ebd73f 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -10,9 +10,10 @@ // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, Reticle, Messages, setEntityCustomData, getEntityCustomData, vec3toStr */ +/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, Reticle, Messages, setEntityCustomData, getEntityCustomData, vec3toStr, Xform */ Script.include("/~/system/libraries/utils.js"); +Script.include("../libraries/Xform.js"); // // add lines where the hand ray picking is happening @@ -879,21 +880,29 @@ function MyController(hand) { var entities = Entities.findEntities(MyAvatar.position, HOTSPOT_DRAW_DISTANCE); var i, l = entities.length; for (i = 0; i < l; i++) { - var grabProps = Entities.getEntityProperties(entities[i], GRABBABLE_PROPERTIES); + var props = Entities.getEntityProperties(entities[i], GRABBABLE_PROPERTIES); // does this entity have an attach point? var wearableData = getEntityCustomData("wearable", entities[i], undefined); if (wearableData && wearableData.joints) { var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"; if (wearableData.joints[handJointName]) { + var handOffsetPos = wearableData.joints[handJointName][0]; + var handOffsetRot = wearableData.joints[handJointName][1]; + + var handOffsetXform = new Xform(handOffsetRot, handOffsetPos); + var objectXform = new Xform(props.rotation, props.position); + var overlayXform = Xform.mul(objectXform, handOffsetXform.inv()); + // draw the hotspot this.equipHotspotOverlays.push(Overlays.addOverlay("sphere", { - position: grabProps.position, + rotation: overlayXform.rot, + position: overlayXform.pos, size: 0.2, color: { red: 90, green: 255, blue: 90 }, alpha: 0.7, solid: true, visible: true, - ignoreRayIntersection: false, + ignoreRayIntersection: true, drawInFront: false })); } diff --git a/scripts/system/libraries/Xform.js b/scripts/system/libraries/Xform.js new file mode 100644 index 0000000000..75051c4983 --- /dev/null +++ b/scripts/system/libraries/Xform.js @@ -0,0 +1,40 @@ +// +// Created by Anthony J. Thibault on 2016/06/21 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// ctor +Xform = function(rot, pos) { + this.rot = rot; + this.pos = pos; +} + +Xform.ident = function() { + return new Xform({x: 0, y: 0, z: 0, w: 1}, {x: 0, y: 0, z: 0}); +}; + +Xform.mul = function(lhs, rhs) { + var rot = Quat.multiply(lhs.rot, rhs.rot); + var pos = Vec3.sum(lhs.pos, Vec3.multiplyQbyV(lhs.rot, rhs.pos)); + return new Xform(rot, pos); +}; + +Xform.prototype.inv = function() { + var invRot = Quat.inverse(this.rot); + var invPos = Vec3.multiply(-1, this.pos); + return new Xform(invRot, Vec3.multiplyQbyV(invRot, invPos)); +}; + +Xform.prototype.mirrorX = function() { + return new Xform({x: this.rot.x, y: -this.rot.y, z: -this.rot.z, w: this.rot.w}, + {x: -this.pos.x, y: this.pos.y, z: this.pos.z}); +}; + +Xform.prototype.toString = function() { + var rot = this.rot; + var pos = this.pos; + return "Xform rot = (" + rot.x + ", " + rot.y + ", " + rot.z + ", " + rot.w + "), pos = (" + pos.x + ", " + pos.y + ", " + pos.z + ")"; +}; From a1844d8c19ab2d28ffc2870829bfc424c57aadb0 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Tue, 21 Jun 2016 17:36:41 -0700 Subject: [PATCH 111/171] Fix plugin include. (How did this build for me locally?) --- plugins/openvr/src/OpenVrHelpers.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index 1bff0073f6..436cd7fc30 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -21,7 +21,7 @@ #include #include #include -#include +#include #include #include "../../interface/src/Menu.h" @@ -347,4 +347,4 @@ controller::Pose openVrControllerPoseToHandPose(bool isLeftHand, const mat4& mat result.velocity = linearVelocity + glm::cross(angularVelocity, position - extractTranslation(mat)); result.angularVelocity = angularVelocity; return result; -} \ No newline at end of file +} From 78a1845afe63c23c41a6f52b203b490b6955c9f6 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 21 Jun 2016 18:13:11 -0700 Subject: [PATCH 112/171] fixed resizing of qml overlays when device pixel ratio changes --- interface/src/Application.cpp | 11 +++-------- libraries/gl/src/gl/OffscreenQmlSurface.cpp | 4 ++-- libraries/gl/src/gl/OffscreenQmlSurface.h | 2 +- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 9bcd85fd02..cff36dcf99 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1459,13 +1459,7 @@ void Application::initializeUi() { }); offscreenUi->resume(); connect(_window, &MainWindow::windowGeometryChanged, [this](const QRect& r){ - static qreal oldDevicePixelRatio = 0; - qreal devicePixelRatio = getActiveDisplayPlugin()->devicePixelRatio(); - if (devicePixelRatio != oldDevicePixelRatio) { - oldDevicePixelRatio = devicePixelRatio; - qDebug() << "Device pixel ratio changed, triggering GL resize"; - resizeGL(); - } + resizeGL(); }); // This will set up the input plugins UI @@ -1840,7 +1834,8 @@ void Application::resizeGL() { static qreal lastDevicePixelRatio = 0; qreal devicePixelRatio = _window->devicePixelRatio(); if (offscreenUi->size() != fromGlm(uiSize) || devicePixelRatio != lastDevicePixelRatio) { - offscreenUi->resize(fromGlm(uiSize)); + qDebug() << "Device pixel ratio changed, triggering resize"; + offscreenUi->resize(fromGlm(uiSize), true); _offscreenContext->makeCurrent(); lastDevicePixelRatio = devicePixelRatio; } diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.cpp b/libraries/gl/src/gl/OffscreenQmlSurface.cpp index 388ca26482..9ba4b5e134 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.cpp +++ b/libraries/gl/src/gl/OffscreenQmlSurface.cpp @@ -414,7 +414,7 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) { _updateTimer.start(); } -void OffscreenQmlSurface::resize(const QSize& newSize_) { +void OffscreenQmlSurface::resize(const QSize& newSize_, bool forceResize) { if (!_renderer || !_renderer->_quickWindow) { return; @@ -433,7 +433,7 @@ void OffscreenQmlSurface::resize(const QSize& newSize_) { } QSize currentSize = _renderer->_quickWindow->geometry().size(); - if (newSize == currentSize) { + if (newSize == currentSize && !forceResize) { return; } diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.h b/libraries/gl/src/gl/OffscreenQmlSurface.h index 22a1b99fe6..4014f2ff25 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.h +++ b/libraries/gl/src/gl/OffscreenQmlSurface.h @@ -38,7 +38,7 @@ public: using MouseTranslator = std::function; virtual void create(QOpenGLContext* context); - void resize(const QSize& size); + void resize(const QSize& size, bool forceResize = false); QSize size() const; Q_INVOKABLE QObject* load(const QUrl& qmlSource, std::function f = [](QQmlContext*, QObject*) {}); Q_INVOKABLE QObject* load(const QString& qmlSourceFile, std::function f = [](QQmlContext*, QObject*) {}) { From e88b264864807d7f8518ded3c447e8fe2f1fb64e Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 22 Jun 2016 09:30:44 -0700 Subject: [PATCH 113/171] revert compound hull shape generation --- .../src/RenderableModelEntityItem.cpp | 25 ++++--------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index ab91edd294..366e365107 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -618,7 +618,6 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { // to find one actual "mesh" (with one or more meshParts in it), but we loop over the meshes, just in case. const uint32_t TRIANGLE_STRIDE = 3; const uint32_t QUAD_STRIDE = 4; - Extents extents; foreach (const FBXMesh& mesh, collisionGeometry.meshes) { // each meshPart is a convex hull foreach (const FBXMeshPart &meshPart, mesh.parts) { @@ -632,18 +631,14 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { glm::vec3 p0 = mesh.vertices[meshPart.triangleIndices[j]]; glm::vec3 p1 = mesh.vertices[meshPart.triangleIndices[j + 1]]; glm::vec3 p2 = mesh.vertices[meshPart.triangleIndices[j + 2]]; - if (!pointsInPart.contains(p0)) { pointsInPart << p0; - extents.addPoint(p0); } if (!pointsInPart.contains(p1)) { pointsInPart << p1; - extents.addPoint(p1); } if (!pointsInPart.contains(p2)) { pointsInPart << p2; - extents.addPoint(p2); } } @@ -657,19 +652,15 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { glm::vec3 p3 = mesh.vertices[meshPart.quadIndices[j + 3]]; if (!pointsInPart.contains(p0)) { pointsInPart << p0; - extents.addPoint(p0); } if (!pointsInPart.contains(p1)) { pointsInPart << p1; - extents.addPoint(p1); } if (!pointsInPart.contains(p2)) { pointsInPart << p2; - extents.addPoint(p2); } if (!pointsInPart.contains(p3)) { pointsInPart << p3; - extents.addPoint(p3); } } @@ -682,29 +673,23 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { } } - glm::vec3 extentsSize = extents.size(); - glm::vec3 scaleToFit = dimensions / extentsSize; - for (int i = 0; i < 3; ++i) { - if (extentsSize[i] < 1.0e-6f) { - scaleToFit[i] = 1.0f; - } - } - // We expect that the collision model will have the same units and will be displaced // from its origin in the same way the visual model is. The visual model has // been centered and probably scaled. We take the scaling and offset which were applied // to the visual model and apply them to the collision model (without regard for the // collision model's extents). + glm::vec3 scaleToFit = dimensions / _model->getFBXGeometry().getUnscaledMeshExtents().size(); // multiply each point by scale before handing the point-set off to the physics engine. // also determine the extents of the collision model. - glm::vec3 scaledModelOffset = _model->getOffset() * _model->getScale(); for (int i = 0; i < pointCollection.size(); i++) { for (int j = 0; j < pointCollection[i].size(); j++) { - pointCollection[i][j] = (pointCollection[i][j] * scaleToFit) + scaledModelOffset; + // compensate for registration + pointCollection[i][j] += _model->getOffset(); + // scale so the collision points match the model points + pointCollection[i][j] *= scaleToFit; } } - info.setParams(type, dimensions, _compoundShapeURL); } else if (type == SHAPE_TYPE_STATIC_MESH) { // compute meshPart local transforms From e4dbb5e61a8ff9fd69f9a78039bb8ec95eb51f61 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 22 Jun 2016 09:57:38 -0700 Subject: [PATCH 114/171] Make mouse reticle be perpendicular to head. --- .../display-plugins/src/display-plugins/CompositorHelper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp index a17fe2fd49..032350a07c 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp @@ -425,7 +425,7 @@ glm::mat4 CompositorHelper::getReticleTransform(const glm::mat4& eyePose, const d = glm::normalize(overlaySurfacePoint); } reticlePosition = headPosition + (d * getReticleDepth()); - quat reticleOrientation = quat(vec3(-spherical.y, spherical.x, 0.0f)); + quat reticleOrientation = glm::quat_cast(_currentDisplayPlugin->getHeadPose()); vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize * getReticleDepth()); return glm::inverse(eyePose) * createMatFromScaleQuatAndPos(reticleScale, reticleOrientation, reticlePosition); } else { From c62b73bf61e14d745f9ce15125999442c9578d2c Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 22 Jun 2016 11:42:57 -0700 Subject: [PATCH 115/171] when the entity-server caps a lifetime of an edit from a tmp-rez only interface, bump the lastEdited time forward so that the interface accepts the change back into its local tree --- libraries/entities/src/EntityTree.cpp | 40 ++++++++++----------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 820d97c915..ad0f929e59 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -130,16 +130,13 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI EntityItemProperties properties = origProperties; bool allowLockChange; - bool canRezPermanentEntities; QUuid senderID; if (senderNode.isNull()) { auto nodeList = DependencyManager::get(); allowLockChange = nodeList->isAllowedEditor(); - canRezPermanentEntities = nodeList->getThisNodeCanRez(); senderID = nodeList->getSessionUUID(); } else { allowLockChange = senderNode->isAllowedEditor(); - canRezPermanentEntities = senderNode->getCanRez(); senderID = senderNode->getUUID(); } @@ -148,14 +145,6 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI return false; } - if (!canRezPermanentEntities) { - // we don't allow a Node that can't create permanent entities to raise lifetimes on existing ones - if (properties.getLifetime() == ENTITY_ITEM_IMMORTAL_LIFETIME || properties.getLifetime() > _maxTmpEntityLifetime) { - qCDebug(entities) << "Capping disallowed entity lifetime adjustment."; - properties.setLifetime(_maxTmpEntityLifetime); - } - } - // enforce support for locked entities. If an entity is currently locked, then the only // property we allow you to change is the locked property. if (entity->getLocked()) { @@ -331,17 +320,6 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti return nullptr; } - bool clientOnly = props.getClientOnly(); - - if (!clientOnly && getIsClient() && !nodeList->getThisNodeCanRez() && nodeList->getThisNodeCanRezTmp()) { - // we are a client which is only allowed to rez temporary entities. cap the lifetime. - if (props.getLifetime() == ENTITY_ITEM_IMMORTAL_LIFETIME) { - props.setLifetime(_maxTmpEntityLifetime); - } else { - props.setLifetime(glm::min(props.getLifetime(), _maxTmpEntityLifetime)); - } - } - bool recordCreationTime = false; if (props.getCreated() == UNKNOWN_CREATED_TIME) { // the entity's creation time was not specified in properties, which means this is a NEW entity @@ -876,6 +854,13 @@ void EntityTree::fixupTerseEditLogging(EntityItemProperties& properties, QList= 0) { + float value = properties.getLifetime(); + changedProperties[index] = QString("lifetime:") + QString::number((int)value); + } + } } int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength, @@ -913,12 +898,15 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c entityItemID, properties); endDecode = usecTimestampNow(); + const quint64 LAST_EDITED_SERVERSIDE_BUMP = 10000; // usec if (!senderNode->getCanRez() && senderNode->getCanRezTmp()) { - // this node is only allowed to rez temporary entities. cap the lifetime. - if (properties.getLifetime() == ENTITY_ITEM_IMMORTAL_LIFETIME) { + // this node is only allowed to rez temporary entities. if need be, cap the lifetime. + if (properties.getLifetime() == ENTITY_ITEM_IMMORTAL_LIFETIME || + properties.getLifetime() > _maxTmpEntityLifetime) { properties.setLifetime(_maxTmpEntityLifetime); - } else { - properties.setLifetime(glm::min(properties.getLifetime(), _maxTmpEntityLifetime)); + // also bump up the lastEdited time of the properties so that the interface that created this edit + // will accept our adjustment to lifetime back into its own entity-tree. + properties.setLastEdited(properties.getLastEdited() + LAST_EDITED_SERVERSIDE_BUMP); } } From b6b73af2b487b10e9cabe85299f498ef0b683df2 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Fri, 17 Jun 2016 14:27:45 -0700 Subject: [PATCH 116/171] Clean up domain-server setup --- domain-server/src/DomainServer.cpp | 70 +++++++++++++----------------- domain-server/src/DomainServer.h | 2 +- 2 files changed, 32 insertions(+), 40 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 223cab61da..a4b57226ed 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -104,23 +104,25 @@ DomainServer::DomainServer(int argc, char* argv[]) : connect(&_settingsManager, &DomainServerSettingsManager::updateNodePermissions, &_gatekeeper, &DomainGatekeeper::updateNodePermissions); - if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth()) { - // we either read a certificate and private key or were not passed one - // and completed login or did not need to - - qDebug() << "Setting up LimitedNodeList and assignments."; - setupNodeListAndAssignments(); - - // setup automatic networking settings with data server - setupAutomaticNetworking(); - - // preload some user public keys so they can connect on first request - _gatekeeper.preloadAllowedUserPublicKeys(); - - optionallyGetTemporaryName(args); + // if we were given a certificate/private key or oauth credentials they must succeed + if (!(optionallyReadX509KeyAndCertificate() && optionallySetupOAuth())) { + return; } + qDebug() << "Setting up domain-server"; + setupNodeListAndAssignments(); + setupAutomaticNetworking(); + _gatekeeper.preloadAllowedUserPublicKeys(); // so they can connect on first request + _metadata = new DomainMetadata(this); + + // check for the temporary name parameter + const QString GET_TEMPORARY_NAME_SWITCH = "--get-temp-name"; + if (args.contains(GET_TEMPORARY_NAME_SWITCH)) { + getTemporaryName(); + } + + qDebug() << "domain-server" << nullptr << "is running"; } DomainServer::~DomainServer() { @@ -233,34 +235,25 @@ bool DomainServer::optionallySetupOAuth() { static const QString METAVERSE_DOMAIN_ID_KEY_PATH = "metaverse.id"; -void DomainServer::optionallyGetTemporaryName(const QStringList& arguments) { - // check for the temporary name parameter - const QString GET_TEMPORARY_NAME_SWITCH = "--get-temp-name"; +void DomainServer::getTemporaryName(bool force) { + // check if we already have a domain ID + const QVariant* idValueVariant = valueForKeyPath(_settingsManager.getSettingsMap(), METAVERSE_DOMAIN_ID_KEY_PATH); - if (arguments.contains(GET_TEMPORARY_NAME_SWITCH)) { - - // make sure we don't already have a domain ID - const QVariant* idValueVariant = valueForKeyPath(_settingsManager.getSettingsMap(), METAVERSE_DOMAIN_ID_KEY_PATH); - if (idValueVariant) { - qWarning() << "Temporary domain name requested but a domain ID is already present in domain-server settings." - << "Will not request temporary name."; + if (idValueVariant) { + qWarning() << "Temporary domain name requested but a domain ID is already present in domain-server settings."; + if (force) { + qWarning() << "Temporary domain name will be requested to replace it."; + } else { + qWarning() << "Temporary domain name will not be requested."; return; } - - // we've been asked to grab a temporary name from the API - // so fire off that request now - auto accountManager = DependencyManager::get(); - - // get callbacks for temporary domain result - JSONCallbackParameters callbackParameters; - callbackParameters.jsonCallbackReceiver = this; - callbackParameters.jsonCallbackMethod = "handleTempDomainSuccess"; - callbackParameters.errorCallbackReceiver = this; - callbackParameters.errorCallbackMethod = "handleTempDomainError"; - - accountManager->sendRequest("/api/v1/domains/temporary", AccountManagerAuth::None, - QNetworkAccessManager::PostOperation, callbackParameters); } + + // request a temporary name from the metaverse + auto accountManager = DependencyManager::get(); + JSONCallbackParameters callbackParameters { this, "handleTempDomainSuccess", this, "handleTempDomainError" }; + accountManager->sendRequest("/api/v1/domains/temporary", AccountManagerAuth::None, + QNetworkAccessManager::PostOperation, callbackParameters); } void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) { @@ -333,7 +326,6 @@ bool DomainServer::packetVersionMatch(const udt::Packet& packet) { void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { - const QString CUSTOM_LOCAL_PORT_OPTION = "metaverse.local_port"; QVariant localPortValue = _settingsManager.valueOrDefaultValueForKeyPath(CUSTOM_LOCAL_PORT_OPTION); diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index c742dbc9b3..8a25591605 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -100,7 +100,7 @@ private: bool optionallySetupOAuth(); bool optionallyReadX509KeyAndCertificate(); - void optionallyGetTemporaryName(const QStringList& arguments); + void getTemporaryName(bool force = false); static bool packetVersionMatch(const udt::Packet& packet); From 3a36b0a2e5307094c106c73d4ddfb3cb5a289838 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Fri, 17 Jun 2016 18:31:23 -0700 Subject: [PATCH 117/171] add temporary domain info to account info --- libraries/networking/src/AccountManager.cpp | 5 +++++ libraries/networking/src/AccountManager.h | 3 +++ libraries/networking/src/DataServerAccountInfo.cpp | 13 ++++++++++--- libraries/networking/src/DataServerAccountInfo.h | 8 +++++++- 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 26b3801ec1..ac6d0cd4a0 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -471,6 +471,11 @@ void AccountManager::setAccessTokenForCurrentAuthURL(const QString& accessToken) persistAccountToFile(); } +void AccountManager::setTemporaryDomain(const QUuid& domainID, const QString& key) { + _accountInfo.setTemporaryDomain(domainID, key); + persistAccountToFile(); +} + void AccountManager::requestAccessToken(const QString& login, const QString& password) { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 4803d2625f..dc3315eb45 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -88,6 +88,9 @@ public: void setSessionID(const QUuid& sessionID) { _sessionID = sessionID; } + void setTemporaryDomain(const QUuid& domainID, const QString& key); + const QString& getTemporaryDomainKey(const QUuid& domainID) { return _accountInfo.getTemporaryDomainKey(domainID); } + public slots: void requestAccessToken(const QString& login, const QString& password); diff --git a/libraries/networking/src/DataServerAccountInfo.cpp b/libraries/networking/src/DataServerAccountInfo.cpp index 211bfdccfa..6c6f3eb90c 100644 --- a/libraries/networking/src/DataServerAccountInfo.cpp +++ b/libraries/networking/src/DataServerAccountInfo.cpp @@ -25,6 +25,8 @@ #pragma clang diagnostic ignored "-Wdeprecated-declarations" #endif +const QString DataServerAccountInfo::EMPTY_KEY = QString(); + DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherInfo) : QObject() { _accessToken = otherInfo._accessToken; _username = otherInfo._username; @@ -33,6 +35,8 @@ DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherI _walletID = otherInfo._walletID; _privateKey = otherInfo._privateKey; _domainID = otherInfo._domainID; + _temporaryDomainID = otherInfo._temporaryDomainID; + _temporaryDomainApiKey = otherInfo._temporaryDomainApiKey; } DataServerAccountInfo& DataServerAccountInfo::operator=(const DataServerAccountInfo& otherInfo) { @@ -51,6 +55,8 @@ void DataServerAccountInfo::swap(DataServerAccountInfo& otherInfo) { swap(_walletID, otherInfo._walletID); swap(_privateKey, otherInfo._privateKey); swap(_domainID, otherInfo._domainID); + swap(_temporaryDomainID, otherInfo._temporaryDomainID); + swap(_temporaryDomainApiKey, otherInfo._temporaryDomainApiKey); } void DataServerAccountInfo::setAccessTokenFromJSON(const QJsonObject& jsonObject) { @@ -145,13 +151,14 @@ QByteArray DataServerAccountInfo::signPlaintext(const QByteArray& plaintext) { QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info) { out << info._accessToken << info._username << info._xmppPassword << info._discourseApiKey - << info._walletID << info._privateKey << info._domainID; - + << info._walletID << info._privateKey << info._domainID + << info._temporaryDomainID << info._temporaryDomainApiKey; return out; } QDataStream& operator>>(QDataStream &in, DataServerAccountInfo& info) { in >> info._accessToken >> info._username >> info._xmppPassword >> info._discourseApiKey - >> info._walletID >> info._privateKey >> info._domainID; + >> info._walletID >> info._privateKey >> info._domainID + >> info._temporaryDomainID >> info._temporaryDomainApiKey; return in; } diff --git a/libraries/networking/src/DataServerAccountInfo.h b/libraries/networking/src/DataServerAccountInfo.h index 6ee726efde..8cb416cf34 100644 --- a/libraries/networking/src/DataServerAccountInfo.h +++ b/libraries/networking/src/DataServerAccountInfo.h @@ -22,6 +22,7 @@ const float SATOSHIS_PER_CREDIT = 100000000.0f; class DataServerAccountInfo : public QObject { Q_OBJECT + const static QString EMPTY_KEY; public: DataServerAccountInfo() {}; DataServerAccountInfo(const DataServerAccountInfo& otherInfo); @@ -52,6 +53,9 @@ public: void setDomainID(const QUuid& domainID) { _domainID = domainID; } const QUuid& getDomainID() const { return _domainID; } + void setTemporaryDomain(const QUuid& domainID, const QString& key) { _temporaryDomainID = domainID; _temporaryDomainApiKey = key; } + const QString& getTemporaryDomainKey(const QUuid& domainID) { return domainID == _temporaryDomainID ? _temporaryDomainApiKey : EMPTY_KEY; } + bool hasProfile() const; void setProfileInfoFromJSON(const QJsonObject& jsonObject); @@ -67,7 +71,9 @@ private: QString _xmppPassword; QString _discourseApiKey; QUuid _walletID; - QUuid _domainID; // if this holds account info for a domain, this holds the ID of that domain + QUuid _domainID; + QUuid _temporaryDomainID; + QString _temporaryDomainApiKey; QByteArray _privateKey; }; From eebf8e91c6bdcca2308e64b75a4b8ee97b1b970b Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Fri, 17 Jun 2016 19:02:27 -0700 Subject: [PATCH 118/171] add api_key to domains/public_key updates --- libraries/networking/src/AccountManager.cpp | 27 +++++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index ac6d0cd4a0..38f33da6ce 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -652,22 +652,33 @@ void AccountManager::processGeneratedKeypair() { const QString DOMAIN_PUBLIC_KEY_UPDATE_PATH = "api/v1/domains/%1/public_key"; QString uploadPath; - if (keypairGenerator->getDomainID().isNull()) { + const auto& domainID = keypairGenerator->getDomainID(); + if (domainID.isNull()) { uploadPath = USER_PUBLIC_KEY_UPDATE_PATH; } else { - uploadPath = DOMAIN_PUBLIC_KEY_UPDATE_PATH.arg(uuidStringWithoutCurlyBraces(keypairGenerator->getDomainID())); + uploadPath = DOMAIN_PUBLIC_KEY_UPDATE_PATH.arg(uuidStringWithoutCurlyBraces(domainID)); } // setup a multipart upload to send up the public key QHttpMultiPart* requestMultiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); - QHttpPart keyPart; - keyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); - keyPart.setHeader(QNetworkRequest::ContentDispositionHeader, - QVariant("form-data; name=\"public_key\"; filename=\"public_key\"")); - keyPart.setBody(keypairGenerator->getPublicKey()); + QHttpPart publicKeyPart; + publicKeyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); - requestMultiPart->append(keyPart); + publicKeyPart.setHeader(QNetworkRequest::ContentDispositionHeader, + QVariant("form-data; name=\"public_key\"; filename=\"public_key\"")); + publicKeyPart.setBody(keypairGenerator->getPublicKey()); + requestMultiPart->append(publicKeyPart); + + if (!domainID.isNull()) { + const auto& key = getTemporaryDomainKey(domainID); + QHttpPart apiKeyPart; + publicKeyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); + apiKeyPart.setHeader(QNetworkRequest::ContentDispositionHeader, + QVariant("form-data; name=\"api_key\"")); + apiKeyPart.setBody(key.toUtf8()); + requestMultiPart->append(apiKeyPart); + } // setup callback parameters so we know once the keypair upload has succeeded or failed JSONCallbackParameters callbackParameters; From a6115cba6e981f17477edd98a0b6695770cc5c19 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Fri, 17 Jun 2016 19:04:03 -0700 Subject: [PATCH 119/171] update temporary domains to use api_key --- domain-server/src/DomainServer.cpp | 149 +++++++++++++++++++---------- domain-server/src/DomainServer.h | 10 +- 2 files changed, 105 insertions(+), 54 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index a4b57226ed..df1697af28 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -76,6 +76,8 @@ DomainServer::DomainServer(int argc, char* argv[]) : setApplicationVersion(BuildInfo::VERSION); QSettings::setDefaultFormat(QSettings::IniFormat); + qDebug() << "Setting up domain-server"; + // make sure we have a fresh AccountManager instance // (need this since domain-server can restart itself and maintain static variables) DependencyManager::set(); @@ -109,20 +111,26 @@ DomainServer::DomainServer(int argc, char* argv[]) : return; } - qDebug() << "Setting up domain-server"; - setupNodeListAndAssignments(); - setupAutomaticNetworking(); - _gatekeeper.preloadAllowedUserPublicKeys(); // so they can connect on first request - - _metadata = new DomainMetadata(this); - // check for the temporary name parameter const QString GET_TEMPORARY_NAME_SWITCH = "--get-temp-name"; if (args.contains(GET_TEMPORARY_NAME_SWITCH)) { getTemporaryName(); } - qDebug() << "domain-server" << nullptr << "is running"; + setupNodeListAndAssignments(); + setupAutomaticNetworking(); + if (!getID().isNull()) { + setupHeartbeatToMetaverse(); + // send the first heartbeat immediately + sendHeartbeatToMetaverse(); + } + + _gatekeeper.preloadAllowedUserPublicKeys(); // so they can connect on first request + + _metadata = new DomainMetadata(this); + + + qDebug() << "domain-server is running"; } DomainServer::~DomainServer() { @@ -150,6 +158,10 @@ void DomainServer::restart() { exit(DomainServer::EXIT_CODE_REBOOT); } +const QUuid& DomainServer::getID() { + return DependencyManager::get()->getSessionUUID(); +} + bool DomainServer::optionallyReadX509KeyAndCertificate() { const QString X509_CERTIFICATE_OPTION = "cert"; const QString X509_PRIVATE_KEY_OPTION = "key"; @@ -264,11 +276,13 @@ void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) { static const QString DOMAIN_KEY = "domain"; static const QString ID_KEY = "id"; static const QString NAME_KEY = "name"; + static const QString KEY_KEY = "api_key"; auto domainObject = jsonObject[DATA_KEY].toObject()[DOMAIN_KEY].toObject(); if (!domainObject.isEmpty()) { auto id = domainObject[ID_KEY].toString(); auto name = domainObject[NAME_KEY].toString(); + auto key = domainObject[KEY_KEY].toString(); qInfo() << "Received new temporary domain name" << name; qDebug() << "The temporary domain ID is" << id; @@ -284,9 +298,13 @@ void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) { // change our domain ID immediately DependencyManager::get()->setSessionUUID(QUuid { id }); - // change our automatic networking settings so that we're communicating with the ICE server - setupICEHeartbeatForFullNetworking(); + // store the new token to the account info + auto accountManager = DependencyManager::get(); + accountManager->setTemporaryDomain(id, key); + // update our heartbeats to use the correct id + setupICEHeartbeatForFullNetworking(); + setupHeartbeatToMetaverse(); } else { qWarning() << "There were problems parsing the API response containing a temporary domain name. Please try again" << "via domain-server relaunch or from the domain-server settings."; @@ -325,7 +343,7 @@ bool DomainServer::packetVersionMatch(const udt::Packet& packet) { } -void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { +void DomainServer::setupNodeListAndAssignments() { const QString CUSTOM_LOCAL_PORT_OPTION = "metaverse.local_port"; QVariant localPortValue = _settingsManager.valueOrDefaultValueForKeyPath(CUSTOM_LOCAL_PORT_OPTION); @@ -450,29 +468,20 @@ bool DomainServer::resetAccountManagerAccessToken() { } void DomainServer::setupAutomaticNetworking() { - auto nodeList = DependencyManager::get(); - + qDebug() << "Updating automatic networking setting in domain-server to" << _automaticNetworkingSetting; _automaticNetworkingSetting = _settingsManager.valueOrDefaultValueForKeyPath(METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH).toString(); + auto nodeList = DependencyManager::get(); + const QUuid& domainID = getID(); + if (_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) { setupICEHeartbeatForFullNetworking(); } - _hasAccessToken = resetAccountManagerAccessToken(); - - if (!_hasAccessToken) { - qDebug() << "Will not send heartbeat to Metaverse API without an access token."; - qDebug() << "If this is not a temporary domain add an access token to your config file or via the web interface."; - - return; - } - if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE || _automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) { - const QUuid& domainID = nodeList->getSessionUUID(); - if (!domainID.isNull()) { qDebug() << "domain-server" << _automaticNetworkingSetting << "automatic networking enabled for ID" << uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString(); @@ -484,9 +493,6 @@ void DomainServer::setupAutomaticNetworking() { // have the LNL enable public socket updating via STUN nodeList->startSTUNPublicSocketUpdate(); - } else { - // send our heartbeat to data server so it knows what our network settings are - sendHeartbeatToMetaverse(); } } else { qDebug() << "Cannot enable domain-server automatic networking without a domain ID." @@ -494,18 +500,20 @@ void DomainServer::setupAutomaticNetworking() { return; } - } else { - sendHeartbeatToMetaverse(); } +} - qDebug() << "Updating automatic networking setting in domain-server to" << _automaticNetworkingSetting; - - // no matter the auto networking settings we should heartbeat to the data-server every 15s +void DomainServer::setupHeartbeatToMetaverse() { + // heartbeat to the data-server every 15s const int DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS = 15 * 1000; - QTimer* dataHeartbeatTimer = new QTimer(this); - connect(dataHeartbeatTimer, SIGNAL(timeout()), this, SLOT(sendHeartbeatToMetaverse())); - dataHeartbeatTimer->start(DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS); + if (!_metaverseHeartbeatTimer) { + // setup a timer to heartbeat with the metaverse-server + _metaverseHeartbeatTimer = new QTimer { this }; + connect(_metaverseHeartbeatTimer, SIGNAL(timeout()), this, SLOT(sendHeartbeatToMetaverse())); + // do not send a heartbeat immediately - this avoids flooding if the heartbeat fails with a 401 + _metaverseHeartbeatTimer->start(DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS); + } } void DomainServer::setupICEHeartbeatForFullNetworking() { @@ -524,22 +532,21 @@ void DomainServer::setupICEHeartbeatForFullNetworking() { limitedNodeList->startSTUNPublicSocketUpdate(); // to send ICE heartbeats we'd better have a private key locally with an uploaded public key - auto accountManager = DependencyManager::get(); - auto domainID = accountManager->getAccountInfo().getDomainID(); - // if we have an access token and we don't have a private key or the current domain ID has changed // we should generate a new keypair - if (!accountManager->getAccountInfo().hasPrivateKey() || domainID != limitedNodeList->getSessionUUID()) { - accountManager->generateNewDomainKeypair(limitedNodeList->getSessionUUID()); + auto accountManager = DependencyManager::get(); + if (!accountManager->getAccountInfo().hasPrivateKey() || accountManager->getAccountInfo().getDomainID() != getID()) { + accountManager->generateNewDomainKeypair(getID()); } // hookup to the signal from account manager that tells us when keypair is available connect(accountManager.data(), &AccountManager::newKeypair, this, &DomainServer::handleKeypairChange); if (!_iceHeartbeatTimer) { - // setup a timer to heartbeat with the ice-server every so often + // setup a timer to heartbeat with the ice-server _iceHeartbeatTimer = new QTimer { this }; connect(_iceHeartbeatTimer, &QTimer::timeout, this, &DomainServer::sendHeartbeatToIceServer); + sendHeartbeatToIceServer(); _iceHeartbeatTimer->start(ICE_HEARBEAT_INTERVAL_MSECS); } } @@ -1067,9 +1074,6 @@ void DomainServer::performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr) } void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) { - auto nodeList = DependencyManager::get(); - const QUuid& domainID = nodeList->getSessionUUID(); - // Setup the domain object to send to the data server QJsonObject domainObject; @@ -1088,6 +1092,13 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) { NodePermissions anonymousPermissions = _settingsManager.getPermissionsForName(NodePermissions::standardNameAnonymous); domainObject[RESTRICTED_ACCESS_FLAG] = !anonymousPermissions.canConnectToDomain; + const auto& temporaryDomainKey = DependencyManager::get()->getTemporaryDomainKey(getID()); + if (!temporaryDomainKey.isEmpty()) { + // add the temporary domain token + const QString KEY_KEY = "api_key"; + domainObject[KEY_KEY] = temporaryDomainKey; + } + if (_metadata) { // Add the metadata to the heartbeat static const QString DOMAIN_HEARTBEAT_KEY = "heartbeat"; @@ -1097,18 +1108,47 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) { QString domainUpdateJSON = QString("{\"domain\":%1}").arg(QString(QJsonDocument(domainObject).toJson(QJsonDocument::Compact))); static const QString DOMAIN_UPDATE = "/api/v1/domains/%1"; - DependencyManager::get()->sendRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)), - AccountManagerAuth::Required, + DependencyManager::get()->sendRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(getID())), + AccountManagerAuth::Optional, QNetworkAccessManager::PutOperation, - JSONCallbackParameters(), + JSONCallbackParameters(nullptr, QString(), this, "handleMetaverseHeartbeatError"), domainUpdateJSON.toUtf8()); } +void DomainServer::handleMetaverseHeartbeatError(QNetworkReply& requestReply) { + if (!_metaverseHeartbeatTimer) { + // avoid rehandling errors from the same issue + return; + } + + // if we have a temporary domain with a bad token, we will get a 401 + if (requestReply.error() == QNetworkReply::NetworkError::AuthenticationRequiredError) { + static const QString DATA_KEY = "data"; + static const QString TOKEN_KEY = "api_key"; + + QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); + auto tokenFailure = jsonObject[DATA_KEY].toObject()[TOKEN_KEY]; + + if (!tokenFailure.isNull()) { + qWarning() << "Temporary domain name lacks a valid API key, and is being reset."; + + // halt heartbeats until we have a token + _metaverseHeartbeatTimer->deleteLater(); + _metaverseHeartbeatTimer = nullptr; + + // give up eventually to avoid flooding traffic + static const int MAX_ATTEMPTS = 5; + static int attempt = 0; + if (++attempt < MAX_ATTEMPTS) { + // get a new temporary name and token + getTemporaryName(true); + } + } + } +} + void DomainServer::sendICEServerAddressToMetaverseAPI() { if (!_iceServerSocket.isNull()) { - auto nodeList = DependencyManager::get(); - const QUuid& domainID = nodeList->getSessionUUID(); - const QString ICE_SERVER_ADDRESS = "ice_server_address"; QJsonObject domainObject; @@ -1116,6 +1156,13 @@ void DomainServer::sendICEServerAddressToMetaverseAPI() { // we're using full automatic networking and we have a current ice-server socket, use that now domainObject[ICE_SERVER_ADDRESS] = _iceServerSocket.getAddress().toString(); + const auto& temporaryDomainKey = DependencyManager::get()->getTemporaryDomainKey(getID()); + if (!temporaryDomainKey.isEmpty()) { + // add the temporary domain token + const QString KEY_KEY = "api_key"; + domainObject[KEY_KEY] = temporaryDomainKey; + } + QString domainUpdateJSON = QString("{\"domain\": %1 }").arg(QString(QJsonDocument(domainObject).toJson())); // make sure we hear about failure so we can retry @@ -1127,7 +1174,7 @@ void DomainServer::sendICEServerAddressToMetaverseAPI() { static const QString DOMAIN_ICE_ADDRESS_UPDATE = "/api/v1/domains/%1/ice_server_address"; - DependencyManager::get()->sendRequest(DOMAIN_ICE_ADDRESS_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)), + DependencyManager::get()->sendRequest(DOMAIN_ICE_ADDRESS_UPDATE.arg(uuidStringWithoutCurlyBraces(getID())), AccountManagerAuth::Optional, QNetworkAccessManager::PutOperation, callbackParameters, diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 8a25591605..138cb9ca2d 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -80,6 +80,8 @@ private slots: void handleTempDomainSuccess(QNetworkReply& requestReply); void handleTempDomainError(QNetworkReply& requestReply); + void handleMetaverseHeartbeatError(QNetworkReply& requestReply); + void queuedQuit(QString quitMessage, int exitCode); void handleKeypairChange(); @@ -96,7 +98,9 @@ signals: void userDisconnected(); private: - void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid()); + const QUuid& getID(); + + void setupNodeListAndAssignments(); bool optionallySetupOAuth(); bool optionallyReadX509KeyAndCertificate(); @@ -108,6 +112,7 @@ private: void setupAutomaticNetworking(); void setupICEHeartbeatForFullNetworking(); + void setupHeartbeatToMetaverse(); void sendHeartbeatToMetaverse(const QString& networkAddress); void randomizeICEServerAddress(bool shouldTriggerHostLookup); @@ -178,6 +183,7 @@ private: // These will be parented to this, they are not dangling DomainMetadata* _metadata { nullptr }; QTimer* _iceHeartbeatTimer { nullptr }; + QTimer* _metaverseHeartbeatTimer { nullptr }; QList _iceServerAddresses; QSet _failedIceServerAddresses; @@ -186,8 +192,6 @@ private: int _numHeartbeatDenials { 0 }; bool _connectedToICEServer { false }; - bool _hasAccessToken { false }; - friend class DomainGatekeeper; friend class DomainMetadata; }; From 4a30d549add061e408c95d692e2098ef6eb70207 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Fri, 17 Jun 2016 19:26:22 -0700 Subject: [PATCH 120/171] force temp domain reset on 404 too (401 already) --- domain-server/src/DomainServer.cpp | 51 +++++++++++++++++++----------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index df1697af28..192c0d26e6 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1121,29 +1121,42 @@ void DomainServer::handleMetaverseHeartbeatError(QNetworkReply& requestReply) { return; } - // if we have a temporary domain with a bad token, we will get a 401 - if (requestReply.error() == QNetworkReply::NetworkError::AuthenticationRequiredError) { - static const QString DATA_KEY = "data"; - static const QString TOKEN_KEY = "api_key"; + // check if we need to force a new temporary domain name + switch (requestReply.error()) { + // if we have a temporary domain with a bad token, we get a 401 + case QNetworkReply::NetworkError::AuthenticationRequiredError: { + static const QString DATA_KEY = "data"; + static const QString TOKEN_KEY = "api_key"; - QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); - auto tokenFailure = jsonObject[DATA_KEY].toObject()[TOKEN_KEY]; + QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); + auto tokenFailure = jsonObject[DATA_KEY].toObject()[TOKEN_KEY]; - if (!tokenFailure.isNull()) { - qWarning() << "Temporary domain name lacks a valid API key, and is being reset."; - - // halt heartbeats until we have a token - _metaverseHeartbeatTimer->deleteLater(); - _metaverseHeartbeatTimer = nullptr; - - // give up eventually to avoid flooding traffic - static const int MAX_ATTEMPTS = 5; - static int attempt = 0; - if (++attempt < MAX_ATTEMPTS) { - // get a new temporary name and token - getTemporaryName(true); + if (!tokenFailure.isNull()) { + qWarning() << "Temporary domain name lacks a valid API key, and is being reset."; } + break; } + // if the domain does not (or no longer) exists, we get a 404 + case QNetworkReply::NetworkError::ContentNotFoundError: + qWarning() << "Domain not found, getting a new temporary domain."; + break; + // otherwise, we erred on something else, and should not force a temporary domain + default: + return; + } + + // halt heartbeats until we have a token + _metaverseHeartbeatTimer->deleteLater(); + _metaverseHeartbeatTimer = nullptr; + + // give up eventually to avoid flooding traffic + static const int MAX_ATTEMPTS = 5; + static int attempt = 0; + if (++attempt < MAX_ATTEMPTS) { + // get a new temporary name and token + getTemporaryName(true); + } else { + qWarning() << "Already attempted too many temporary domain requests. Please set a domain ID manually or restart."; } } From d3735322a5c9baa97312d4504069a476b18a16f3 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 22 Jun 2016 13:03:13 -0700 Subject: [PATCH 121/171] Whitespace. --- interface/src/scripting/ToolbarScriptingInterface.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/scripting/ToolbarScriptingInterface.h b/interface/src/scripting/ToolbarScriptingInterface.h index d3706cb6e1..9379284e55 100644 --- a/interface/src/scripting/ToolbarScriptingInterface.h +++ b/interface/src/scripting/ToolbarScriptingInterface.h @@ -20,7 +20,7 @@ class ToolbarProxy; class ToolbarScriptingInterface : public QObject, public Dependency { Q_OBJECT public: - Q_INVOKABLE QObject* getToolbar(const QString& toolbarId); + Q_INVOKABLE QObject* getToolbar(const QString& toolbarId); }; #endif // hifi_ToolbarScriptingInterface_h From d4cfe6256a93884bf13f6869f1c2192bd6e4c442 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 22 Jun 2016 14:46:08 -0700 Subject: [PATCH 122/171] don't render mini mirror if rendering fullscreen mirror --- interface/src/ui/ApplicationOverlay.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 9330f1f6c4..6d5df31766 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -150,7 +150,8 @@ void ApplicationOverlay::renderRearViewToFbo(RenderArgs* renderArgs) { } void ApplicationOverlay::renderRearView(RenderArgs* renderArgs) { - if (!qApp->isHMDMode() && Menu::getInstance()->isOptionChecked(MenuOption::MiniMirror)) { + if (!qApp->isHMDMode() && Menu::getInstance()->isOptionChecked(MenuOption::MiniMirror) && + !Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)) { gpu::Batch& batch = *renderArgs->_batch; auto geometryCache = DependencyManager::get(); From 8009d23f7007fbf311ab80f593346d4cf5658764 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 22 Jun 2016 15:20:45 -0700 Subject: [PATCH 123/171] more STATIC_MESH and dynamic overlap prevention --- libraries/entities/src/EntityItem.cpp | 12 ++-- libraries/entities/src/EntityItem.h | 4 +- libraries/entities/src/ModelEntityItem.cpp | 57 ++++++++++++------- libraries/entities/src/ModelEntityItem.h | 6 +- .../entities/src/ParticleEffectEntityItem.cpp | 6 +- .../entities/src/ParticleEffectEntityItem.h | 4 +- libraries/entities/src/ZoneEntityItem.cpp | 4 +- libraries/entities/src/ZoneEntityItem.h | 6 +- 8 files changed, 61 insertions(+), 38 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 2abb9f12e2..f0a4d40860 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1693,9 +1693,13 @@ void EntityItem::updateCollisionMask(uint8_t value) { } void EntityItem::updateDynamic(bool value) { - if (_dynamic != value) { - if (getShapeType() == SHAPE_TYPE_STATIC_MESH) { - _dynamic = false; + if (getDynamic() != value) { + // dynamic and STATIC_MESH are incompatible so we check for that case + if (value && getShapeType() == SHAPE_TYPE_STATIC_MESH) { + if (_dynamic) { + _dynamic = false; + _dirtyFlags |= Simulation::DIRTY_MOTION_TYPE; + } } else { _dynamic = value; _dirtyFlags |= Simulation::DIRTY_MOTION_TYPE; @@ -1749,7 +1753,7 @@ void EntityItem::computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask group = BULLET_COLLISION_GROUP_COLLISIONLESS; mask = 0; } else { - if (_dynamic) { + if (getDynamic()) { group = BULLET_COLLISION_GROUP_DYNAMIC; } else if (isMovingRelativeToParent() || hasActions()) { group = BULLET_COLLISION_GROUP_KINEMATIC; diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 4a691462ab..9fa13690f1 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -283,7 +283,7 @@ public: void computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask) const; - bool getDynamic() const { return _dynamic; } + bool getDynamic() const { return SHAPE_TYPE_STATIC_MESH == getShapeType() ? false : _dynamic; } void setDynamic(bool value) { _dynamic = value; } virtual bool shouldBePhysical() const { return false; } @@ -348,7 +348,7 @@ public: void updateDynamic(bool value); void updateLifetime(float value); void updateCreated(uint64_t value); - virtual void updateShapeType(ShapeType type) { /* do nothing */ } + virtual void setShapeType(ShapeType type) { /* do nothing */ } uint32_t getDirtyFlags() const { return _dirtyFlags; } void clearDirtyFlags(uint32_t mask = 0xffffffff) { _dirtyFlags &= ~mask; } diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 40faf2c3c3..8e925b2f79 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -77,7 +77,7 @@ bool ModelEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(modelURL, setModelURL); SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL); SET_ENTITY_PROPERTY_FROM_PROPERTIES(textures, setTextures); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, updateShapeType); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType); SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointRotationsSet, setJointRotationsSet); SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointRotations, setJointRotations); SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointTranslationsSet, setJointTranslationsSet); @@ -145,7 +145,7 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, dataAt += bytesFromAnimation; } - READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, updateShapeType); + READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType); if (animationPropertiesChanged) { _dirtyFlags |= Simulation::DIRTY_UPDATEABLE; @@ -257,37 +257,54 @@ void ModelEntityItem::debugDump() const { qCDebug(entities) << " compound shape URL:" << getCompoundShapeURL(); } -void ModelEntityItem::updateShapeType(ShapeType type) { - // BEGIN_TEMPORARY_WORKAROUND - // we have allowed inconsistent ShapeType's to be stored in SVO files in the past (this was a bug) - // but we are now enforcing the entity properties to be consistent. To make the possible we're - // introducing a temporary workaround: we will ignore ShapeType updates that conflict with the - // _compoundShapeURL. - if (hasCompoundShapeURL()) { - type = SHAPE_TYPE_COMPOUND; - } - // END_TEMPORARY_WORKAROUND - +void ModelEntityItem::setShapeType(ShapeType type) { if (type != _shapeType) { + if (type == SHAPE_TYPE_STATIC_MESH && _dynamic) { + // dynamic and STATIC_MESH are incompatible + // since the shape is being set here we clear the dynamic bit + _dynamic = false; + _dirtyFlags |= Simulation::DIRTY_MOTION_TYPE; + } _shapeType = type; _dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS; } } -// virtual ShapeType ModelEntityItem::getShapeType() const { - if (_shapeType == SHAPE_TYPE_COMPOUND) { - return hasCompoundShapeURL() ? SHAPE_TYPE_COMPOUND : SHAPE_TYPE_NONE; - } else { - return _shapeType; + return computeTrueShapeType(); +} + +ShapeType ModelEntityItem::computeTrueShapeType() const { + ShapeType type = _shapeType; + if (type == SHAPE_TYPE_STATIC_MESH && _dynamic) { + // dynamic is incompatible with STATIC_MESH + // shouldn't fall in here but just in case --> fall back to COMPOUND + type = SHAPE_TYPE_COMPOUND; + } + if (type == SHAPE_TYPE_COMPOUND && !hasCompoundShapeURL()) { + // no compoundURL set --> fall back to NONE + type = SHAPE_TYPE_NONE; + } + return type; +} + +void ModelEntityItem::setModelURL(const QString& url) { + if (_modelURL != url) { + _modelURL = url; + _parsedModelURL = QUrl(url); + if (_shapeType == SHAPE_TYPE_STATIC_MESH) { + _dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS; + } } } void ModelEntityItem::setCompoundShapeURL(const QString& url) { if (_compoundShapeURL != url) { + ShapeType oldType = computeTrueShapeType(); _compoundShapeURL = url; - _dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS; - _shapeType = _compoundShapeURL.isEmpty() ? SHAPE_TYPE_NONE : SHAPE_TYPE_COMPOUND; + if (oldType != computeTrueShapeType()) { + _dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS; + } } } diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 29730bf4df..7b7edaf945 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -50,9 +50,10 @@ public: virtual bool needsToCallUpdate() const; virtual void debugDump() const; - void updateShapeType(ShapeType type); + void setShapeType(ShapeType type); virtual ShapeType getShapeType() const; + // TODO: Move these to subclasses, or other appropriate abstraction // getters/setters applicable to models and particles @@ -76,7 +77,7 @@ public: } // model related properties - virtual void setModelURL(const QString& url) { _modelURL = url; _parsedModelURL = QUrl(url); } + virtual void setModelURL(const QString& url); virtual void setCompoundShapeURL(const QString& url); // Animation related items... @@ -130,6 +131,7 @@ public: private: void setAnimationSettings(const QString& value); // only called for old bitstream format + ShapeType computeTrueShapeType() const; protected: // these are used: diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index a7bd0038e6..c501737146 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -342,7 +342,7 @@ bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& propert SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, updateShapeType); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType); SET_ENTITY_PROPERTY_FROM_PROPERTIES(maxParticles, setMaxParticles); SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifespan, setLifespan); SET_ENTITY_PROPERTY_FROM_PROPERTIES(isEmitting, setIsEmitting); @@ -406,7 +406,7 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch READ_ENTITY_PROPERTY(PROP_EMITTING_PARTICLES, bool, setIsEmitting); } - READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, updateShapeType); + READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType); READ_ENTITY_PROPERTY(PROP_MAX_PARTICLES, quint32, setMaxParticles); READ_ENTITY_PROPERTY(PROP_LIFESPAN, float, setLifespan); READ_ENTITY_PROPERTY(PROP_EMIT_RATE, float, setEmitRate); @@ -584,7 +584,7 @@ void ParticleEffectEntityItem::debugDump() const { qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); } -void ParticleEffectEntityItem::updateShapeType(ShapeType type) { +void ParticleEffectEntityItem::setShapeType(ShapeType type) { if (type != _shapeType) { _shapeType = type; _dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS; diff --git a/libraries/entities/src/ParticleEffectEntityItem.h b/libraries/entities/src/ParticleEffectEntityItem.h index 4538a1bb43..777f3b6cf6 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.h +++ b/libraries/entities/src/ParticleEffectEntityItem.h @@ -95,8 +95,8 @@ public: void setAlphaSpread(float alphaSpread); float getAlphaSpread() const { return _alphaSpread; } - void updateShapeType(ShapeType type); - virtual ShapeType getShapeType() const { return _shapeType; } + void setShapeType(ShapeType type) override; + virtual ShapeType getShapeType() const override { return _shapeType; } virtual void debugDump() const; diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index a28b8210c2..0b99d0377f 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -73,7 +73,7 @@ bool ZoneEntityItem::setProperties(const EntityItemProperties& properties) { bool somethingChangedInStage = _stageProperties.setProperties(properties); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, updateShapeType); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType); SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL); SET_ENTITY_PROPERTY_FROM_PROPERTIES(backgroundMode, setBackgroundMode); @@ -117,7 +117,7 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, bytesRead += bytesFromStage; dataAt += bytesFromStage; - READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, updateShapeType); + READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType); READ_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL); READ_ENTITY_PROPERTY(PROP_BACKGROUND_MODE, BackgroundMode, setBackgroundMode); diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 56968aa9c9..599e70f83e 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -54,9 +54,9 @@ public: static bool getDrawZoneBoundaries() { return _drawZoneBoundaries; } static void setDrawZoneBoundaries(bool value) { _drawZoneBoundaries = value; } - virtual bool isReadyToComputeShape() { return false; } - void updateShapeType(ShapeType type) { _shapeType = type; } - virtual ShapeType getShapeType() const; + virtual bool isReadyToComputeShape() override { return false; } + void setShapeType(ShapeType type) override { _shapeType = type; } + virtual ShapeType getShapeType() const override; virtual bool hasCompoundShapeURL() const { return !_compoundShapeURL.isEmpty(); } const QString getCompoundShapeURL() const { return _compoundShapeURL; } From e5b89ebd17aaa10fcb7253c10fcaa4ea0576d9ae Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 22 Jun 2016 15:33:44 -0700 Subject: [PATCH 124/171] bump version number --- libraries/networking/src/udt/PacketHeaders.cpp | 2 +- libraries/networking/src/udt/PacketHeaders.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 6ca50420f3..c74b10820d 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -49,7 +49,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityAdd: case PacketType::EntityEdit: case PacketType::EntityData: - return VERSION_ENTITIES_PROPERLY_ENCODE_SHAPE_EDITS; + return VERSION_MODEL_ENTITIES_SUPPORT_STATIC_MESH; case PacketType::AvatarIdentity: case PacketType::AvatarData: case PacketType::BulkAvatarData: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index ae54450fee..e484a06502 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -180,6 +180,7 @@ const PacketVersion VERSION_LIGHT_HAS_FALLOFF_RADIUS = 57; const PacketVersion VERSION_ENTITIES_NO_FLY_ZONES = 58; const PacketVersion VERSION_ENTITIES_MORE_SHAPES = 59; const PacketVersion VERSION_ENTITIES_PROPERLY_ENCODE_SHAPE_EDITS = 60; +const PacketVersion VERSION_MODEL_ENTITIES_SUPPORT_STATIC_MESH = 61; enum class AvatarMixerPacketVersion : PacketVersion { TranslationSupport = 17, From f495f5e3b56173d15977159300db9802fa46843d Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 22 Jun 2016 16:05:55 -0700 Subject: [PATCH 125/171] removed mac hydra support --- plugins/hifiSixense/src/SixenseManager.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/plugins/hifiSixense/src/SixenseManager.cpp b/plugins/hifiSixense/src/SixenseManager.cpp index 03028249a3..48d13a8eaf 100644 --- a/plugins/hifiSixense/src/SixenseManager.cpp +++ b/plugins/hifiSixense/src/SixenseManager.cpp @@ -66,14 +66,8 @@ const QString SHOW_DEBUG_RAW = "Debug Draw Raw Data"; const QString SHOW_DEBUG_CALIBRATED = "Debug Draw Calibrated Data"; bool SixenseManager::isSupported() const { -#ifdef HAVE_SIXENSE - -#if defined(Q_OS_OSX) - return QSysInfo::macVersion() <= QSysInfo::MV_MAVERICKS; -#else +#if defined(HAVE_SIXENSE) && !defined(Q_OS_OSX) return true; -#endif - #else return false; #endif From 3c9f3f392736b56a0641a6f6707c00077b2871bf Mon Sep 17 00:00:00 2001 From: Zander Otavka Date: Wed, 22 Jun 2016 16:22:14 -0700 Subject: [PATCH 126/171] Automatically enter first person when in HMD mode --- interface/src/Application.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d20a62fc7b..ddfa5dae8b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5350,7 +5350,13 @@ void Application::updateDisplayMode() { // reset the avatar, to set head and hand palms back to a reasonable default pose. getMyAvatar()->reset(false); - Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin"); + // go into first person when they are in HMD mode, since 3rd person HMD is dumb + if (isHMDMode() && !menu->isOptionChecked(MenuOption::FirstPerson)) { + menu->setIsOptionChecked(MenuOption::FirstPerson, true); + cameraMenuChanged(); + } + + Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin"); } mat4 Application::getEyeProjection(int eye) const { From 581d87d6536b45867ea751369d5a3bd534948014 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 22 Jun 2016 16:40:10 -0700 Subject: [PATCH 127/171] feedback re magic numbers and variable name. --- .../src/display-plugins/hmd/HmdDisplayPlugin.cpp | 11 ++++++----- plugins/openvr/src/OpenVrHelpers.cpp | 3 ++- scripts/system/controllers/handControllerGrab.js | 4 ++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index dbf264179e..f1aa1edc81 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -420,8 +420,9 @@ bool HmdDisplayPlugin::setHandLaser(uint32_t hands, HandLaserMode mode, const ve } void HmdDisplayPlugin::compositeExtra() { - std::array handLasers; - std::array renderHandPoses; + const int NUMBER_OF_HANDS = 2; + std::array handLasers; + std::array renderHandPoses; Transform uiModelTransform; withPresentThreadLock([&] { handLasers = _handLasers; @@ -443,9 +444,9 @@ void HmdDisplayPlugin::compositeExtra() { using namespace oglplus; useProgram(_laserProgram); _laserGeometry->Use(); - std::array handLaserModelMatrices; + std::array handLaserModelMatrices; - for (int i = 0; i < 2; ++i) { + for (int i = 0; i < NUMBER_OF_HANDS; ++i) { if (renderHandPoses[i] == identity) { continue; } @@ -485,7 +486,7 @@ void HmdDisplayPlugin::compositeExtra() { auto eyePose = _currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye); auto view = glm::inverse(eyePose); const auto& projection = _eyeProjections[eye]; - for (int i = 0; i < 2; ++i) { + for (int i = 0; i < NUMBER_OF_HANDS; ++i) { if (handLaserModelMatrices[i] == identity) { continue; } diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index 436cd7fc30..e71c8942d6 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -194,7 +194,8 @@ void enableOpenVrKeyboard(PluginContainer* container) { _overlayMenuConnection = QObject::connect(action, &QAction::triggered, [action] { if (action->isChecked()) { _overlayRevealed = true; - QTimer::singleShot(100, [&] { _overlayRevealed = false; }); + const int KEYBOARD_DELAY_MS = 100; + QTimer::singleShot(KEYBOARD_DELAY_MS, [&] { _overlayRevealed = false; }); } }); diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 39d85f1224..7706132c58 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -877,8 +877,8 @@ function MyController(hand) { // find entities near the avatar that might be equipable. var entities = Entities.findEntities(MyAvatar.position, HOTSPOT_DRAW_DISTANCE); - var i, l = entities.length; - for (i = 0; i < l; i++) { + var i, length = entities.length; + for (i = 0; i < length; i++) { var grabProps = Entities.getEntityProperties(entities[i], GRABBABLE_PROPERTIES); // does this entity have an attach point? var wearableData = getEntityCustomData("wearable", entities[i], undefined); From 7e5b9db13a688100d9d587c1a4147e6a65f15e78 Mon Sep 17 00:00:00 2001 From: Zander Otavka Date: Wed, 22 Jun 2016 16:50:47 -0700 Subject: [PATCH 128/171] Uses spaces, not tabs --- interface/src/Application.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ddfa5dae8b..53b9c18360 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5349,14 +5349,14 @@ void Application::updateDisplayMode() { // reset the avatar, to set head and hand palms back to a reasonable default pose. getMyAvatar()->reset(false); - - // go into first person when they are in HMD mode, since 3rd person HMD is dumb - if (isHMDMode() && !menu->isOptionChecked(MenuOption::FirstPerson)) { - menu->setIsOptionChecked(MenuOption::FirstPerson, true); - cameraMenuChanged(); - } - - Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin"); + + // go into first person when they are in HMD mode, since 3rd person HMD is dumb + if (isHMDMode() && !menu->isOptionChecked(MenuOption::FirstPerson)) { + menu->setIsOptionChecked(MenuOption::FirstPerson, true); + cameraMenuChanged(); + } + + Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin"); } mat4 Application::getEyeProjection(int eye) const { From a9ed0b1c8386c530e08ef9e23845ca21580f05f7 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 22 Jun 2016 16:56:50 -0700 Subject: [PATCH 129/171] completely destroyed sixense on macs --- cmake/externals/sixense/CMakeLists.txt | 25 +-- cmake/macros/TargetSixense.cmake | 12 +- plugins/hifiSixense/src/SixenseManager.cpp | 2 + plugins/hifiSixense/src/SixenseSupportOSX.cpp | 155 ------------------ 4 files changed, 10 insertions(+), 184 deletions(-) delete mode 100644 plugins/hifiSixense/src/SixenseSupportOSX.cpp diff --git a/cmake/externals/sixense/CMakeLists.txt b/cmake/externals/sixense/CMakeLists.txt index 16f2850449..bd0d042c0b 100644 --- a/cmake/externals/sixense/CMakeLists.txt +++ b/cmake/externals/sixense/CMakeLists.txt @@ -57,30 +57,7 @@ if (WIN32) elseif(APPLE) - set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/lib/osx_x64/release_dll/libsixense_x64.dylib CACHE TYPE INTERNAL) - set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${SOURCE_DIR}/lib/osx_x64/debug_dll/libsixensed_x64.dylib CACHE TYPE INTERNAL) - - set(_SIXENSE_LIB_DIR "${SOURCE_DIR}/lib/osx_x64") - ExternalProject_Add_Step( - ${EXTERNAL_NAME} - change-install-name-release - COMMENT "Calling install_name_tool on libraries to fix install name for dylib linking" - COMMAND ${CMAKE_COMMAND} -DINSTALL_NAME_LIBRARY_DIR=${_SIXENSE_LIB_DIR}/release_dll -P ${EXTERNAL_PROJECT_DIR}/OSXInstallNameChange.cmake - DEPENDEES install - WORKING_DIRECTORY - LOG 1 - ) - - set(_SIXENSE_LIB_DIR "${SOURCE_DIR}/lib/osx_x64") - ExternalProject_Add_Step( - ${EXTERNAL_NAME} - change-install-name-debug - COMMENT "Calling install_name_tool on libraries to fix install name for dylib linking" - COMMAND ${CMAKE_COMMAND} -DINSTALL_NAME_LIBRARY_DIR=${_SIXENSE_LIB_DIR}/debug_dll -P ${EXTERNAL_PROJECT_DIR}/OSXInstallNameChange.cmake - DEPENDEES install - WORKING_DIRECTORY - LOG 1 - ) + # We no longer support Sixense on Macs due to bugs in the Sixense DLL elseif(NOT ANDROID) diff --git a/cmake/macros/TargetSixense.cmake b/cmake/macros/TargetSixense.cmake index 6fd9cede1f..28128d8b79 100644 --- a/cmake/macros/TargetSixense.cmake +++ b/cmake/macros/TargetSixense.cmake @@ -6,9 +6,11 @@ # See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html # macro(TARGET_SIXENSE) - add_dependency_external_projects(sixense) - find_package(Sixense REQUIRED) - target_include_directories(${TARGET_NAME} PRIVATE ${SIXENSE_INCLUDE_DIRS}) - target_link_libraries(${TARGET_NAME} ${SIXENSE_LIBRARIES}) - add_definitions(-DHAVE_SIXENSE) + if(NOT APPLE) + add_dependency_external_projects(sixense) + find_package(Sixense REQUIRED) + target_include_directories(${TARGET_NAME} PRIVATE ${SIXENSE_INCLUDE_DIRS}) + target_link_libraries(${TARGET_NAME} ${SIXENSE_LIBRARIES}) + add_definitions(-DHAVE_SIXENSE) + endif() endmacro() diff --git a/plugins/hifiSixense/src/SixenseManager.cpp b/plugins/hifiSixense/src/SixenseManager.cpp index 48d13a8eaf..ade643ec72 100644 --- a/plugins/hifiSixense/src/SixenseManager.cpp +++ b/plugins/hifiSixense/src/SixenseManager.cpp @@ -131,6 +131,7 @@ void SixenseManager::setSixenseFilter(bool filter) { void SixenseManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { BAIL_IF_NOT_LOADED +#ifdef HAVE_SIXENSE static bool sixenseHasBeenConnected { false }; if (!sixenseHasBeenConnected && sixenseIsBaseConnected(0)) { sixenseHasBeenConnected = true; @@ -146,6 +147,7 @@ void SixenseManager::pluginUpdate(float deltaTime, const controller::InputCalibr _container->requestReset(); _inputDevice->_requestReset = false; } +#endif } void SixenseManager::InputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { diff --git a/plugins/hifiSixense/src/SixenseSupportOSX.cpp b/plugins/hifiSixense/src/SixenseSupportOSX.cpp deleted file mode 100644 index fce2ea023b..0000000000 --- a/plugins/hifiSixense/src/SixenseSupportOSX.cpp +++ /dev/null @@ -1,155 +0,0 @@ -// -// SixenseSupportOSX.cpp -// libraries/input-plugins/src/input-plugins -// -// Created by Clement on 10/20/15. -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -// Mock implementation of sixense.h to hide dynamic linking on OS X -#if defined(__APPLE__) && defined(HAVE_SIXENSE) -#include - -#include - -#include -#include -#include - -#ifndef SIXENSE_LIB_FILENAME -#define SIXENSE_LIB_FILENAME QCoreApplication::applicationDirPath() + "/../Frameworks/libsixense_x64" -#endif - -using Library = std::unique_ptr; -static Library SIXENSE; - -struct Callable { - template - int operator() (Args&&... args){ - return reinterpret_cast(function)(std::forward(args)...); - } - QFunctionPointer function; -}; - -Callable resolve(const Library& library, const char* name) { - Q_ASSERT_X(library && library->isLoaded(), __FUNCTION__, "Sixense library not loaded"); - auto function = library->resolve(name); - Q_ASSERT_X(function, __FUNCTION__, std::string("Could not resolve ").append(name).c_str()); - return Callable { function }; -} -#define FORWARD resolve(SIXENSE, __FUNCTION__) - - -void loadSixense() { - Q_ASSERT_X(!(SIXENSE && SIXENSE->isLoaded()), __FUNCTION__, "Sixense library already loaded"); - SIXENSE.reset(new QLibrary(SIXENSE_LIB_FILENAME)); - Q_CHECK_PTR(SIXENSE); - - if (SIXENSE->load()){ - qDebug() << "Loaded sixense library for hydra support -" << SIXENSE->fileName(); - } else { - qDebug() << "Sixense library at" << SIXENSE->fileName() << "failed to load:" << SIXENSE->errorString(); - qDebug() << "Continuing without hydra support."; - } -} -void unloadSixense() { - SIXENSE->unload(); -} - - -// sixense.h wrapper for OSX dynamic linking -int sixenseInit() { - loadSixense(); - if (!SIXENSE || !SIXENSE->isLoaded()) { - return SIXENSE_FAILURE; - } - return FORWARD(); -} -int sixenseExit() { - auto returnCode = FORWARD(); - unloadSixense(); - return returnCode; -} - -int sixenseGetMaxBases() { - return FORWARD(); -} -int sixenseSetActiveBase(int i) { - return FORWARD(i); -} -int sixenseIsBaseConnected(int i) { - return FORWARD(i); -} - -int sixenseGetMaxControllers() { - return FORWARD(); -} -int sixenseIsControllerEnabled(int which) { - return FORWARD(which); -} -int sixenseGetNumActiveControllers() { - return FORWARD(); -} - -int sixenseGetHistorySize() { - return FORWARD(); -} - -int sixenseGetData(int which, int index_back, sixenseControllerData* data) { - return FORWARD(which, index_back, data); -} -int sixenseGetAllData(int index_back, sixenseAllControllerData* data) { - return FORWARD(index_back, data); -} -int sixenseGetNewestData(int which, sixenseControllerData* data) { - return FORWARD(which, data); -} -int sixenseGetAllNewestData(sixenseAllControllerData* data) { - return FORWARD(data); -} - -int sixenseSetHemisphereTrackingMode(int which_controller, int state) { - return FORWARD(which_controller, state); -} -int sixenseGetHemisphereTrackingMode(int which_controller, int* state) { - return FORWARD(which_controller, state); -} -int sixenseAutoEnableHemisphereTracking(int which_controller) { - return FORWARD(which_controller); -} - -int sixenseSetHighPriorityBindingEnabled(int on_or_off) { - return FORWARD(on_or_off); -} -int sixenseGetHighPriorityBindingEnabled(int* on_or_off) { - return FORWARD(on_or_off); -} - -int sixenseTriggerVibration(int controller_id, int duration_100ms, int pattern_id) { - return FORWARD(controller_id, duration_100ms, pattern_id); -} - -int sixenseSetFilterEnabled(int on_or_off) { - return FORWARD(on_or_off); -} -int sixenseGetFilterEnabled(int* on_or_off) { - return FORWARD(on_or_off); -} - -int sixenseSetFilterParams(float near_range, float near_val, float far_range, float far_val) { - return FORWARD(near_range, near_val, far_range, far_val); -} -int sixenseGetFilterParams(float* near_range, float* near_val, float* far_range, float* far_val) { - return FORWARD(near_range, near_val, far_range, far_val); -} - -int sixenseSetBaseColor(unsigned char red, unsigned char green, unsigned char blue) { - return FORWARD(red, green, blue); -} -int sixenseGetBaseColor(unsigned char* red, unsigned char* green, unsigned char* blue) { - return FORWARD(red, green, blue); -} -#endif From 645ae3c21e96fe299ceac3c2499b218b9fc87213 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 22 Jun 2016 17:02:02 -0700 Subject: [PATCH 130/171] remove tabs --- cmake/macros/TargetSixense.cmake | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cmake/macros/TargetSixense.cmake b/cmake/macros/TargetSixense.cmake index 28128d8b79..07dcfe67e4 100644 --- a/cmake/macros/TargetSixense.cmake +++ b/cmake/macros/TargetSixense.cmake @@ -6,11 +6,11 @@ # See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html # macro(TARGET_SIXENSE) - if(NOT APPLE) - add_dependency_external_projects(sixense) - find_package(Sixense REQUIRED) - target_include_directories(${TARGET_NAME} PRIVATE ${SIXENSE_INCLUDE_DIRS}) - target_link_libraries(${TARGET_NAME} ${SIXENSE_LIBRARIES}) - add_definitions(-DHAVE_SIXENSE) - endif() + if(NOT APPLE) + add_dependency_external_projects(sixense) + find_package(Sixense REQUIRED) + target_include_directories(${TARGET_NAME} PRIVATE ${SIXENSE_INCLUDE_DIRS}) + target_link_libraries(${TARGET_NAME} ${SIXENSE_LIBRARIES}) + add_definitions(-DHAVE_SIXENSE) + endif() endmacro() From cf44783550a906679324fa5f13847b617ec4dd3c Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 22 Jun 2016 17:53:38 -0700 Subject: [PATCH 131/171] can't enter independent mode through edit.js in HMD mode --- scripts/system/libraries/entityCameraTool.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/libraries/entityCameraTool.js b/scripts/system/libraries/entityCameraTool.js index a8e9335956..63f7c43fdc 100644 --- a/scripts/system/libraries/entityCameraTool.js +++ b/scripts/system/libraries/entityCameraTool.js @@ -141,7 +141,7 @@ CameraManager = function() { }; that.enable = function() { - if (Camera.mode == "independent" || that.enabled) return; + if (Camera.mode == "independent" || that.enabled || HMD.active) return; for (var i = 0; i < CAPTURED_KEYS.length; i++) { Controller.captureKeyEvents({ From c0be9aec6aedd683e12d7d61dfbf9ff210526d70 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Thu, 9 Jun 2016 13:28:25 -0700 Subject: [PATCH 132/171] cleanup the callers of the resamplers for mic to net and net to speaker, remove loopback resampler --- libraries/audio-client/src/AudioClient.cpp | 88 +++++++--------------- libraries/audio-client/src/AudioClient.h | 1 - 2 files changed, 26 insertions(+), 63 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 0c7a79e2a3..ac055bb0f6 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -102,7 +102,6 @@ AudioClient::AudioClient() : _reverbOptions(&_scriptReverbOptions), _inputToNetworkResampler(NULL), _networkToOutputResampler(NULL), - _loopbackResampler(NULL), _outgoingAvatarAudioSequenceNumber(0), _audioOutputIODevice(_receivedAudioStream, this), _stats(&_receivedAudioStream), @@ -315,54 +314,35 @@ bool adjustedFormatForAudioDevice(const QAudioDeviceInfo& audioDevice, // FIXME: directly using 24khz has a bug somewhere that causes channels to be swapped. // Continue using our internal resampler, for now. - if (true || !audioDevice.isFormatSupported(desiredAudioFormat)) { - qCDebug(audioclient) << "The desired format for audio I/O is" << desiredAudioFormat; - qCDebug(audioclient, "The desired audio format is not supported by this device"); + qCDebug(audioclient) << "The desired format for audio I/O is" << desiredAudioFormat; - if (desiredAudioFormat.channelCount() == 1) { - adjustedAudioFormat = desiredAudioFormat; - adjustedAudioFormat.setChannelCount(2); - - if (false && audioDevice.isFormatSupported(adjustedAudioFormat)) { - return true; - } else { - adjustedAudioFormat.setChannelCount(1); - } - } - - const int FORTY_FOUR = 44100; - - adjustedAudioFormat = desiredAudioFormat; + const int FORTY_FOUR = 44100; + adjustedAudioFormat = desiredAudioFormat; #ifdef Q_OS_ANDROID - adjustedAudioFormat.setSampleRate(FORTY_FOUR); + adjustedAudioFormat.setSampleRate(FORTY_FOUR); #else - const int HALF_FORTY_FOUR = FORTY_FOUR / 2; + const int HALF_FORTY_FOUR = FORTY_FOUR / 2; - if (audioDevice.supportedSampleRates().contains(AudioConstants::SAMPLE_RATE * 2)) { - // use 48, which is a sample downsample, upsample - adjustedAudioFormat.setSampleRate(AudioConstants::SAMPLE_RATE * 2); - } else if (audioDevice.supportedSampleRates().contains(HALF_FORTY_FOUR)) { - // use 22050, resample but closer to 24 - adjustedAudioFormat.setSampleRate(HALF_FORTY_FOUR); - } else if (audioDevice.supportedSampleRates().contains(FORTY_FOUR)) { - // use 48000, resample - adjustedAudioFormat.setSampleRate(FORTY_FOUR); - } + if (audioDevice.supportedSampleRates().contains(AudioConstants::SAMPLE_RATE * 2)) { + // use 48, which is a sample downsample, upsample + adjustedAudioFormat.setSampleRate(AudioConstants::SAMPLE_RATE * 2); + } else if (audioDevice.supportedSampleRates().contains(HALF_FORTY_FOUR)) { + // use 22050, resample but closer to 24 + adjustedAudioFormat.setSampleRate(HALF_FORTY_FOUR); + } else if (audioDevice.supportedSampleRates().contains(FORTY_FOUR)) { + // use 48000, resample + adjustedAudioFormat.setSampleRate(FORTY_FOUR); + } #endif - if (adjustedAudioFormat != desiredAudioFormat) { - // return the nearest in case it needs 2 channels - adjustedAudioFormat = audioDevice.nearestFormat(adjustedAudioFormat); - return true; - } else { - return false; - } - } else { - // set the adjustedAudioFormat to the desiredAudioFormat, since it will work - adjustedAudioFormat = desiredAudioFormat; + if (adjustedAudioFormat != desiredAudioFormat) { + // return the nearest in case it needs 2 channels + adjustedAudioFormat = audioDevice.nearestFormat(adjustedAudioFormat); return true; + } else { + return false; } } @@ -467,11 +447,6 @@ void AudioClient::stop() { // "switch" to invalid devices in order to shut down the state switchInputToAudioDevice(QAudioDeviceInfo()); switchOutputToAudioDevice(QAudioDeviceInfo()); - - if (_loopbackResampler) { - delete _loopbackResampler; - _loopbackResampler = NULL; - } } void AudioClient::handleAudioEnvironmentDataPacket(QSharedPointer message) { @@ -675,16 +650,10 @@ void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) { } } - // do we need to setup a resampler? - if (_inputFormat.sampleRate() != _outputFormat.sampleRate() && !_loopbackResampler) { - qCDebug(audioclient) << "Attemping to create a resampler for input format to output format for audio loopback."; - - assert(_inputFormat.sampleSize() == 16); - assert(_outputFormat.sampleSize() == 16); - int channelCount = (_inputFormat.channelCount() == 2 && _outputFormat.channelCount() == 2) ? 2 : 1; - - _loopbackResampler = new AudioSRC(_inputFormat.sampleRate(), _outputFormat.sampleRate(), channelCount); - } + // NOTE: we assume the inputFormat and the outputFormat are the same, since on any modern + // multimedia OS they should be. If there is a device that this is not true for, we can + // add back support to do resampling. + Q_ASSERT(_inputFormat.sampleRate() == _outputFormat.sampleRate()); static QByteArray loopBackByteArray; @@ -696,7 +665,8 @@ void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) { int16_t* inputSamples = reinterpret_cast(inputByteArray.data()); int16_t* loopbackSamples = reinterpret_cast(loopBackByteArray.data()); - possibleResampling(_loopbackResampler, + auto NO_RESAMPLER = nullptr; + possibleResampling(NO_RESAMPLER, inputSamples, loopbackSamples, numInputSamples, numLoopbackSamples, _inputFormat, _outputFormat); @@ -1039,12 +1009,6 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDevice _networkToOutputResampler = NULL; } - if (_loopbackResampler) { - // if we were using an input to output resample, delete it here - delete _loopbackResampler; - _loopbackResampler = NULL; - } - if (!outputDeviceInfo.isNull()) { qCDebug(audioclient) << "The audio output device " << outputDeviceInfo.deviceName() << "is available."; _outputAudioDeviceName = outputDeviceInfo.deviceName().trimmed(); diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 3a14c878f6..dc46db5657 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -260,7 +260,6 @@ private: // possible streams needed for resample AudioSRC* _inputToNetworkResampler; AudioSRC* _networkToOutputResampler; - AudioSRC* _loopbackResampler; // Adds Reverb void configureReverb(); From 1d8d1240cad863ab853123b874d78d677ce26e07 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 23 Jun 2016 10:38:46 -0700 Subject: [PATCH 133/171] remove override to avoid new Jenkins OSX warnings --- libraries/entities/src/ParticleEffectEntityItem.h | 4 ++-- libraries/entities/src/ZoneEntityItem.h | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/entities/src/ParticleEffectEntityItem.h b/libraries/entities/src/ParticleEffectEntityItem.h index 777f3b6cf6..9ddda62c8b 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.h +++ b/libraries/entities/src/ParticleEffectEntityItem.h @@ -95,8 +95,8 @@ public: void setAlphaSpread(float alphaSpread); float getAlphaSpread() const { return _alphaSpread; } - void setShapeType(ShapeType type) override; - virtual ShapeType getShapeType() const override { return _shapeType; } + void setShapeType(ShapeType type); + virtual ShapeType getShapeType() const { return _shapeType; } virtual void debugDump() const; diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 599e70f83e..f0f2a91d63 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -54,9 +54,9 @@ public: static bool getDrawZoneBoundaries() { return _drawZoneBoundaries; } static void setDrawZoneBoundaries(bool value) { _drawZoneBoundaries = value; } - virtual bool isReadyToComputeShape() override { return false; } - void setShapeType(ShapeType type) override { _shapeType = type; } - virtual ShapeType getShapeType() const override; + virtual bool isReadyToComputeShape() { return false; } + void setShapeType(ShapeType type) { _shapeType = type; } + virtual ShapeType getShapeType() const; virtual bool hasCompoundShapeURL() const { return !_compoundShapeURL.isEmpty(); } const QString getCompoundShapeURL() const { return _compoundShapeURL; } From a0bb55e4c8579679b7832542bb6b67459fb3c109 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 23 Jun 2016 10:48:29 -0700 Subject: [PATCH 134/171] coding standard fun --- scripts/system/libraries/entityCameraTool.js | 37 +++++++++++--------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/scripts/system/libraries/entityCameraTool.js b/scripts/system/libraries/entityCameraTool.js index 63f7c43fdc..301b60f550 100644 --- a/scripts/system/libraries/entityCameraTool.js +++ b/scripts/system/libraries/entityCameraTool.js @@ -141,7 +141,9 @@ CameraManager = function() { }; that.enable = function() { - if (Camera.mode == "independent" || that.enabled || HMD.active) return; + if (Camera.mode == "independent" || that.enabled || HMD.active) { + return; + } for (var i = 0; i < CAPTURED_KEYS.length; i++) { Controller.captureKeyEvents({ @@ -179,7 +181,9 @@ CameraManager = function() { } that.disable = function(ignoreCamera) { - if (!that.enabled) return; + if (!that.enabled) { + return; + } for (var i = 0; i < CAPTURED_KEYS.length; i++) { Controller.releaseKeyEvents({ @@ -352,27 +356,21 @@ CameraManager = function() { that.mousePressEvent = function(event) { if (cameraTool.mousePressEvent(event)) { - return true; } - - if (!that.enabled) return; + if (!that.enabled) { + return; + } if (event.isRightButton || (event.isLeftButton && event.isControl && !event.isShifted)) { - that.mode = MODE_ORBIT; } else if (event.isMiddleButton || (event.isLeftButton && event.isControl && event.isShifted)) { - - that.mode = MODE_PAN; } if (that.mode !== MODE_INACTIVE) { - - hasDragged = false; - return true; } @@ -381,7 +379,9 @@ CameraManager = function() { that.mouseReleaseEvent = function(event) { - if (!that.enabled) return; + if (!that.enabled) { + return; + } that.mode = MODE_INACTIVE; Reticle.setVisible(true); @@ -403,7 +403,9 @@ CameraManager = function() { }; that.wheelEvent = function(event) { - if (!that.enabled) return; + if (!that.enabled) { + return; + } var dZoom = -event.delta * SCROLL_SENSITIVITY; @@ -459,8 +461,12 @@ CameraManager = function() { } function normalizeDegrees(degrees) { - while (degrees > 180) degrees -= 360; - while (degrees < -180) degrees += 360; + while (degrees > 180) { + degrees -= 360; + } + while (degrees < -180) { + degrees += 360; + } return degrees; } @@ -483,7 +489,6 @@ CameraManager = function() { that.targetZoomDistance = clamp(that.targetZoomDistance, MIN_ZOOM_DISTANCE, MAX_ZOOM_DISTANCE); } - if (easing) { easingTime = Math.min(EASE_TIME, easingTime + dt); } From 6111e4952afdfced12aca6ac0eb1c2d1906793d8 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Thu, 23 Jun 2016 10:59:26 -0700 Subject: [PATCH 135/171] fix comments --- libraries/audio-client/src/AudioClient.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index ac055bb0f6..7628c09748 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -326,13 +326,13 @@ bool adjustedFormatForAudioDevice(const QAudioDeviceInfo& audioDevice, const int HALF_FORTY_FOUR = FORTY_FOUR / 2; if (audioDevice.supportedSampleRates().contains(AudioConstants::SAMPLE_RATE * 2)) { - // use 48, which is a sample downsample, upsample + // use 48, which is a simple downsample, upsample adjustedAudioFormat.setSampleRate(AudioConstants::SAMPLE_RATE * 2); } else if (audioDevice.supportedSampleRates().contains(HALF_FORTY_FOUR)) { // use 22050, resample but closer to 24 adjustedAudioFormat.setSampleRate(HALF_FORTY_FOUR); } else if (audioDevice.supportedSampleRates().contains(FORTY_FOUR)) { - // use 48000, resample + // use 44100, resample adjustedAudioFormat.setSampleRate(FORTY_FOUR); } #endif From 2fd38c1585f0f1d3b2a3da4df98777c5ca491050 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 23 Jun 2016 13:10:15 -0700 Subject: [PATCH 136/171] when adding or editing an entity from a script, make the _lastEdited in the transmitted properties be the same as the one in the Entity in the local tree --- libraries/entities/src/EntityScriptingInterface.cpp | 2 ++ libraries/entities/src/EntityTree.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index e0863041a1..55d93d5b5b 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -179,6 +179,7 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties } entity->setLastBroadcast(usecTimestampNow()); + propertiesWithSimID.setLastEdited(entity->getLastEdited()); } else { qCDebug(entities) << "script failed to add new Entity to local Octree"; success = false; @@ -376,6 +377,7 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& properties.setQueryAACube(entity->getQueryAACube()); } entity->setLastBroadcast(usecTimestampNow()); + properties.setLastEdited(entity->getLastEdited()); // if we've moved an entity with children, check/update the queryAACube of all descendents and tell the server // if they've changed. diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index ad0f929e59..7ebfecbe8e 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -898,7 +898,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c entityItemID, properties); endDecode = usecTimestampNow(); - const quint64 LAST_EDITED_SERVERSIDE_BUMP = 10000; // usec + const quint64 LAST_EDITED_SERVERSIDE_BUMP = 1; // usec if (!senderNode->getCanRez() && senderNode->getCanRezTmp()) { // this node is only allowed to rez temporary entities. if need be, cap the lifetime. if (properties.getLifetime() == ENTITY_ITEM_IMMORTAL_LIFETIME || From fc42a3aef52759e8f8cda7a665deeede5377345e Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 23 Jun 2016 15:36:47 -0700 Subject: [PATCH 137/171] Grab script hotspot work * Updated grab/equip logic to use sphere vs sphere tests, instead of sphere vs entity bounding box. * Added debug flag for visualizing grab spheres. * hotspot overlays are now updated as the objects they are attached to move. * You can now use the search beam to near grab large objects, as well as the sphere sphere test. * Optimized EntityPropertyCache to make a single call to Entities.getEntityProperties instead of three. * Moved grab script options from the "Developer > Hands" menu to the "Developer > Grab Script" menu. --- .../system/controllers/handControllerGrab.js | 312 +++++++++++------- 1 file changed, 200 insertions(+), 112 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 31f8ebd73f..1772e55015 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -10,10 +10,9 @@ // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, Reticle, Messages, setEntityCustomData, getEntityCustomData, vec3toStr, Xform */ +/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, Reticle, Messages, setEntityCustomData, getEntityCustomData, vec3toStr */ Script.include("/~/system/libraries/utils.js"); -Script.include("../libraries/Xform.js"); // // add lines where the hand ray picking is happening @@ -38,6 +37,9 @@ var THUMB_ON_VALUE = 0.5; var HAND_HEAD_MIX_RATIO = 0.0; // 0 = only use hands for search/move. 1 = only use head for search/move. var PICK_WITH_HAND_RAY = true; + +var DRAW_GRAB_SPHERES = false; +var DRAW_HAND_SPHERES = false; var DROP_WITHOUT_SHAKE = false; // @@ -68,16 +70,20 @@ var LINE_ENTITY_DIMENSIONS = { var LINE_LENGTH = 500; var PICK_MAX_DISTANCE = 500; // max length of pick-ray + // // near grabbing // -var GRAB_RADIUS = 0.06; // if the ray misses but an object is this close, it will still be selected +var EQUIP_RADIUS = 0.1; // radius used for palm vs equip-hotspot for equipping. + var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position -var NEAR_PICK_MAX_DISTANCE = 0.3; // max length of pick-ray for close grabbing to be selected + +var NEAR_GRAB_RADIUS = 0.15; // radius used for palm vs object for near grabbing. +var NEAR_GRAB_PICK_RADIUS = 0.25; // radius used for search ray vs object for near grabbing. + var PICK_BACKOFF_DISTANCE = 0.2; // helps when hand is intersecting the grabble object var NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-grabbed -var SHOW_GRAB_SPHERE = false; // draw a green sphere to show the grab search position and size var CHECK_TOO_FAR_UNEQUIP_TIME = 1.0; // seconds // @@ -254,7 +260,8 @@ function restore2DMode() { } } -// constructor +// EntityPropertiesCache is a helper class that contains a cache of entity properties. +// the hope is to prevent excess calls to Entity.getEntityProperties() function EntityPropertiesCache() { this.cache = {}; } @@ -265,34 +272,55 @@ EntityPropertiesCache.prototype.findEntities = function(position, radius) { var entities = Entities.findEntities(position, radius); var _this = this; entities.forEach(function (x) { - _this.addEntity(x); + _this.updateEntity(x); }); }; -EntityPropertiesCache.prototype.addEntity = function(entityID) { +EntityPropertiesCache.prototype.updateEntity = function(entityID) { var props = Entities.getEntityProperties(entityID, GRABBABLE_PROPERTIES); - var grabbableProps = getEntityCustomData(GRABBABLE_DATA_KEY, entityID, DEFAULT_GRABBABLE_DATA); - var grabProps = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); - var wearableProps = getEntityCustomData("wearable", entityID, {}); - this.cache[entityID] = { props: props, grabbableProps: grabbableProps, grabProps: grabProps, wearableProps: wearableProps }; + + // convert props.userData from a string to an object. + var userData = {}; + if (props.userData) { + try { + userData = JSON.parse(props.userData); + } catch(err) { + print("WARNING: malformed userData on " + entityID + ", name = " + props.name + ", error = " + err); + } + } + props.userData = userData; + + this.cache[entityID] = props; }; EntityPropertiesCache.prototype.getEntities = function() { return Object.keys(this.cache); } EntityPropertiesCache.prototype.getProps = function(entityID) { var obj = this.cache[entityID] - return obj ? obj.props : undefined; + return obj ? obj : undefined; }; EntityPropertiesCache.prototype.getGrabbableProps = function(entityID) { - var obj = this.cache[entityID] - return obj ? obj.grabbableProps : undefined; + var props = this.cache[entityID]; + if (props) { + return props.userData.grabbableKey ? props.userData.grabbableKey : DEFAULT_GRABBABLE_DATA; + } else { + return undefined; + } }; EntityPropertiesCache.prototype.getGrabProps = function(entityID) { - var obj = this.cache[entityID] - return obj ? obj.grabProps : undefined; + var props = this.cache[entityID]; + if (props) { + return props.userData.grabKey ? props.userData.grabKey : {}; + } else { + return undefined; + } }; EntityPropertiesCache.prototype.getWearableProps = function(entityID) { - var obj = this.cache[entityID] - return obj ? obj.wearableProps : undefined; + var props = this.cache[entityID]; + if (props) { + return props.userData.wearable ? props.userData.wearable : {}; + } else { + return undefined; + } }; function MyController(hand) { @@ -323,7 +351,6 @@ function MyController(hand) { //for visualizations this.overlayLine = null; this.particleBeamObject = null; - this.grabSphere = null; //for lights this.spotlight = null; @@ -384,7 +411,7 @@ function MyController(hand) { } this.setState = function(newState, reason) { - this.grabSphereOff(); + if (WANT_DEBUG || WANT_DEBUG_STATE) { var oldStateName = stateToName(this.state); var newStateName = stateToName(newState); @@ -491,39 +518,6 @@ function MyController(hand) { } } - this.grabSphereOn = function() { - var color = {red: 0, green: 255, blue: 0}; - if (this.grabSphere === null) { - var sphereProperties = { - position: this.getHandPosition(), - size: GRAB_RADIUS*2, - color: color, - alpha: 0.1, - solid: true, - ignoreRayIntersection: true, - drawInFront: true, // Even when burried inside of something, show it. - visible: true - } - this.grabSphere = Overlays.addOverlay("sphere", sphereProperties); - } else { - Overlays.editOverlay(this.grabSphere, { - position: this.getHandPosition(), - size: GRAB_RADIUS*2, - color: color, - alpha: 0.1, - solid: true, - visible: true - }); - } - } - - this.grabSphereOff = function() { - if (this.grabSphere !== null) { - Overlays.deleteOverlay(this.grabSphere); - this.grabSphere = null; - } - }; - this.overlayLineOn = function(closePoint, farPoint, color) { if (this.overlayLine === null) { var lineProperties = { @@ -873,51 +867,123 @@ function MyController(hand) { } }; - this.searchEnter = function() { - this.equipHotspotOverlays = []; + this.createHotspots = function () { + + var HAND_SPHERE_COLOR = { red: 90, green: 255, blue: 90 }; + var HAND_SPHERE_ALPHA = 0.7; + var HAND_SPHERE_RADIUS = 0.01; + + var EQUIP_SPHERE_COLOR = { red: 90, green: 255, blue: 90 }; + var EQUIP_SPHERE_ALPHA = 0.3; + + var NEAR_SPHERE_COLOR = { red: 90, green: 90, blue: 255 }; + var NEAR_SPHERE_ALPHA = 0.1; + + this.hotspotOverlays = []; + + if (DRAW_HAND_SPHERES) { + // add tiny spheres around the palm. + var handPosition = this.getHandPosition(); + var overlay = Overlays.addOverlay("sphere", { + position: handPosition, + size: HAND_SPHERE_RADIUS * 2, + color: HAND_SPHERE_COLOR, + alpha: HAND_SPHERE_ALPHA, + solid: true, + visible: true, + ignoreRayIntersection: true, + drawInFront: false + }); + + this.hotspotOverlays.push({ + entityID: undefined, + overlay: overlay, + type: "hand" + }); + } // find entities near the avatar that might be equipable. - var entities = Entities.findEntities(MyAvatar.position, HOTSPOT_DRAW_DISTANCE); - var i, l = entities.length; - for (i = 0; i < l; i++) { - var props = Entities.getEntityProperties(entities[i], GRABBABLE_PROPERTIES); - // does this entity have an attach point? - var wearableData = getEntityCustomData("wearable", entities[i], undefined); - if (wearableData && wearableData.joints) { - var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"; - if (wearableData.joints[handJointName]) { - var handOffsetPos = wearableData.joints[handJointName][0]; - var handOffsetRot = wearableData.joints[handJointName][1]; + this.entityPropertyCache.clear(); + this.entityPropertyCache.findEntities(MyAvatar.position, HOTSPOT_DRAW_DISTANCE); - var handOffsetXform = new Xform(handOffsetRot, handOffsetPos); - var objectXform = new Xform(props.rotation, props.position); - var overlayXform = Xform.mul(objectXform, handOffsetXform.inv()); + var _this = this; + this.entityPropertyCache.getEntities().forEach(function (entityID) { + if (_this.entityIsEquippableWithoutDistanceCheck(entityID)) { + var props = _this.entityPropertyCache.getProps(entityID); - // draw the hotspot - this.equipHotspotOverlays.push(Overlays.addOverlay("sphere", { - rotation: overlayXform.rot, - position: overlayXform.pos, - size: 0.2, - color: { red: 90, green: 255, blue: 90 }, - alpha: 0.7, - solid: true, - visible: true, - ignoreRayIntersection: true, - drawInFront: false - })); - } + overlay = Overlays.addOverlay("sphere", { + rotation: props.rotation, + position: props.position, + size: EQUIP_RADIUS * 2, + color: EQUIP_SPHERE_COLOR, + alpha: EQUIP_SPHERE_ALPHA, + solid: true, + visible: true, + ignoreRayIntersection: true, + drawInFront: false + }); + + _this.hotspotOverlays.push({ + entityID: entityID, + overlay: overlay, + type: "equip" + }); } - } + + if (DRAW_GRAB_SPHERES && _this.entityIsGrabbable(entityID)) { + var props = _this.entityPropertyCache.getProps(entityID); + + overlay = Overlays.addOverlay("sphere", { + rotation: props.rotation, + position: props.position, + size: NEAR_GRAB_RADIUS * 2, + color: NEAR_SPHERE_COLOR, + alpha: NEAR_SPHERE_ALPHA, + solid: true, + visible: true, + ignoreRayIntersection: true, + drawInFront: false + }); + + _this.hotspotOverlays.push({ + entityID: entityID, + overlay: overlay, + type: "near" + }); + } + }); + }; + + this.updateHotspots = function() { + var _this = this; + this.hotspotOverlays.forEach(function (overlayInfo) { + if (overlayInfo.type === "hand") { + Overlays.editOverlay(overlayInfo.overlay, { position: _this.getHandPosition() }); + } else if (overlayInfo.type === "equip") { + _this.entityPropertyCache.updateEntity(overlayInfo.entityID); + var props = _this.entityPropertyCache.getProps(overlayInfo.entityID); + Overlays.editOverlay(overlayInfo.overlay, { position: props.position, rotation: props.rotation }); + } else if (overlayInfo.type === "near") { + _this.entityPropertyCache.updateEntity(overlayInfo.entityID); + var props = _this.entityPropertyCache.getProps(overlayInfo.entityID); + Overlays.editOverlay(overlayInfo.overlay, { position: props.position, rotation: props.rotation }); + } + }); + }; + + this.destroyHotspots = function() { + this.hotspotOverlays.forEach(function (overlayInfo) { + Overlays.deleteOverlay(overlayInfo.overlay); + }); + this.hotspotOverlays = []; + }; + + this.searchEnter = function() { + this.createHotspots(); }; this.searchExit = function() { - - // delete all equip hotspots - var i, l = this.equipHotspotOverlays.length; - for (i = 0; i < l; i++) { - Overlays.deleteOverlay(this.equipHotspotOverlays[i]); - } - this.equipHotspotOverlays = []; + this.destroyHotspots(); }; /// @@ -984,7 +1050,13 @@ function MyController(hand) { return grabbableProps && grabbableProps.wantsTrigger; }; - this.entityIsEquippable = function (entityID, handPosition) { + this.entityIsEquippableWithoutDistanceCheck = function (entityID) { + var props = this.entityPropertyCache.getProps(entityID); + var handPosition = props.position; + return this.entityIsEquippableWithDistanceCheck(entityID, handPosition); + }; + + this.entityIsEquippableWithDistanceCheck = function (entityID, handPosition) { var props = this.entityPropertyCache.getProps(entityID); var distance = Vec3.distance(props.position, handPosition); var grabProps = this.entityPropertyCache.getGrabProps(entityID); @@ -998,9 +1070,9 @@ function MyController(hand) { return false; } - if (distance > NEAR_PICK_MAX_DISTANCE) { + if (distance > EQUIP_RADIUS) { if (debug) { - print("equip is skipping '" + props.name + "': too far away."); + print("equip is skipping '" + props.name + "': too far away, " + distance + " meters"); } return false; } @@ -1113,7 +1185,7 @@ function MyController(hand) { return true; }; - this.entityIsNearGrabbable = function(entityID, handPosition) { + this.entityIsNearGrabbable = function(entityID, handPosition, maxDistance) { if (!this.entityIsGrabbable(entityID)) { return false; @@ -1123,7 +1195,7 @@ function MyController(hand) { var distance = Vec3.distance(props.position, handPosition); var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); - if (distance > NEAR_PICK_MAX_DISTANCE) { + if (distance > maxDistance) { // too far away, don't grab if (debug) { print(" grab is skipping '" + props.name + "': too far away."); @@ -1138,6 +1210,8 @@ function MyController(hand) { var _this = this; var name; + this.updateHotspots(); + this.grabbedEntity = null; this.isInitialGrab = false; this.shouldResetParentOnRelease = false; @@ -1151,16 +1225,12 @@ function MyController(hand) { var handPosition = this.getHandPosition(); - if (SHOW_GRAB_SPHERE) { - this.grabSphereOn(); - } - this.entityPropertyCache.clear(); - this.entityPropertyCache.findEntities(handPosition, GRAB_RADIUS); + this.entityPropertyCache.findEntities(handPosition, NEAR_GRAB_RADIUS); var candidateEntities = this.entityPropertyCache.getEntities(); var equippableEntities = candidateEntities.filter(function (entity) { - return _this.entityIsEquippable(entity, handPosition); + return _this.entityIsEquippableWithDistanceCheck(entity, handPosition); }); var entity; @@ -1181,16 +1251,19 @@ function MyController(hand) { } } + var grabbableEntities = candidateEntities.filter(function (entity) { + return _this.entityIsNearGrabbable(entity, handPosition, NEAR_GRAB_RADIUS); + }); + var rayPickInfo = this.calcRayPickInfo(this.hand); this.intersectionDistance = rayPickInfo.distance; - if (rayPickInfo.entityID) { - candidateEntities.push(rayPickInfo.entityID); - this.entityPropertyCache.addEntity(rayPickInfo.entityID); - } - var grabbableEntities = candidateEntities.filter(function (entity) { - return _this.entityIsNearGrabbable(entity, handPosition); - }); + if (rayPickInfo.entityID) { + this.entityPropertyCache.updateEntity(rayPickInfo.entityID); + if (this.entityIsGrabbable(rayPickInfo.entityID) && rayPickInfo.distance < NEAR_GRAB_PICK_RADIUS) { + grabbableEntities.push(rayPickInfo.entityID); + } + } if (grabbableEntities.length > 0) { // sort by distance @@ -1732,11 +1805,11 @@ function MyController(hand) { this.lastUnequipCheckTime = now; if (props.parentID == MyAvatar.sessionUUID && - Vec3.length(props.localPosition) > NEAR_PICK_MAX_DISTANCE * 2.0) { + Vec3.length(props.localPosition) > NEAR_GRAB_PICK_RADIUS * 2.0) { var handPosition = this.getHandPosition(); // the center of the equipped object being far from the hand isn't enough to autoequip -- we also // need to fail the findEntities test. - var nearPickedCandidateEntities = Entities.findEntities(handPosition, GRAB_RADIUS); + var nearPickedCandidateEntities = Entities.findEntities(handPosition, EQUIP_RADIUS); if (nearPickedCandidateEntities.indexOf(this.grabbedEntity) == -1) { // for whatever reason, the held/equipped entity has been pulled away. ungrab or unequip. print("handControllerGrab -- autoreleasing held or equipped item because it is far from hand." + @@ -2182,17 +2255,32 @@ function cleanup() { Script.scriptEnding.connect(cleanup); Script.update.connect(update); +if (!Menu.menuExists("Developer > Grab Script")) { + Menu.addMenu("Developer > Grab Script"); +} + Menu.addMenuItem({ - menuName: "Developer > Hands", + menuName: "Developer > Grab Script", menuItemName: "Drop Without Shake", isCheckable: true, isChecked: DROP_WITHOUT_SHAKE }); +Menu.addMenuItem({ + menuName: "Developer > Grab Script", + menuItemName: "Draw Grab Spheres", + isCheckable: true, + isChecked: DRAW_GRAB_SPHERES +}); + function handleMenuItemEvent(menuItem) { if (menuItem === "Drop Without Shake") { DROP_WITHOUT_SHAKE = Menu.isOptionChecked("Drop Without Shake"); } + if (menuItem === "Draw Grab Spheres") { + DRAW_GRAB_SPHERES = Menu.isOptionChecked("Draw Grab Spheres"); + DRAW_HAND_SPHERES = DRAW_GRAB_SPHERES; + } } Menu.menuItemEvent.connect(handleMenuItemEvent); From f8391f00629535b9849d928b0075c0f032dc61f2 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 23 Jun 2016 16:51:15 -0700 Subject: [PATCH 138/171] fix reticle bugs --- interface/src/Application.cpp | 26 +++++++++---------- interface/src/Application.h | 1 + libraries/ui/src/OffscreenUi.cpp | 5 ++-- .../controllers/handControllerPointer.js | 21 +++++++++++---- 4 files changed, 32 insertions(+), 21 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 37c3b361bf..26da43fb58 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -921,17 +921,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : cycleCamera(); } else if (action == controller::toInt(controller::Action::UI_NAV_SELECT)) { if (!offscreenUi->navigationFocused()) { - auto reticlePosition = getApplicationCompositor().getReticlePosition(); - offscreenUi->toggleMenu(QPoint(reticlePosition.x, reticlePosition.y)); + toggleMenuUnderReticle(); } } else if (action == controller::toInt(controller::Action::CONTEXT_MENU)) { - auto reticlePosition = getApplicationCompositor().getReticlePosition(); - offscreenUi->toggleMenu(QPoint(reticlePosition.x, reticlePosition.y)); - } else if (action == controller::toInt(controller::Action::UI_NAV_SELECT)) { - if (!offscreenUi->navigationFocused()) { - auto reticlePosition = getApplicationCompositor().getReticlePosition(); - offscreenUi->toggleMenu(QPoint(reticlePosition.x, reticlePosition.y)); - } + toggleMenuUnderReticle(); } else if (action == controller::toInt(controller::Action::RETICLE_X)) { auto oldPos = getApplicationCompositor().getReticlePosition(); getApplicationCompositor().setReticlePosition({ oldPos.x + state, oldPos.y }); @@ -1240,7 +1233,16 @@ QString Application::getUserAgent() { return userAgent; } - +void Application::toggleMenuUnderReticle() const { + // In HMD, if the menu is near the mouse but not under it, the reticle can be at a significantly + // different depth. When you focus on the menu, the cursor can appear to your crossed eyes as both + // on the menu and off. + // Even in 2D, it is arguable whether the user would want the menu to be to the side. + const float X_LEFT_SHIFT = 50.0; + auto offscreenUi = DependencyManager::get(); + auto reticlePosition = getApplicationCompositor().getReticlePosition(); + offscreenUi->toggleMenu(QPoint(reticlePosition.x - X_LEFT_SHIFT, reticlePosition.y)); +} void Application::checkChangeCursor() { QMutexLocker locker(&_changeCursorLock); @@ -2462,9 +2464,7 @@ void Application::keyPressEvent(QKeyEvent* event) { void Application::keyReleaseEvent(QKeyEvent* event) { if (event->key() == Qt::Key_Alt && _altPressed && hasFocus()) { - auto offscreenUi = DependencyManager::get(); - auto reticlePosition = getApplicationCompositor().getReticlePosition(); - offscreenUi->toggleMenu(QPoint(reticlePosition.x, reticlePosition.y)); + toggleMenuUnderReticle(); } _keysPressed.remove(event->key()); diff --git a/interface/src/Application.h b/interface/src/Application.h index 5beaa5b455..6857ba2a3a 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -408,6 +408,7 @@ private: static void dragEnterEvent(QDragEnterEvent* event); void maybeToggleMenuVisible(QMouseEvent* event) const; + void toggleMenuUnderReticle() const; MainWindow* _window; QElapsedTimer& _sessionRunTimer; diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index fa1a31d196..1a7d4b2328 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -492,10 +492,9 @@ void OffscreenUi::unfocusWindows() { Q_ASSERT(invokeResult); } -void OffscreenUi::toggleMenu(const QPoint& screenPosition) { +void OffscreenUi::toggleMenu(const QPoint& screenPosition) { // caller should already have mapped using getReticlePosition emit showDesktop(); // we really only want to do this if you're showing the menu, but for now this works - auto virtualPos = mapToVirtualScreen(screenPosition, nullptr); - QMetaObject::invokeMethod(_desktop, "toggleMenu", Q_ARG(QVariant, virtualPos)); + QMetaObject::invokeMethod(_desktop, "toggleMenu", Q_ARG(QVariant, screenPosition)); } diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index 374be0d1a1..a0f1f47b3c 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -208,9 +208,9 @@ function isShakingMouse() { // True if the person is waving the mouse around try return isShaking; } var NON_LINEAR_DIVISOR = 2; -var MINIMUM_SEEK_DISTANCE = 0.01; -function updateSeeking() { - if (!Reticle.visible || isShakingMouse()) { +var MINIMUM_SEEK_DISTANCE = 0.1; +function updateSeeking(doNotStartSeeking) { + if (!doNotStartSeeking && (!Reticle.visible || isShakingMouse())) { if (!isSeeking) { print('Start seeking mouse.'); isSeeking = true; @@ -224,8 +224,8 @@ function updateSeeking() { if (!lookAt2D) { // If this happens, something has gone terribly wrong. print('Cannot seek without lookAt position'); isSeeking = false; - return; - } // E.g., if parallel to location in HUD + return; // E.g., if parallel to location in HUD + } var copy = Reticle.position; function updateDimension(axis) { var distanceBetween = lookAt2D[axis] - Reticle.position[axis]; @@ -353,6 +353,16 @@ clickMapping.from(rightTrigger.full).when(isPointingAtOverlayStartedNonFullTrigg clickMapping.from(leftTrigger.full).when(isPointingAtOverlayStartedNonFullTrigger(leftTrigger)).to(Controller.Actions.ReticleClick); clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(Controller.Actions.ContextMenu); clickMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(Controller.Actions.ContextMenu); +clickMapping.from(Controller.Hardware.Keyboard.RightMouseClicked).peek().to(function () { + // Allow the reticle depth to be set correctly: + // Wait a tick for the context menu to be displayed, and then simulate a (non-hand-controller) mouse move + // so that the system updates qml state (Reticle.pointingAtSystemOverlay) before it gives us a mouseMove. + // We don't want the system code to always do this for us, because, e.g., we do not want to get a mouseMove + // after the Left/RightSecondaryThumb gives us a context menu. Only from the mouse. + Script.setTimeout(function () { + Reticle.setPosition(Reticle.position); + }, 0); +}); // Partial smoothed trigger is activation. clickMapping.from(rightTrigger.partial).to(makeToggleAction(Controller.Standard.RightHand)); clickMapping.from(leftTrigger.partial).to(makeToggleAction(Controller.Standard.LeftHand)); @@ -386,6 +396,7 @@ function update() { expireMouseCursor(); clearSystemLaser(); } + updateSeeking(true); if (!handControllerLockOut.expired(now)) { return off(); // Let them use mouse it in peace. } From 7bd553c09cea7784828354085901adf788f37a0c Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 23 Jun 2016 17:03:15 -0700 Subject: [PATCH 139/171] near grab logic to uses sphere vs entity box instead of sphere vs sphere. Adjusted debug drawing accordingly. --- .../system/controllers/handControllerGrab.js | 70 +++++++++++++------ 1 file changed, 47 insertions(+), 23 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 26f977f9b0..b1cf3927d9 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -38,7 +38,7 @@ var HAND_HEAD_MIX_RATIO = 0.0; // 0 = only use hands for search/move. 1 = only var PICK_WITH_HAND_RAY = true; -var DRAW_GRAB_SPHERES = false; +var DRAW_GRAB_BOXES = false; var DRAW_HAND_SPHERES = false; var DROP_WITHOUT_SHAKE = false; @@ -80,6 +80,8 @@ var EQUIP_RADIUS = 0.1; // radius used for palm vs equip-hotspot for equipping. var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position var NEAR_GRAB_RADIUS = 0.15; // radius used for palm vs object for near grabbing. +var NEAR_GRAB_MAX_DISTANCE = 1.0; // you cannot grab objects that are this far away from your hand + var NEAR_GRAB_PICK_RADIUS = 0.25; // radius used for search ray vs object for near grabbing. var PICK_BACKOFF_DISTANCE = 0.2; // helps when hand is intersecting the grabble object @@ -870,26 +872,48 @@ function MyController(hand) { this.createHotspots = function () { var props, overlay; - var HAND_SPHERE_COLOR = { red: 90, green: 255, blue: 90 }; - var HAND_SPHERE_ALPHA = 0.7; - var HAND_SPHERE_RADIUS = 0.01; + var HAND_EQUIP_SPHERE_COLOR = { red: 90, green: 255, blue: 90 }; + var HAND_EQUIP_SPHERE_ALPHA = 0.7; + var HAND_EQUIP_SPHERE_RADIUS = 0.01; + + var HAND_GRAB_SPHERE_COLOR = { red: 90, green: 90, blue: 255 }; + var HAND_GRAB_SPHERE_ALPHA = 0.3; + var HAND_GRAB_SPHERE_RADIUS = NEAR_GRAB_RADIUS; var EQUIP_SPHERE_COLOR = { red: 90, green: 255, blue: 90 }; var EQUIP_SPHERE_ALPHA = 0.3; - var NEAR_SPHERE_COLOR = { red: 90, green: 90, blue: 255 }; - var NEAR_SPHERE_ALPHA = 0.1; + var GRAB_BOX_COLOR = { red: 90, green: 90, blue: 255 }; + var GRAB_BOX_ALPHA = 0.1; this.hotspotOverlays = []; if (DRAW_HAND_SPHERES) { - // add tiny spheres around the palm. + // add tiny green sphere around the palm. var handPosition = this.getHandPosition(); overlay = Overlays.addOverlay("sphere", { position: handPosition, - size: HAND_SPHERE_RADIUS * 2, - color: HAND_SPHERE_COLOR, - alpha: HAND_SPHERE_ALPHA, + size: HAND_EQUIP_SPHERE_RADIUS * 2, + color: HAND_EQUIP_SPHERE_COLOR, + alpha: HAND_EQUIP_SPHERE_ALPHA, + solid: true, + visible: true, + ignoreRayIntersection: true, + drawInFront: false + }); + + this.hotspotOverlays.push({ + entityID: undefined, + overlay: overlay, + type: "hand" + }); + + // add larger blue sphere around the palm. + overlay = Overlays.addOverlay("sphere", { + position: handPosition, + size: HAND_GRAB_SPHERE_RADIUS * 2, + color: HAND_GRAB_SPHERE_COLOR, + alpha: HAND_GRAB_SPHERE_ALPHA, solid: true, visible: true, ignoreRayIntersection: true, @@ -931,15 +955,15 @@ function MyController(hand) { }); } - if (DRAW_GRAB_SPHERES && _this.entityIsGrabbable(entityID)) { + if (DRAW_GRAB_BOXES && _this.entityIsGrabbable(entityID)) { props = _this.entityPropertyCache.getProps(entityID); - overlay = Overlays.addOverlay("sphere", { + overlay = Overlays.addOverlay("cube", { rotation: props.rotation, position: props.position, - size: NEAR_GRAB_RADIUS * 2, - color: NEAR_SPHERE_COLOR, - alpha: NEAR_SPHERE_ALPHA, + size: props.dimensions, //{x: props.dimensions.x, y: props.dimensions.y, z: props.dimensions.z}, + color: GRAB_BOX_COLOR, + alpha: GRAB_BOX_ALPHA, solid: true, visible: true, ignoreRayIntersection: true, @@ -1254,7 +1278,7 @@ function MyController(hand) { } var grabbableEntities = candidateEntities.filter(function (entity) { - return _this.entityIsNearGrabbable(entity, handPosition, NEAR_GRAB_RADIUS); + return _this.entityIsNearGrabbable(entity, handPosition, NEAR_GRAB_MAX_DISTANCE); }); var rayPickInfo = this.calcRayPickInfo(this.hand); @@ -1807,11 +1831,11 @@ function MyController(hand) { this.lastUnequipCheckTime = now; if (props.parentID == MyAvatar.sessionUUID && - Vec3.length(props.localPosition) > NEAR_GRAB_PICK_RADIUS * 2.0) { + Vec3.length(props.localPosition) > NEAR_GRAB_MAX_DISTANCE) { var handPosition = this.getHandPosition(); // the center of the equipped object being far from the hand isn't enough to autoequip -- we also // need to fail the findEntities test. - var nearPickedCandidateEntities = Entities.findEntities(handPosition, EQUIP_RADIUS); + var nearPickedCandidateEntities = Entities.findEntities(handPosition, NEAR_GRAB_RADIUS); if (nearPickedCandidateEntities.indexOf(this.grabbedEntity) == -1) { // for whatever reason, the held/equipped entity has been pulled away. ungrab or unequip. print("handControllerGrab -- autoreleasing held or equipped item because it is far from hand." + @@ -2270,18 +2294,18 @@ Menu.addMenuItem({ Menu.addMenuItem({ menuName: "Developer > Grab Script", - menuItemName: "Draw Grab Spheres", + menuItemName: "Draw Grab Boxes", isCheckable: true, - isChecked: DRAW_GRAB_SPHERES + isChecked: DRAW_GRAB_BOXES }); function handleMenuItemEvent(menuItem) { if (menuItem === "Drop Without Shake") { DROP_WITHOUT_SHAKE = Menu.isOptionChecked("Drop Without Shake"); } - if (menuItem === "Draw Grab Spheres") { - DRAW_GRAB_SPHERES = Menu.isOptionChecked("Draw Grab Spheres"); - DRAW_HAND_SPHERES = DRAW_GRAB_SPHERES; + if (menuItem === "Draw Grab Boxes") { + DRAW_GRAB_BOXES = Menu.isOptionChecked("Draw Grab Boxes"); + DRAW_HAND_SPHERES = DRAW_GRAB_BOXES; } } From 1de1c632afbb4f6f124108d32265e2aa3ec25c53 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Thu, 23 Jun 2016 17:16:42 -0700 Subject: [PATCH 140/171] use private_description instead of description for domain settings --- domain-server/resources/web/settings/js/settings.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index c2cb2ecb80..4188e86548 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -555,7 +555,7 @@ function createNewDomainID(description, justConnected) { // get the JSON object ready that we'll use to create a new domain var domainJSON = { "domain": { - "description": description + "private_description": description }, "access_token": $(Settings.ACCESS_TOKEN_SELECTOR).val() } @@ -748,8 +748,8 @@ function chooseFromHighFidelityDomains(clickedButton) { _.each(data.data.domains, function(domain){ var domainString = ""; - if (domain.description) { - domainString += '"' + domain.description + '" - '; + if (domain.private_description) { + domainString += '"' + domain.private_description + '" - '; } domainString += domain.id; From 13310923c4440ec6b0a97dbf89627dc63fe832ad Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Thu, 23 Jun 2016 19:38:23 -0700 Subject: [PATCH 141/171] reset to temp domain after logout --- .../resources/web/settings/js/settings.js | 2 ++ domain-server/src/DomainServer.cpp | 19 ++++++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 4188e86548..4f153d6190 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -457,6 +457,8 @@ function disonnectHighFidelityAccount() { }, function(){ // we need to post to settings to clear the access-token $(Settings.ACCESS_TOKEN_SELECTOR).val('').change(); + // reset the domain id to get a new temporary name + $(Settings.DOMAIN_ID_SELECTOR).val('').change(); saveSettings(); }); } diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 192c0d26e6..ca0d7728dd 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -111,12 +111,6 @@ DomainServer::DomainServer(int argc, char* argv[]) : return; } - // check for the temporary name parameter - const QString GET_TEMPORARY_NAME_SWITCH = "--get-temp-name"; - if (args.contains(GET_TEMPORARY_NAME_SWITCH)) { - getTemporaryName(); - } - setupNodeListAndAssignments(); setupAutomaticNetworking(); if (!getID().isNull()) { @@ -125,6 +119,12 @@ DomainServer::DomainServer(int argc, char* argv[]) : sendHeartbeatToMetaverse(); } + // check for the temporary name parameter + const QString GET_TEMPORARY_NAME_SWITCH = "--get-temp-name"; + if (args.contains(GET_TEMPORARY_NAME_SWITCH)) { + getTemporaryName(); + } + _gatekeeper.preloadAllowedUserPublicKeys(); // so they can connect on first request _metadata = new DomainMetadata(this); @@ -251,12 +251,13 @@ void DomainServer::getTemporaryName(bool force) { // check if we already have a domain ID const QVariant* idValueVariant = valueForKeyPath(_settingsManager.getSettingsMap(), METAVERSE_DOMAIN_ID_KEY_PATH); + qInfo() << "Requesting temporary domain name"; if (idValueVariant) { - qWarning() << "Temporary domain name requested but a domain ID is already present in domain-server settings."; + qDebug() << "A domain ID is already present in domain-server settings:" << idValueVariant->toString(); if (force) { - qWarning() << "Temporary domain name will be requested to replace it."; + qDebug() << "Requesting temporary domain name to replace current ID:" << getID(); } else { - qWarning() << "Temporary domain name will not be requested."; + qInfo() << "Abandoning request of temporary domain name."; return; } } From b1b378a91feb062e160cf794002e59c13e5ed11b Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Thu, 23 Jun 2016 19:38:41 -0700 Subject: [PATCH 142/171] add back access token to domain-server --- domain-server/src/DomainServer.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index ca0d7728dd..6e5c1fd361 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -470,6 +470,9 @@ bool DomainServer::resetAccountManagerAccessToken() { void DomainServer::setupAutomaticNetworking() { qDebug() << "Updating automatic networking setting in domain-server to" << _automaticNetworkingSetting; + + resetAccountManagerAccessToken(); + _automaticNetworkingSetting = _settingsManager.valueOrDefaultValueForKeyPath(METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH).toString(); From 1fcd7aa0c44d714fa4505b9b97fdad698ed9a674 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Thu, 23 Jun 2016 19:54:34 -0700 Subject: [PATCH 143/171] add build version to heartbeat --- domain-server/src/DomainServer.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 223cab61da..d421c6554b 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1081,6 +1081,11 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) { // Setup the domain object to send to the data server QJsonObject domainObject; + // add the version + static const QString VERSION_KEY = "version"; + domainObject[VERSION_KEY] = BuildInfo::VERSION; + + // add networking if (!networkAddress.isEmpty()) { static const QString PUBLIC_NETWORK_ADDRESS_KEY = "network_address"; domainObject[PUBLIC_NETWORK_ADDRESS_KEY] = networkAddress; @@ -1089,10 +1094,10 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) { static const QString AUTOMATIC_NETWORKING_KEY = "automatic_networking"; domainObject[AUTOMATIC_NETWORKING_KEY] = _automaticNetworkingSetting; - // add a flag to indicate if this domain uses restricted access - for now that will exclude it from listings - const QString RESTRICTED_ACCESS_FLAG = "restricted"; - // consider the domain to have restricted access if "anonymous" connections can't connect to the domain. + // add access level for anonymous connections + // consider the domain to be "restricted" if anonymous connections are disallowed + static const QString RESTRICTED_ACCESS_FLAG = "restricted"; NodePermissions anonymousPermissions = _settingsManager.getPermissionsForName(NodePermissions::standardNameAnonymous); domainObject[RESTRICTED_ACCESS_FLAG] = !anonymousPermissions.canConnectToDomain; From 15fbff4ffb1ebc2225d7234ee820c2334978a755 Mon Sep 17 00:00:00 2001 From: Zander Otavka Date: Fri, 24 Jun 2016 10:09:52 -0700 Subject: [PATCH 144/171] Add eslintrc file --- scripts/.eslintrc.js | 72 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 scripts/.eslintrc.js diff --git a/scripts/.eslintrc.js b/scripts/.eslintrc.js new file mode 100644 index 0000000000..e866713b11 --- /dev/null +++ b/scripts/.eslintrc.js @@ -0,0 +1,72 @@ +module.exports = { + "root": true, + "extends": "eslint:recommended", + "parserOptions": { + "ecmaVersion": 5 + }, + "globals": { + "Account": false, + "AnimationCache": false, + "Assets": false, + "Audio": false, + "AudioDevice": false, + "AudioEffectOptions": false, + "AvatarList": false, + "AvatarManager": false, + "Camera": false, + "Clipboard": false, + "Controller": false, + "DialogsManager": false, + "Entities": false, + "FaceTracker": false, + "GlobalServices": false, + "HMD": false, + "LODManager": false, + "Mat4": false, + "Menu": false, + "Messages": false, + "ModelCache": false, + "MyAvatar": false, + "Overlays": false, + "Paths": false, + "Quat": false, + "Rates": false, + "Recording": false, + "Reticle": false, + "Scene": false, + "Script": false, + "ScriptDiscoveryService": false, + "Settings": false, + "SoundCache": false, + "Stats": false, + "TextureCache": false, + "Uuid": false, + "UndoStack": false, + "Vec3": false, + "WebSocket": false, + "WebWindow": false, + "Window": false, + "XMLHttpRequest": false, + "location": false, + "print": false + }, + "rules": { + "brace-style": ["error", "1tbs", { "allowSingleLine": false }], + "comma-dangle": ["error", "only-multiline"], + "camelcase": ["error"], + "curly": ["error", "all"], + "indent": ["error", 4, { "SwitchCase": 1 }], + "keyword-spacing": ["error", { "before": true, "after": true }], + "max-len": ["error", 128, 4], + "new-cap": ["error"], + //"no-magic-numbers": ["error", { "ignore": [0, 1], "ignoreArrayIndexes": true }], + "no-multiple-empty-lines": ["error"], + "no-multi-spaces": ["error"], + "no-unused-vars": ["error", { "args": "none", "vars": "local" }], + "semi": ["error", "always"], + "spaced-comment": ["error", "always", { + "line": { "markers": ["/"] } + }], + "space-before-function-paren": ["error", "never"] + } +}; From 88ad570024a927d80b24fbc036897fa7bb0b971f Mon Sep 17 00:00:00 2001 From: Zander Otavka Date: Fri, 24 Jun 2016 10:16:26 -0700 Subject: [PATCH 145/171] Revert "Uses spaces, not tabs" This reverts commit 7e5b9db13a688100d9d587c1a4147e6a65f15e78. --- interface/src/Application.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 53b9c18360..ddfa5dae8b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5349,14 +5349,14 @@ void Application::updateDisplayMode() { // reset the avatar, to set head and hand palms back to a reasonable default pose. getMyAvatar()->reset(false); - - // go into first person when they are in HMD mode, since 3rd person HMD is dumb - if (isHMDMode() && !menu->isOptionChecked(MenuOption::FirstPerson)) { - menu->setIsOptionChecked(MenuOption::FirstPerson, true); - cameraMenuChanged(); - } - - Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin"); + + // go into first person when they are in HMD mode, since 3rd person HMD is dumb + if (isHMDMode() && !menu->isOptionChecked(MenuOption::FirstPerson)) { + menu->setIsOptionChecked(MenuOption::FirstPerson, true); + cameraMenuChanged(); + } + + Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin"); } mat4 Application::getEyeProjection(int eye) const { From 41d38ab63045405b7379a1516ebd7056460e97bf Mon Sep 17 00:00:00 2001 From: Zander Otavka Date: Fri, 24 Jun 2016 10:16:40 -0700 Subject: [PATCH 146/171] Revert "Automatically enter first person when in HMD mode" This reverts commit 3c9f3f392736b56a0641a6f6707c00077b2871bf. --- interface/src/Application.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ddfa5dae8b..d20a62fc7b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5350,13 +5350,7 @@ void Application::updateDisplayMode() { // reset the avatar, to set head and hand palms back to a reasonable default pose. getMyAvatar()->reset(false); - // go into first person when they are in HMD mode, since 3rd person HMD is dumb - if (isHMDMode() && !menu->isOptionChecked(MenuOption::FirstPerson)) { - menu->setIsOptionChecked(MenuOption::FirstPerson, true); - cameraMenuChanged(); - } - - Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin"); + Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin"); } mat4 Application::getEyeProjection(int eye) const { From 39ff03cfb51acf0bd719fd5c39d1ff8caaf2a357 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Fri, 24 Jun 2016 10:53:46 -0700 Subject: [PATCH 147/171] reduce log spam in Vive plugins --- plugins/openvr/src/OpenVrDisplayPlugin.cpp | 4 +++- plugins/openvr/src/OpenVrHelpers.cpp | 28 ++++++++++++++++------ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 5233ad644a..cfb374e3bd 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -96,7 +96,9 @@ bool OpenVrDisplayPlugin::internalActivate() { glm::vec3 uiPos(0.0f, UI_HEIGHT, UI_RADIUS - (0.5f * zSize) - UI_Z_OFFSET); _sensorResetMat = glm::inverse(createMatFromQuatAndPos(glm::quat(), uiPos)); } else { - qDebug() << "OpenVR: error could not get chaperone pointer"; + #if DEV_BUILD + qDebug() << "OpenVR: error could not get chaperone pointer"; + #endif } return Parent::internalActivate(); diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index e71c8942d6..399712d920 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -64,17 +64,25 @@ vr::IVRSystem* acquireOpenVrSystem() { if (hmdPresent) { Lock lock(mutex); if (!activeHmd) { - qCDebug(displayplugins) << "OpenVR: No vr::IVRSystem instance active, building"; + #if DEV_BUILD + qCDebug(displayplugins) << "OpenVR: No vr::IVRSystem instance active, building"; + #endif vr::EVRInitError eError = vr::VRInitError_None; activeHmd = vr::VR_Init(&eError, vr::VRApplication_Scene); - qCDebug(displayplugins) << "OpenVR display: HMD is " << activeHmd << " error is " << eError; + #if DEV_BUILD + qCDebug(displayplugins) << "OpenVR display: HMD is " << activeHmd << " error is " << eError; + #endif } if (activeHmd) { - qCDebug(displayplugins) << "OpenVR: incrementing refcount"; + #if DEV_BUILD + qCDebug(displayplugins) << "OpenVR: incrementing refcount"; + #endif ++refCount; } } else { - qCDebug(displayplugins) << "OpenVR: no hmd present"; + #if DEV_BUILD + qCDebug(displayplugins) << "OpenVR: no hmd present"; + #endif } return activeHmd; } @@ -82,10 +90,14 @@ vr::IVRSystem* acquireOpenVrSystem() { void releaseOpenVrSystem() { if (activeHmd) { Lock lock(mutex); - qCDebug(displayplugins) << "OpenVR: decrementing refcount"; + #if DEV_BUILD + qCDebug(displayplugins) << "OpenVR: decrementing refcount"; + #endif --refCount; if (0 == refCount) { - qCDebug(displayplugins) << "OpenVR: zero refcount, deallocate VR system"; + #if DEV_BUILD + qCDebug(displayplugins) << "OpenVR: zero refcount, deallocate VR system"; + #endif vr::VR_Shutdown(); activeHmd = nullptr; } @@ -261,7 +273,9 @@ void handleOpenVrEvents() { default: break; } - qDebug() << "OpenVR: Event " << activeHmd->GetEventTypeNameFromEnum((vr::EVREventType)event.eventType) << "(" << event.eventType << ")"; + #if DEV_BUILD + qDebug() << "OpenVR: Event " << activeHmd->GetEventTypeNameFromEnum((vr::EVREventType)event.eventType) << "(" << event.eventType << ")"; + #endif } } From a095da31fd8c1422058da1de0622554124f4f56c Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 24 Jun 2016 11:48:55 -0700 Subject: [PATCH 148/171] Fix initial visibility of QML windows from scripts --- libraries/ui/src/QmlWindowClass.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp index b8834f0549..c0eba4abf3 100644 --- a/libraries/ui/src/QmlWindowClass.cpp +++ b/libraries/ui/src/QmlWindowClass.cpp @@ -118,7 +118,7 @@ void QmlWindowClass::initQml(QVariantMap properties) { } bool visible = !properties.contains(VISIBILE_PROPERTY) || properties[VISIBILE_PROPERTY].toBool(); - object->setProperty(VISIBILE_PROPERTY, visible); + object->setProperty(OFFSCREEN_VISIBILITY_PROPERTY, visible); object->setProperty(SOURCE_PROPERTY, _source); // Forward messages received from QML on to the script From d45694836fe3ffbfe21b8cc0b2642bbadd05e72c Mon Sep 17 00:00:00 2001 From: Zander Otavka Date: Fri, 24 Jun 2016 11:57:12 -0700 Subject: [PATCH 149/171] Add signal for display mode change in HMD --- interface/src/scripting/HMDScriptingInterface.cpp | 3 +++ .../src/display-plugins/AbstractHMDScriptingInterface.h | 1 + 2 files changed, 4 insertions(+) diff --git a/interface/src/scripting/HMDScriptingInterface.cpp b/interface/src/scripting/HMDScriptingInterface.cpp index 7bf1547a3c..9b633ef96d 100644 --- a/interface/src/scripting/HMDScriptingInterface.cpp +++ b/interface/src/scripting/HMDScriptingInterface.cpp @@ -19,6 +19,9 @@ #include "Application.h" HMDScriptingInterface::HMDScriptingInterface() { + connect(qApp, &Application::activeDisplayPluginChanged, [this]{ + emit displayModeChanged(isHMDMode()); + }); } glm::vec3 HMDScriptingInterface::calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction) const { diff --git a/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h b/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h index 5df58ce677..f260fa959f 100644 --- a/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h +++ b/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h @@ -31,6 +31,7 @@ public: signals: void IPDScaleChanged(); + void displayModeChanged(bool isHMDMode); private: float _IPDScale{ 1.0 }; From 0c56af7ae8042982eb1f0969261c7908e513d5f7 Mon Sep 17 00:00:00 2001 From: Zander Otavka Date: Fri, 24 Jun 2016 11:57:50 -0700 Subject: [PATCH 150/171] Add default script to enter first person for HMD --- scripts/defaultScripts.js | 1 + scripts/system/firstPersonHMD.js | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 scripts/system/firstPersonHMD.js diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 2a050d183e..f6d9d95ada 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -21,3 +21,4 @@ Script.load("system/controllers/handControllerPointer.js"); Script.load("system/controllers/squeezeHands.js"); Script.load("system/controllers/grab.js"); Script.load("system/dialTone.js"); +Script.load("system/firstPersonHMD.js"); diff --git a/scripts/system/firstPersonHMD.js b/scripts/system/firstPersonHMD.js new file mode 100644 index 0000000000..082c6304be --- /dev/null +++ b/scripts/system/firstPersonHMD.js @@ -0,0 +1,17 @@ +// +// firstPersonHMD.js +// system +// +// Created by Zander Otavka on 6/24/16 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// Automatically enter first person mode when entering HMD mode +HMD.displayModeChanged.connect(function(isHMDMode) { + if (isHMDMode) { + Camera.setModeString("first person"); + } +}); From 8bf72f28da84d177e67e920fa1e3f1b243a587b6 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 24 Jun 2016 12:06:02 -0700 Subject: [PATCH 151/171] Fix for grab script search ray length --- scripts/system/controllers/handControllerGrab.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 7706132c58..93d2269584 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -1173,10 +1173,12 @@ function MyController(hand) { } var rayPickInfo = this.calcRayPickInfo(this.hand); - this.intersectionDistance = rayPickInfo.distance; if (rayPickInfo.entityID) { candidateEntities.push(rayPickInfo.entityID); this.entityPropertyCache.addEntity(rayPickInfo.entityID); + this.intersectionDistance = rayPickInfo.distance; + } else { + this.intersectionDistance = 0; } var grabbableEntities = candidateEntities.filter(function (entity) { From dd023e16fd53c500b6741fd6b7ff60ff05a61501 Mon Sep 17 00:00:00 2001 From: Zander Otavka Date: Fri, 24 Jun 2016 13:16:24 -0700 Subject: [PATCH 152/171] Tabs -> spaces --- interface/src/scripting/HMDScriptingInterface.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/scripting/HMDScriptingInterface.cpp b/interface/src/scripting/HMDScriptingInterface.cpp index 9b633ef96d..49c6635fb3 100644 --- a/interface/src/scripting/HMDScriptingInterface.cpp +++ b/interface/src/scripting/HMDScriptingInterface.cpp @@ -19,9 +19,9 @@ #include "Application.h" HMDScriptingInterface::HMDScriptingInterface() { - connect(qApp, &Application::activeDisplayPluginChanged, [this]{ - emit displayModeChanged(isHMDMode()); - }); + connect(qApp, &Application::activeDisplayPluginChanged, [this]{ + emit displayModeChanged(isHMDMode()); + }); } glm::vec3 HMDScriptingInterface::calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction) const { From 7da854d98c70cf4272c4d68c84f898789e6f28e3 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 24 Jun 2016 13:35:47 -0700 Subject: [PATCH 153/171] do not reset hud from button --- interface/src/ui/OverlayConductor.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index 6a99641ce4..7d0dc6c650 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -118,10 +118,12 @@ void OverlayConductor::update(float dt) { bool isDriving = updateAvatarHasDriveInput(); bool drivingChanged = prevDriving != isDriving; bool isAtRest = updateAvatarIsAtRest(); + bool shouldRecenter = false; if (_flags & SuppressedByDrive) { if (!isDriving) { _flags &= ~SuppressedByDrive; + shouldRecenter = true; } } else { if (myAvatar->getClearOverlayWhenMoving() && drivingChanged && isDriving) { @@ -132,6 +134,7 @@ void OverlayConductor::update(float dt) { if (_flags & SuppressedByHead) { if (isAtRest) { _flags &= ~SuppressedByHead; + shouldRecenter = true; } } else { if (_hmdMode && headOutsideOverlay()) { @@ -143,8 +146,8 @@ void OverlayConductor::update(float dt) { bool targetVisible = Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && (0 == (_flags & SuppressMask)); if (targetVisible != currentVisible) { offscreenUi->setPinned(!targetVisible); - if (targetVisible && _hmdMode) { - centerUI(); - } + } + if (shouldRecenter) { + centerUI(); } } From 2ae0a7defc2bfbcdb4fc030d56153e1736363d37 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 24 Jun 2016 14:46:46 -0700 Subject: [PATCH 154/171] If there are TWO conditions holding things back, weight for them both to clear. --- interface/src/ui/OverlayConductor.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index 7d0dc6c650..e18522cb2f 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -123,7 +123,9 @@ void OverlayConductor::update(float dt) { if (_flags & SuppressedByDrive) { if (!isDriving) { _flags &= ~SuppressedByDrive; - shouldRecenter = true; + if (_flags & SuppressMask) { + shouldRecenter = true; + } } } else { if (myAvatar->getClearOverlayWhenMoving() && drivingChanged && isDriving) { @@ -134,7 +136,9 @@ void OverlayConductor::update(float dt) { if (_flags & SuppressedByHead) { if (isAtRest) { _flags &= ~SuppressedByHead; - shouldRecenter = true; + if (_flags & SuppressMask) { + shouldRecenter = true; + } } } else { if (_hmdMode && headOutsideOverlay()) { From 9ae3c386166a6834c8c571324189c65d1fe51b69 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 24 Jun 2016 15:26:24 -0700 Subject: [PATCH 155/171] doh! --- interface/src/ui/OverlayConductor.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index e18522cb2f..2ee106b6b3 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -123,9 +123,7 @@ void OverlayConductor::update(float dt) { if (_flags & SuppressedByDrive) { if (!isDriving) { _flags &= ~SuppressedByDrive; - if (_flags & SuppressMask) { - shouldRecenter = true; - } + shouldRecenter = true; } } else { if (myAvatar->getClearOverlayWhenMoving() && drivingChanged && isDriving) { @@ -136,9 +134,7 @@ void OverlayConductor::update(float dt) { if (_flags & SuppressedByHead) { if (isAtRest) { _flags &= ~SuppressedByHead; - if (_flags & SuppressMask) { - shouldRecenter = true; - } + shouldRecenter = true; } } else { if (_hmdMode && headOutsideOverlay()) { @@ -151,7 +147,7 @@ void OverlayConductor::update(float dt) { if (targetVisible != currentVisible) { offscreenUi->setPinned(!targetVisible); } - if (shouldRecenter) { + if (shouldRecenter && !_flags) { centerUI(); } } From 5e69af83c69c94e78ec813cd7aea4bfbe270e031 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 22 Jun 2016 17:53:38 -0700 Subject: [PATCH 156/171] can't enter independent mode through edit.js in HMD mode --- scripts/system/libraries/entityCameraTool.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/libraries/entityCameraTool.js b/scripts/system/libraries/entityCameraTool.js index a8e9335956..63f7c43fdc 100644 --- a/scripts/system/libraries/entityCameraTool.js +++ b/scripts/system/libraries/entityCameraTool.js @@ -141,7 +141,7 @@ CameraManager = function() { }; that.enable = function() { - if (Camera.mode == "independent" || that.enabled) return; + if (Camera.mode == "independent" || that.enabled || HMD.active) return; for (var i = 0; i < CAPTURED_KEYS.length; i++) { Controller.captureKeyEvents({ From d5feac94f4cb3581f755cda07fbda4261f0a89bf Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 23 Jun 2016 10:48:29 -0700 Subject: [PATCH 157/171] coding standard fun --- scripts/system/libraries/entityCameraTool.js | 37 +++++++++++--------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/scripts/system/libraries/entityCameraTool.js b/scripts/system/libraries/entityCameraTool.js index 63f7c43fdc..301b60f550 100644 --- a/scripts/system/libraries/entityCameraTool.js +++ b/scripts/system/libraries/entityCameraTool.js @@ -141,7 +141,9 @@ CameraManager = function() { }; that.enable = function() { - if (Camera.mode == "independent" || that.enabled || HMD.active) return; + if (Camera.mode == "independent" || that.enabled || HMD.active) { + return; + } for (var i = 0; i < CAPTURED_KEYS.length; i++) { Controller.captureKeyEvents({ @@ -179,7 +181,9 @@ CameraManager = function() { } that.disable = function(ignoreCamera) { - if (!that.enabled) return; + if (!that.enabled) { + return; + } for (var i = 0; i < CAPTURED_KEYS.length; i++) { Controller.releaseKeyEvents({ @@ -352,27 +356,21 @@ CameraManager = function() { that.mousePressEvent = function(event) { if (cameraTool.mousePressEvent(event)) { - return true; } - - if (!that.enabled) return; + if (!that.enabled) { + return; + } if (event.isRightButton || (event.isLeftButton && event.isControl && !event.isShifted)) { - that.mode = MODE_ORBIT; } else if (event.isMiddleButton || (event.isLeftButton && event.isControl && event.isShifted)) { - - that.mode = MODE_PAN; } if (that.mode !== MODE_INACTIVE) { - - hasDragged = false; - return true; } @@ -381,7 +379,9 @@ CameraManager = function() { that.mouseReleaseEvent = function(event) { - if (!that.enabled) return; + if (!that.enabled) { + return; + } that.mode = MODE_INACTIVE; Reticle.setVisible(true); @@ -403,7 +403,9 @@ CameraManager = function() { }; that.wheelEvent = function(event) { - if (!that.enabled) return; + if (!that.enabled) { + return; + } var dZoom = -event.delta * SCROLL_SENSITIVITY; @@ -459,8 +461,12 @@ CameraManager = function() { } function normalizeDegrees(degrees) { - while (degrees > 180) degrees -= 360; - while (degrees < -180) degrees += 360; + while (degrees > 180) { + degrees -= 360; + } + while (degrees < -180) { + degrees += 360; + } return degrees; } @@ -483,7 +489,6 @@ CameraManager = function() { that.targetZoomDistance = clamp(that.targetZoomDistance, MIN_ZOOM_DISTANCE, MAX_ZOOM_DISTANCE); } - if (easing) { easingTime = Math.min(EASE_TIME, easingTime + dt); } From f90a351400c5de2a89a30c25dc72a00fe25ca8ab Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 24 Jun 2016 16:31:24 -0700 Subject: [PATCH 158/171] Don't reposition windows prior to selecting the initial display plugin --- interface/resources/qml/desktop/Desktop.qml | 16 +++++++++++++++- interface/src/Application.cpp | 8 +++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index 9f10cfc64a..27fa9692b9 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -24,6 +24,13 @@ FocusScope { readonly property int invalid_position: -9999; property rect recommendedRect: Qt.rect(0,0,0,0); property var expectedChildren; + property bool repositionLocked: true + + onRepositionLockedChanged: { + if (!repositionLocked) { + d.handleSizeChanged(); + } + } onHeightChanged: d.handleSizeChanged(); @@ -52,11 +59,14 @@ FocusScope { readonly property real menu: 8000 } - QtObject { id: d function handleSizeChanged() { + if (desktop.repositionLocked) { + return; + } + var oldRecommendedRect = recommendedRect; var newRecommendedRectJS = (typeof Controller === "undefined") ? Qt.rect(0,0,0,0) : Controller.getRecommendedOverlayRect(); var newRecommendedRect = Qt.rect(newRecommendedRectJS.x, newRecommendedRectJS.y, @@ -235,6 +245,10 @@ FocusScope { } function repositionAll() { + if (desktop.repositionLocked) { + return; + } + var oldRecommendedRect = recommendedRect; var oldRecommendedDimmensions = { x: oldRecommendedRect.width, y: oldRecommendedRect.height }; var newRecommendedRect = Controller.getRecommendedOverlayRect(); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 26da43fb58..858b86025f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -963,6 +963,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : updateHeartbeat(); loadSettings(); + + // Now that we've loaded the menu and thus switched to the previous display plugin + // we can unlock the desktop repositioning code, since all the positions will be + // relative to the desktop size for this plugin + auto offscreenUi = DependencyManager::get(); + offscreenUi->getDesktop()->setProperty("repositionLocked", false); + // Make sure we don't time out during slow operations at startup updateHeartbeat(); @@ -5348,7 +5355,6 @@ void Application::updateDisplayMode() { _displayPlugin = newDisplayPlugin; } - emit activeDisplayPluginChanged(); // reset the avatar, to set head and hand palms back to a reasonable default pose. From 2786061d71d96ce6157c2b98b59a59f292347736 Mon Sep 17 00:00:00 2001 From: Zander Otavka Date: Fri, 24 Jun 2016 16:34:54 -0700 Subject: [PATCH 159/171] Fix bug with entity adding w/o permission Entities could be added to the local tree, but be rejected by the server because of a lack of permissions. --- libraries/entities/src/EntityTree.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 7ebfecbe8e..21e5865c09 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -320,6 +320,11 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti return nullptr; } + if (!properties.getClientOnly() && getIsClient() && + !nodeList->getThisNodeCanRez() && !nodeList->getThisNodeCanRezTmp()) { + return nullptr; + } + bool recordCreationTime = false; if (props.getCreated() == UNKNOWN_CREATED_TIME) { // the entity's creation time was not specified in properties, which means this is a NEW entity From a4da63c1ff33ad58f04ca35ed7d31647400b71bd Mon Sep 17 00:00:00 2001 From: Zander Otavka Date: Fri, 24 Jun 2016 16:39:57 -0700 Subject: [PATCH 160/171] Fix bug with Entities.addEntity returning bad id If addEntity failed for whatever reason (eg. lack of permission), it would return a non-null UUID. Fixes [FogBugz case #365](https://highfidelity.fogbugz.com/f/cases/356/Entities-addEntity-can-return-a-UUID-even-when-unsuccessful). --- libraries/entities/src/EntityScriptingInterface.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 55d93d5b5b..856e526b4c 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -191,9 +191,11 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties if (success) { emit debitEnergySource(cost); queueEntityMessage(PacketType::EntityAdd, id, propertiesWithSimID); - } - return id; + return id; + } else { + return QUuid(); + } } QUuid EntityScriptingInterface::addModelEntity(const QString& name, const QString& modelUrl, const glm::vec3& position) { From 8a5369fcb15e98cdf2143d690834d29fa58f1ec7 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 25 Jun 2016 12:03:36 +1200 Subject: [PATCH 161/171] Undo hide users.js when logged out --- scripts/system/users.js | 109 +++++++++++++++------------------------- 1 file changed, 41 insertions(+), 68 deletions(-) diff --git a/scripts/system/users.js b/scripts/system/users.js index 5b0ba42a45..54fa5951ac 100644 --- a/scripts/system/users.js +++ b/scripts/system/users.js @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -var PopUpMenu = function(properties) { +var PopUpMenu = function (properties) { var value = properties.value, promptOverlay, valueOverlay, @@ -217,7 +217,7 @@ var PopUpMenu = function(properties) { }; }; -var usersWindow = (function() { +var usersWindow = (function () { var baseURL = Script.resolvePath("assets/images/tools/"), WINDOW_WIDTH = 260, @@ -253,11 +253,7 @@ var usersWindow = (function() { WINDOW_BORDER_BOTTOM_MARGIN = WINDOW_BASE_MARGIN, WINDOW_BORDER_LEFT_MARGIN = WINDOW_BASE_MARGIN, WINDOW_BORDER_RADIUS = 4, - WINDOW_BORDER_COLOR = { - red: 255, - green: 255, - blue: 255 - }, + WINDOW_BORDER_COLOR = { red: 255, green: 255, blue: 255 }, WINDOW_BORDER_ALPHA = 0.5, windowBorder, @@ -381,12 +377,9 @@ var usersWindow = (function() { isMirrorDisplay = false, isFullscreenMirror = false, - windowPosition = {}, // Bottom left corner of window pane. + windowPosition = {}, // Bottom left corner of window pane. isMovingWindow = false, - movingClickOffset = { - x: 0, - y: 0 - }, + movingClickOffset = { x: 0, y: 0 }, isUsingScrollbars = false, isMovingScrollbar = false, @@ -408,7 +401,9 @@ var usersWindow = (function() { } // Reserve space for title, friends button, and option controls - nonUsersHeight = WINDOW_MARGIN + windowLineHeight + FRIENDS_BUTTON_SPACER + FRIENDS_BUTTON_HEIGHT + DISPLAY_SPACER + windowLineHeight + VISIBILITY_SPACER + windowLineHeight + WINDOW_BASE_MARGIN; + nonUsersHeight = WINDOW_MARGIN + windowLineHeight + FRIENDS_BUTTON_SPACER + FRIENDS_BUTTON_HEIGHT + DISPLAY_SPACER + + windowLineHeight + VISIBILITY_SPACER + + windowLineHeight + WINDOW_BASE_MARGIN; // Limit window to height of viewport above window position minus VU meter and mirror if displayed windowHeight = linesOfUsers.length * windowLineHeight - windowLineSpacing + nonUsersHeight; @@ -461,14 +456,17 @@ var usersWindow = (function() { x: scrollbarBackgroundPosition.x, y: scrollbarBackgroundPosition.y }); - scrollbarBarPosition.y = scrollbarBackgroundPosition.y + 1 + scrollbarValue * (scrollbarBackgroundHeight - scrollbarBarHeight - 2); + scrollbarBarPosition.y = scrollbarBackgroundPosition.y + 1 + + scrollbarValue * (scrollbarBackgroundHeight - scrollbarBarHeight - 2); Overlays.editOverlay(scrollbarBar, { x: scrollbarBackgroundPosition.x + 1, y: scrollbarBarPosition.y }); x = windowLeft + WINDOW_MARGIN; - y = windowPosition.y - FRIENDS_BUTTON_HEIGHT - DISPLAY_SPACER - windowLineHeight - VISIBILITY_SPACER - windowLineHeight - WINDOW_BASE_MARGIN; + y = windowPosition.y - FRIENDS_BUTTON_HEIGHT - DISPLAY_SPACER + - windowLineHeight - VISIBILITY_SPACER + - windowLineHeight - WINDOW_BASE_MARGIN; Overlays.editOverlay(friendsButton, { x: x, y: y @@ -556,36 +554,9 @@ var usersWindow = (function() { usersRequest.ontimeout = pollUsersTimedOut; usersRequest.onreadystatechange = processUsers; usersRequest.send(); - checkLoggedIn(); } - var loggedIn = false; - - function checkLoggedIn() { - loggedIn = Account.isLoggedIn(); - if (loggedIn === false) { - Overlays.editOverlay(friendsButton, { - visible: false - }); - visibilityControl.setVisible(false); - displayControl.setVisible(false); - } else { - if (isMinimized === true) { - loggedIn = true; - return - } - Overlays.editOverlay(friendsButton, { - visible: true - }); - visibilityControl.setVisible(true); - displayControl.setVisible(true); - loggedIn = true; - - } - } - - - processUsers = function() { + processUsers = function () { var response, myUsername, user, @@ -638,7 +609,7 @@ var usersWindow = (function() { } }; - pollUsersTimedOut = function() { + pollUsersTimedOut = function () { print("Error: Request for users status timed out"); usersTimer = Script.setTimeout(pollUsers, HTTP_GET_TIMEOUT); // Try again after a longer delay. }; @@ -662,15 +633,11 @@ var usersWindow = (function() { Overlays.editOverlay(scrollbarBar, { visible: isVisible && isUsingScrollbars && !isMinimized }); - - if (loggedIn === true) { - Overlays.editOverlay(friendsButton, { - visible: isVisible && !isMinimized - }); - displayControl.setVisible(isVisible && !isMinimized); - visibilityControl.setVisible(isVisible && !isMinimized); - } - + Overlays.editOverlay(friendsButton, { + visible: isVisible && !isMinimized + }); + displayControl.setVisible(isVisible && !isMinimized); + visibilityControl.setVisible(isVisible && !isMinimized); } function setVisible(visible) { @@ -763,7 +730,9 @@ var usersWindow = (function() { userClicked = firstUserToDisplay + lineClicked; - if (0 <= userClicked && userClicked < linesOfUsers.length && 0 <= overlayX && overlayX <= usersOnline[linesOfUsers[userClicked]].textWidth) { + if (0 <= userClicked && userClicked < linesOfUsers.length && 0 <= overlayX + && overlayX <= usersOnline[linesOfUsers[userClicked]].textWidth) { + //print("Go to " + usersOnline[linesOfUsers[userClicked]].username); location.goToUser(usersOnline[linesOfUsers[userClicked]].username); } @@ -831,8 +800,12 @@ var usersWindow = (function() { var isVisible; if (isMovingScrollbar) { - if (scrollbarBackgroundPosition.x - WINDOW_MARGIN <= event.x && event.x <= scrollbarBackgroundPosition.x + SCROLLBAR_BACKGROUND_WIDTH + WINDOW_MARGIN && scrollbarBackgroundPosition.y - WINDOW_MARGIN <= event.y && event.y <= scrollbarBackgroundPosition.y + scrollbarBackgroundHeight + WINDOW_MARGIN) { - scrollbarValue = (event.y - scrollbarBarClickedAt * scrollbarBarHeight - scrollbarBackgroundPosition.y) / (scrollbarBackgroundHeight - scrollbarBarHeight - 2); + if (scrollbarBackgroundPosition.x - WINDOW_MARGIN <= event.x + && event.x <= scrollbarBackgroundPosition.x + SCROLLBAR_BACKGROUND_WIDTH + WINDOW_MARGIN + && scrollbarBackgroundPosition.y - WINDOW_MARGIN <= event.y + && event.y <= scrollbarBackgroundPosition.y + scrollbarBackgroundHeight + WINDOW_MARGIN) { + scrollbarValue = (event.y - scrollbarBarClickedAt * scrollbarBarHeight - scrollbarBackgroundPosition.y) + / (scrollbarBackgroundHeight - scrollbarBarHeight - 2); scrollbarValue = Math.min(Math.max(scrollbarValue, 0.0), 1.0); firstUserToDisplay = Math.floor(scrollbarValue * (linesOfUsers.length - numUsersToDisplay)); updateOverlayPositions(); @@ -858,9 +831,13 @@ var usersWindow = (function() { isVisible = isBorderVisible; if (isVisible) { - isVisible = windowPosition.x - WINDOW_BORDER_LEFT_MARGIN <= event.x && event.x <= windowPosition.x - WINDOW_BORDER_LEFT_MARGIN + WINDOW_BORDER_WIDTH && windowPosition.y - windowHeight - WINDOW_BORDER_TOP_MARGIN <= event.y && event.y <= windowPosition.y + WINDOW_BORDER_BOTTOM_MARGIN; + isVisible = windowPosition.x - WINDOW_BORDER_LEFT_MARGIN <= event.x + && event.x <= windowPosition.x - WINDOW_BORDER_LEFT_MARGIN + WINDOW_BORDER_WIDTH + && windowPosition.y - windowHeight - WINDOW_BORDER_TOP_MARGIN <= event.y + && event.y <= windowPosition.y + WINDOW_BORDER_BOTTOM_MARGIN; } else { - isVisible = windowPosition.x <= event.x && event.x <= windowPosition.x + WINDOW_WIDTH && windowPosition.y - windowHeight <= event.y && event.y <= windowPosition.y; + isVisible = windowPosition.x <= event.x && event.x <= windowPosition.x + WINDOW_WIDTH + && windowPosition.y - windowHeight <= event.y && event.y <= windowPosition.y; } if (isVisible !== isBorderVisible) { isBorderVisible = isVisible; @@ -901,7 +878,8 @@ var usersWindow = (function() { isMirrorDisplay = Menu.isOptionChecked(MIRROR_MENU_ITEM); isFullscreenMirror = Menu.isOptionChecked(FULLSCREEN_MIRROR_MENU_ITEM); - if (viewport.y !== oldViewport.y || isMirrorDisplay !== oldIsMirrorDisplay || isFullscreenMirror !== oldIsFullscreenMirror) { + if (viewport.y !== oldViewport.y || isMirrorDisplay !== oldIsMirrorDisplay + || isFullscreenMirror !== oldIsFullscreenMirror) { calculateWindowHeight(); updateUsersDisplay(); } @@ -951,8 +929,8 @@ var usersWindow = (function() { } else { hmdViewport = Controller.getRecommendedOverlayRect(); windowPosition = { - x: (viewport.x - hmdViewport.width) / 2, // HMD viewport is narrower than screen. - y: hmdViewport.height // HMD viewport starts at top of screen but only extends down so far. + x: (viewport.x - hmdViewport.width) / 2, // HMD viewport is narrower than screen. + y: hmdViewport.height // HMD viewport starts at top of screen but only extends down so far. }; } @@ -960,7 +938,7 @@ var usersWindow = (function() { windowBorder = Overlays.addOverlay("rectangle", { x: 0, - y: viewport.y, // Start up off-screen + y: viewport.y, // Start up off-screen width: WINDOW_BORDER_WIDTH, height: windowBorderHeight, radius: WINDOW_BORDER_RADIUS, @@ -1123,11 +1101,6 @@ var usersWindow = (function() { visible: isVisible && !isMinimized }); - - Script.setTimeout(function() { - checkLoggedIn() - }, 0); - Controller.mousePressEvent.connect(onMousePressEvent); Controller.mouseMoveEvent.connect(onMouseMoveEvent); Controller.mouseReleaseEvent.connect(onMouseReleaseEvent); @@ -1170,4 +1143,4 @@ var usersWindow = (function() { setUp(); Script.scriptEnding.connect(tearDown); -}()); \ No newline at end of file +}()); From 1c1d81ae25437e3b06aca39bbb973c0be634c5ee Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 25 Jun 2016 12:18:03 +1200 Subject: [PATCH 162/171] Fix missing users.js menu item --- scripts/system/users.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/system/users.js b/scripts/system/users.js index 54fa5951ac..92a05b30fd 100644 --- a/scripts/system/users.js +++ b/scripts/system/users.js @@ -361,9 +361,9 @@ var usersWindow = (function () { myVisibility, - MENU_NAME = "Tools", + MENU_NAME = "View", MENU_ITEM = "Users Online", - MENU_ITEM_AFTER = "Chat...", + MENU_ITEM_AFTER = "Overlays", SETTING_USERS_WINDOW_MINIMIZED = "UsersWindow.Minimized", SETINGS_USERS_WINDOW_OFFSET = "UsersWindow.Offset", From 8b0f59c9cfd032de2010d3d0cf1a9b93851f28a1 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 25 Jun 2016 13:12:16 +1200 Subject: [PATCH 163/171] Fix users window minimized state not being remembered --- scripts/system/users.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/scripts/system/users.js b/scripts/system/users.js index 92a05b30fd..afc3ac2de4 100644 --- a/scripts/system/users.js +++ b/scripts/system/users.js @@ -370,7 +370,7 @@ var usersWindow = (function () { // +ve x, y values are offset from left, top of screen; -ve from right, bottom. isVisible = true, - isMinimized = false, + isMinimized = true, isBorderVisible = false, viewport, @@ -388,6 +388,12 @@ var usersWindow = (function () { scrollbarBarClickedAt, // 0.0 .. 1.0 scrollbarValue = 0.0; // 0.0 .. 1.0 + function isValueTrue(value) { + // Work around Boolean Settings values being read as string when Interface starts up but as Booleans when re-read after + // Being written if refresh script. + return value === true || value === "true"; + } + function calculateWindowHeight() { var AUDIO_METER_HEIGHT = 52, MIRROR_HEIGHT = 220, @@ -664,6 +670,7 @@ var usersWindow = (function () { } }); updateOverlayVisibility(); + Settings.setValue(SETTING_USERS_WINDOW_MINIMIZED, isMinimized); } function onMenuItemEvent(event) { @@ -1121,12 +1128,10 @@ var usersWindow = (function () { pollUsers(); // Set minimized at end - setup code does not handle `minimized == false` correctly - setMinimized(Settings.getValue(SETTING_USERS_WINDOW_MINIMIZED, false)); + setMinimized(isValueTrue(Settings.getValue(SETTING_USERS_WINDOW_MINIMIZED, false))); } function tearDown() { - Settings.setValue(SETTING_USERS_WINDOW_MINIMIZED, isMinimized); - Menu.removeMenuItem(MENU_NAME, MENU_ITEM); Script.clearTimeout(usersTimer); From 0b6797acc354c44b89316fb429b90f83845d53c6 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 25 Jun 2016 13:48:43 +1200 Subject: [PATCH 164/171] Fix "show me" and "visible to" in users window not being remembered --- scripts/system/users.js | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/scripts/system/users.js b/scripts/system/users.js index afc3ac2de4..b5fe7c4fad 100644 --- a/scripts/system/users.js +++ b/scripts/system/users.js @@ -359,14 +359,17 @@ var usersWindow = (function () { usersTimer = null, USERS_UPDATE_TIMEOUT = 5000, // ms = 5s + showMe, myVisibility, MENU_NAME = "View", MENU_ITEM = "Users Online", MENU_ITEM_AFTER = "Overlays", + SETTING_USERS_SHOW_ME = "UsersWindow.ShowMe", + SETTING_USERS_VISIBLE_TO = "UsersWindow.VisibleTo", SETTING_USERS_WINDOW_MINIMIZED = "UsersWindow.Minimized", - SETINGS_USERS_WINDOW_OFFSET = "UsersWindow.Offset", + SETTING_USERS_WINDOW_OFFSET = "UsersWindow.Offset", // +ve x, y values are offset from left, top of screen; -ve from right, bottom. isVisible = true, @@ -550,7 +553,7 @@ var usersWindow = (function () { function pollUsers() { var url = API_URL; - if (displayControl.getValue() === DISPLAY_FRIENDS) { + if (showMe === DISPLAY_FRIENDS) { url += API_FRIENDS_FILTER; } @@ -681,9 +684,11 @@ var usersWindow = (function () { function onFindableByChanged(event) { if (VISIBILITY_VALUES.indexOf(event) !== -1) { + myVisibility = event; visibilityControl.setValue(event); + Settings.setValue(SETTING_USERS_VISIBLE_TO, myVisibility); } else { - print("Error: Unrecognized onFindableByChanged value: " + myVisibility); + print("Error: Unrecognized onFindableByChanged value: " + event); } } @@ -713,11 +718,15 @@ var usersWindow = (function () { usersTimer = null; } pollUsers(); + showMe = displayControl.getValue(); + Settings.setValue(SETTING_USERS_SHOW_ME, showMe); return; } if (visibilityControl.handleClick(clickedOverlay)) { - GlobalServices.findableBy = visibilityControl.getValue(); + myVisibility = visibilityControl.getValue(); + GlobalServices.findableBy = myVisibility; + Settings.setValue(SETTING_USERS_VISIBLE_TO, myVisibility); return; } @@ -869,7 +878,7 @@ var usersWindow = (function () { // Save offset of bottom of window to nearest edge of the window. offset.x = (windowPosition.x + WINDOW_WIDTH / 2 < viewport.x / 2) ? windowPosition.x : windowPosition.x - viewport.x; offset.y = (windowPosition.y < viewport.y / 2) ? windowPosition.y : windowPosition.y - viewport.y; - Settings.setValue(SETINGS_USERS_WINDOW_OFFSET, JSON.stringify(offset)); + Settings.setValue(SETTING_USERS_WINDOW_OFFSET, JSON.stringify(offset)); isMovingWindow = false; } } @@ -925,9 +934,9 @@ var usersWindow = (function () { viewport = Controller.getViewportDimensions(); - offsetSetting = Settings.getValue(SETINGS_USERS_WINDOW_OFFSET); + offsetSetting = Settings.getValue(SETTING_USERS_WINDOW_OFFSET); if (offsetSetting !== "") { - offset = JSON.parse(Settings.getValue(SETINGS_USERS_WINDOW_OFFSET)); + offset = JSON.parse(Settings.getValue(SETTING_USERS_WINDOW_OFFSET)); } if (offset.hasOwnProperty("x") && offset.hasOwnProperty("y")) { windowPosition.x = offset.x < 0 ? viewport.x + offset.x : offset.x; @@ -1048,9 +1057,14 @@ var usersWindow = (function () { alpha: FRIENDS_BUTTON_ALPHA }); + showMe = Settings.getValue(SETTING_USERS_SHOW_ME, ""); + if (DISPLAY_VALUES.indexOf(showMe) === -1) { + showMe = DISPLAY_EVERYONE; + } + displayControl = new PopUpMenu({ prompt: DISPLAY_PROMPT, - value: DISPLAY_VALUES[0], + value: showMe, values: DISPLAY_VALUES, displayValues: DISPLAY_DISPLAY_VALUES, x: 0, @@ -1075,10 +1089,9 @@ var usersWindow = (function () { visible: isVisible && !isMinimized }); - myVisibility = GlobalServices.findableBy; + myVisibility = Settings.getValue(SETTING_USERS_VISIBLE_TO, ""); if (VISIBILITY_VALUES.indexOf(myVisibility) === -1) { - print("Error: Unrecognized findableBy value: " + myVisibility); - myVisibility = VISIBILITY_ALL; + myVisibility = VISIBILITY_FRIENDS; } visibilityControl = new PopUpMenu({ From dc07368dc9717c71e212b4c46ef6f20b48201660 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 25 Jun 2016 14:23:35 +1200 Subject: [PATCH 165/171] Hide users list when logged out --- scripts/system/users.js | 94 +++++++++++++++++++++++------------------ 1 file changed, 54 insertions(+), 40 deletions(-) diff --git a/scripts/system/users.js b/scripts/system/users.js index b5fe7c4fad..f23a0ea215 100644 --- a/scripts/system/users.js +++ b/scripts/system/users.js @@ -372,6 +372,7 @@ var usersWindow = (function () { SETTING_USERS_WINDOW_OFFSET = "UsersWindow.Offset", // +ve x, y values are offset from left, top of screen; -ve from right, bottom. + isLoggedIn = false, isVisible = true, isMinimized = true, isBorderVisible = false, @@ -526,13 +527,13 @@ var usersWindow = (function () { scrollbarBackgroundHeight = numUsersToDisplay * windowLineHeight - windowLineSpacing / 2; Overlays.editOverlay(scrollbarBackground, { height: scrollbarBackgroundHeight, - visible: isUsingScrollbars + visible: isLoggedIn && isUsingScrollbars }); scrollbarBarHeight = Math.max(numUsersToDisplay / linesOfUsers.length * scrollbarBackgroundHeight, SCROLLBAR_BAR_MIN_HEIGHT); Overlays.editOverlay(scrollbarBar, { height: scrollbarBarHeight, - visible: isUsingScrollbars + visible: isLoggedIn && isUsingScrollbars }); } @@ -550,6 +551,41 @@ var usersWindow = (function () { }); } + function updateOverlayVisibility() { + Overlays.editOverlay(windowBorder, { + visible: isLoggedIn && isVisible && isBorderVisible + }); + Overlays.editOverlay(windowPane, { + visible: isLoggedIn && isVisible + }); + Overlays.editOverlay(windowHeading, { + visible: isLoggedIn && isVisible + }); + Overlays.editOverlay(minimizeButton, { + visible: isLoggedIn && isVisible + }); + Overlays.editOverlay(scrollbarBackground, { + visible: isLoggedIn && isVisible && isUsingScrollbars && !isMinimized + }); + Overlays.editOverlay(scrollbarBar, { + visible: isLoggedIn && isVisible && isUsingScrollbars && !isMinimized + }); + Overlays.editOverlay(friendsButton, { + visible: isLoggedIn && isVisible && !isMinimized + }); + displayControl.setVisible(isLoggedIn && isVisible && !isMinimized); + visibilityControl.setVisible(isLoggedIn && isVisible && !isMinimized); + } + + function checkLoggedIn() { + var wasLoggedIn = isLoggedIn; + + isLoggedIn = Account.isLoggedIn(); + if (isLoggedIn !== wasLoggedIn) { + updateOverlayVisibility(); + } + } + function pollUsers() { var url = API_URL; @@ -563,6 +599,8 @@ var usersWindow = (function () { usersRequest.ontimeout = pollUsersTimedOut; usersRequest.onreadystatechange = processUsers; usersRequest.send(); + + checkLoggedIn(); } processUsers = function () { @@ -623,32 +661,6 @@ var usersWindow = (function () { usersTimer = Script.setTimeout(pollUsers, HTTP_GET_TIMEOUT); // Try again after a longer delay. }; - function updateOverlayVisibility() { - Overlays.editOverlay(windowBorder, { - visible: isVisible && isBorderVisible - }); - Overlays.editOverlay(windowPane, { - visible: isVisible - }); - Overlays.editOverlay(windowHeading, { - visible: isVisible - }); - Overlays.editOverlay(minimizeButton, { - visible: isVisible - }); - Overlays.editOverlay(scrollbarBackground, { - visible: isVisible && isUsingScrollbars && !isMinimized - }); - Overlays.editOverlay(scrollbarBar, { - visible: isVisible && isUsingScrollbars && !isMinimized - }); - Overlays.editOverlay(friendsButton, { - visible: isVisible && !isMinimized - }); - displayControl.setVisible(isVisible && !isMinimized); - visibilityControl.setVisible(isVisible && !isMinimized); - } - function setVisible(visible) { isVisible = visible; @@ -662,7 +674,6 @@ var usersWindow = (function () { } updateOverlayVisibility(); - } function setMinimized(minimized) { @@ -876,8 +887,10 @@ var usersWindow = (function () { if (isMovingWindow) { // Save offset of bottom of window to nearest edge of the window. - offset.x = (windowPosition.x + WINDOW_WIDTH / 2 < viewport.x / 2) ? windowPosition.x : windowPosition.x - viewport.x; - offset.y = (windowPosition.y < viewport.y / 2) ? windowPosition.y : windowPosition.y - viewport.y; + offset.x = (windowPosition.x + WINDOW_WIDTH / 2 < viewport.x / 2) + ? windowPosition.x : windowPosition.x - viewport.x; + offset.y = (windowPosition.y < viewport.y / 2) + ? windowPosition.y : windowPosition.y - viewport.y; Settings.setValue(SETTING_USERS_WINDOW_OFFSET, JSON.stringify(offset)); isMovingWindow = false; } @@ -960,7 +973,7 @@ var usersWindow = (function () { radius: WINDOW_BORDER_RADIUS, color: WINDOW_BORDER_COLOR, alpha: WINDOW_BORDER_ALPHA, - visible: isVisible && isBorderVisible + visible: false }); windowPane = Overlays.addOverlay("text", { @@ -976,7 +989,7 @@ var usersWindow = (function () { backgroundAlpha: WINDOW_BACKGROUND_ALPHA, text: "", font: WINDOW_FONT, - visible: isVisible + visible: false }); windowHeading = Overlays.addOverlay("text", { @@ -991,7 +1004,7 @@ var usersWindow = (function () { backgroundAlpha: 0.0, text: "No users online", font: WINDOW_FONT, - visible: isVisible && !isMinimized + visible: false }); minimizeButton = Overlays.addOverlay("image", { @@ -1008,7 +1021,7 @@ var usersWindow = (function () { }, color: MIN_MAX_BUTTON_COLOR, alpha: MIN_MAX_BUTTON_ALPHA, - visible: isVisible && !isMinimized + visible: false }); scrollbarBackgroundPosition = { @@ -1023,7 +1036,7 @@ var usersWindow = (function () { backgroundColor: SCROLLBAR_BACKGROUND_COLOR, backgroundAlpha: SCROLLBAR_BACKGROUND_ALPHA, text: "", - visible: isVisible && isUsingScrollbars && !isMinimized + visible: false }); scrollbarBarPosition = { @@ -1038,7 +1051,7 @@ var usersWindow = (function () { backgroundColor: SCROLLBAR_BAR_COLOR, backgroundAlpha: SCROLLBAR_BAR_ALPHA, text: "", - visible: isVisible && isUsingScrollbars && !isMinimized + visible: false }); friendsButton = Overlays.addOverlay("image", { @@ -1054,7 +1067,8 @@ var usersWindow = (function () { height: FRIENDS_BUTTON_SVG_HEIGHT }, color: FRIENDS_BUTTON_COLOR, - alpha: FRIENDS_BUTTON_ALPHA + alpha: FRIENDS_BUTTON_ALPHA, + visible: false }); showMe = Settings.getValue(SETTING_USERS_SHOW_ME, ""); @@ -1086,7 +1100,7 @@ var usersWindow = (function () { popupBackgroundAlpha: DISPLAY_OPTIONS_BACKGROUND_ALPHA, buttonColor: MIN_MAX_BUTTON_COLOR, buttonAlpha: MIN_MAX_BUTTON_ALPHA, - visible: isVisible && !isMinimized + visible: false }); myVisibility = Settings.getValue(SETTING_USERS_VISIBLE_TO, ""); @@ -1118,7 +1132,7 @@ var usersWindow = (function () { popupBackgroundAlpha: DISPLAY_OPTIONS_BACKGROUND_ALPHA, buttonColor: MIN_MAX_BUTTON_COLOR, buttonAlpha: MIN_MAX_BUTTON_ALPHA, - visible: isVisible && !isMinimized + visible: false }); Controller.mousePressEvent.connect(onMousePressEvent); From 17ce80ed69e97cdb075c27584751cccb78098cf5 Mon Sep 17 00:00:00 2001 From: Marko Kudjerski Date: Fri, 24 Jun 2016 19:28:31 -0700 Subject: [PATCH 166/171] sign executables with SHA256 --- cmake/macros/OptionalWinExecutableSigning.cmake | 2 +- cmake/templates/NSIS.template.in | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/macros/OptionalWinExecutableSigning.cmake b/cmake/macros/OptionalWinExecutableSigning.cmake index 784aae716f..41ca5762dc 100644 --- a/cmake/macros/OptionalWinExecutableSigning.cmake +++ b/cmake/macros/OptionalWinExecutableSigning.cmake @@ -22,7 +22,7 @@ macro(optional_win_executable_signing) # setup a post build command to sign the executable add_custom_command( TARGET ${TARGET_NAME} POST_BUILD - COMMAND ${SIGNTOOL_EXECUTABLE} sign /f %HF_PFX_FILE% /p %HF_PFX_PASSPHRASE% /tr http://tsa.starfieldtech.com /td SHA256 ${EXECUTABLE_PATH} + COMMAND ${SIGNTOOL_EXECUTABLE} sign /fd sha256 /f %HF_PFX_FILE% /p %HF_PFX_PASSPHRASE% /tr http://tsa.starfieldtech.com /td SHA256 ${EXECUTABLE_PATH} ) else () message(FATAL_ERROR "HF_PFX_PASSPHRASE must be set for executables to be signed.") diff --git a/cmake/templates/NSIS.template.in b/cmake/templates/NSIS.template.in index 0ea1199c09..4786b12743 100644 --- a/cmake/templates/NSIS.template.in +++ b/cmake/templates/NSIS.template.in @@ -64,7 +64,7 @@ ; The Inner invocation has written an uninstaller binary for us. ; We need to sign it if it's a production or PR build. !if @PRODUCTION_BUILD@ == 1 - !system '"@SIGNTOOL_EXECUTABLE@" sign /f %HF_PFX_FILE% /p %HF_PFX_PASSPHRASE% /tr http://tsa.starfieldtech.com /td SHA256 $%TEMP%\@UNINSTALLER_NAME@' = 0 + !system '"@SIGNTOOL_EXECUTABLE@" sign /fd sha256 /f %HF_PFX_FILE% /p %HF_PFX_PASSPHRASE% /tr http://tsa.starfieldtech.com /td SHA256 $%TEMP%\@UNINSTALLER_NAME@' = 0 !endif ; Good. Now we can carry on writing the real installer. From bbe9b931bee9364581feb1ab983de329f7d0c301 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 26 Jun 2016 14:59:42 -0700 Subject: [PATCH 167/171] on Linux, don't mess with the menus from the hydra plugin because it makes Qt stop --- plugins/hifiSixense/src/SixenseManager.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/hifiSixense/src/SixenseManager.cpp b/plugins/hifiSixense/src/SixenseManager.cpp index ade643ec72..baf13f1fae 100644 --- a/plugins/hifiSixense/src/SixenseManager.cpp +++ b/plugins/hifiSixense/src/SixenseManager.cpp @@ -77,6 +77,7 @@ bool SixenseManager::activate() { InputPlugin::activate(); #ifdef HAVE_SIXENSE + #if !defined(Q_OS_LINUX) _container->addMenu(MENU_PATH); _container->addMenuItem(PluginType::INPUT_PLUGIN, MENU_PATH, TOGGLE_SMOOTH, [this] (bool clicked) { setSixenseFilter(clicked); }, @@ -89,6 +90,7 @@ bool SixenseManager::activate() { _container->addMenuItem(PluginType::INPUT_PLUGIN, MENU_PATH, SHOW_DEBUG_CALIBRATED, [this] (bool clicked) { _inputDevice->setDebugDrawCalibrated(clicked); }, true, false); + #endif auto userInputMapper = DependencyManager::get(); userInputMapper->registerDevice(_inputDevice); @@ -106,8 +108,10 @@ void SixenseManager::deactivate() { InputPlugin::deactivate(); #ifdef HAVE_SIXENSE + #if !defined(Q_OS_LINUX) _container->removeMenuItem(MENU_NAME, TOGGLE_SMOOTH); _container->removeMenu(MENU_PATH); + #endif _inputDevice->_poseStateMap.clear(); From 55c2466249d65875f2a65e04a520de976edb4f34 Mon Sep 17 00:00:00 2001 From: Zander Otavka Date: Mon, 27 Jun 2016 10:25:53 -0700 Subject: [PATCH 168/171] Move .eslintrc.js to project root This way, all project scripts are in scope, and the .eslintrc doesn't get pushed to the installer with the default scripts. --- scripts/.eslintrc.js => .eslintrc.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename scripts/.eslintrc.js => .eslintrc.js (100%) diff --git a/scripts/.eslintrc.js b/.eslintrc.js similarity index 100% rename from scripts/.eslintrc.js rename to .eslintrc.js From 235ee4bd463e69c3bcd1d808ece0244c1014926d Mon Sep 17 00:00:00 2001 From: Zander Otavka Date: Mon, 27 Jun 2016 13:06:12 -0700 Subject: [PATCH 169/171] Refactor Camera scripting interface a little get/setRotation has been removed, as it was a duplicate of get/setOrientation, but undocumented and barely used. get/setTransform and get/setPerspective have been changed to methods from slots, since glm::mat4 is not exposed to the scriping interface so they don't work in scripting. --- interface/src/Application.cpp | 26 +++++++++++++------------- interface/src/Camera.cpp | 16 ++++++++-------- interface/src/Camera.h | 27 ++++++++++++--------------- 3 files changed, 33 insertions(+), 36 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 26da43fb58..049356e298 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1717,22 +1717,22 @@ void Application::paintGL() { if (isHMDMode()) { mat4 camMat = myAvatar->getSensorToWorldMatrix() * myAvatar->getHMDSensorMatrix(); _myCamera.setPosition(extractTranslation(camMat)); - _myCamera.setRotation(glm::quat_cast(camMat)); + _myCamera.setOrientation(glm::quat_cast(camMat)); } else { _myCamera.setPosition(myAvatar->getDefaultEyePosition()); - _myCamera.setRotation(myAvatar->getHead()->getCameraOrientation()); + _myCamera.setOrientation(myAvatar->getHead()->getCameraOrientation()); } } else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) { if (isHMDMode()) { auto hmdWorldMat = myAvatar->getSensorToWorldMatrix() * myAvatar->getHMDSensorMatrix(); - _myCamera.setRotation(glm::normalize(glm::quat_cast(hmdWorldMat))); + _myCamera.setOrientation(glm::normalize(glm::quat_cast(hmdWorldMat))); _myCamera.setPosition(extractTranslation(hmdWorldMat) + myAvatar->getOrientation() * boomOffset); } else { - _myCamera.setRotation(myAvatar->getHead()->getOrientation()); + _myCamera.setOrientation(myAvatar->getHead()->getOrientation()); if (Menu::getInstance()->isOptionChecked(MenuOption::CenterPlayerInView)) { _myCamera.setPosition(myAvatar->getDefaultEyePosition() - + _myCamera.getRotation() * boomOffset); + + _myCamera.getOrientation() * boomOffset); } else { _myCamera.setPosition(myAvatar->getDefaultEyePosition() + myAvatar->getOrientation() * boomOffset); @@ -1751,7 +1751,7 @@ void Application::paintGL() { glm::quat worldMirrorRotation = mirrorBodyOrientation * mirrorHmdRotation; - _myCamera.setRotation(worldMirrorRotation); + _myCamera.setOrientation(worldMirrorRotation); glm::vec3 hmdOffset = extractTranslation(myAvatar->getHMDSensorMatrix()); // Mirror HMD lateral offsets @@ -1762,7 +1762,7 @@ void Application::paintGL() { + mirrorBodyOrientation * glm::vec3(0.0f, 0.0f, 1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror + mirrorBodyOrientation * hmdOffset); } else { - _myCamera.setRotation(myAvatar->getWorldAlignedOrientation() + _myCamera.setOrientation(myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f))); _myCamera.setPosition(myAvatar->getDefaultEyePosition() + glm::vec3(0, _raiseMirror * myAvatar->getUniformScale(), 0) @@ -1775,11 +1775,11 @@ void Application::paintGL() { if (cameraEntity != nullptr) { if (isHMDMode()) { glm::quat hmdRotation = extractRotation(myAvatar->getHMDSensorMatrix()); - _myCamera.setRotation(cameraEntity->getRotation() * hmdRotation); + _myCamera.setOrientation(cameraEntity->getRotation() * hmdRotation); glm::vec3 hmdOffset = extractTranslation(myAvatar->getHMDSensorMatrix()); _myCamera.setPosition(cameraEntity->getPosition() + (hmdRotation * hmdOffset)); } else { - _myCamera.setRotation(cameraEntity->getRotation()); + _myCamera.setOrientation(cameraEntity->getRotation()); _myCamera.setPosition(cameraEntity->getPosition()); } } @@ -3314,9 +3314,9 @@ void Application::updateMyAvatarLookAtPosition() { if (isLookingAtSomeone) { deflection *= GAZE_DEFLECTION_REDUCTION_DURING_EYE_CONTACT; } - lookAtSpot = origin + _myCamera.getRotation() * glm::quat(glm::radians(glm::vec3( + lookAtSpot = origin + _myCamera.getOrientation() * glm::quat(glm::radians(glm::vec3( eyePitch * deflection, eyeYaw * deflection, 0.0f))) * - glm::inverse(_myCamera.getRotation()) * (lookAtSpot - origin); + glm::inverse(_myCamera.getOrientation()) * (lookAtSpot - origin); } } @@ -4032,7 +4032,7 @@ void Application::loadViewFrustum(Camera& camera, ViewFrustum& viewFrustum) { // Set the viewFrustum up with the correct position and orientation of the camera viewFrustum.setPosition(camera.getPosition()); - viewFrustum.setOrientation(camera.getRotation()); + viewFrustum.setOrientation(camera.getOrientation()); // Ask the ViewFrustum class to calculate our corners viewFrustum.calculate(); @@ -4305,7 +4305,7 @@ void Application::renderRearViewMirror(RenderArgs* renderArgs, const QRect& regi myAvatar->getOrientation() * glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_REARVIEW_DISTANCE * myAvatar->getScale()); } _mirrorCamera.setProjection(glm::perspective(glm::radians(fov), aspect, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP)); - _mirrorCamera.setRotation(myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI, 0.0f))); + _mirrorCamera.setOrientation(myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI, 0.0f))); // set the bounds of rear mirror view diff --git a/interface/src/Camera.cpp b/interface/src/Camera.cpp index 53a3500bff..227bdadb97 100644 --- a/interface/src/Camera.cpp +++ b/interface/src/Camera.cpp @@ -62,14 +62,14 @@ void Camera::update(float deltaTime) { } void Camera::recompose() { - mat4 orientation = glm::mat4_cast(_rotation); + mat4 orientation = glm::mat4_cast(_orientation); mat4 translation = glm::translate(mat4(), _position); _transform = translation * orientation; } void Camera::decompose() { _position = vec3(_transform[3]); - _rotation = glm::quat_cast(_transform); + _orientation = glm::quat_cast(_transform); } void Camera::setTransform(const glm::mat4& transform) { @@ -85,8 +85,8 @@ void Camera::setPosition(const glm::vec3& position) { } } -void Camera::setRotation(const glm::quat& rotation) { - _rotation = rotation; +void Camera::setOrientation(const glm::quat& orientation) { + _orientation = orientation; recompose(); if (_isKeepLookingAt) { lookAt(_lookingAt); @@ -154,9 +154,9 @@ QString Camera::getModeString() const { void Camera::lookAt(const glm::vec3& lookAt) { glm::vec3 up = IDENTITY_UP; glm::mat4 lookAtMatrix = glm::lookAt(_position, lookAt, up); - glm::quat rotation = glm::quat_cast(lookAtMatrix); - rotation.w = -rotation.w; // Rosedale approved - _rotation = rotation; + glm::quat orientation = glm::quat_cast(lookAtMatrix); + orientation.w = -orientation.w; // Rosedale approved + _orientation = orientation; } void Camera::keepLookingAt(const glm::vec3& point) { @@ -171,7 +171,7 @@ void Camera::loadViewFrustum(ViewFrustum& frustum) const { // Set the viewFrustum up with the correct position and orientation of the camera frustum.setPosition(getPosition()); - frustum.setOrientation(getRotation()); + frustum.setOrientation(getOrientation()); // Ask the ViewFrustum class to calculate our corners frustum.calculate(); diff --git a/interface/src/Camera.h b/interface/src/Camera.h index 486b98c100..46cad2efc8 100644 --- a/interface/src/Camera.h +++ b/interface/src/Camera.h @@ -45,7 +45,7 @@ class Camera : public QObject { public: Camera(); - void initialize(); // instantly put the camera at the ideal position and rotation. + void initialize(); // instantly put the camera at the ideal position and orientation. void update( float deltaTime ); @@ -57,25 +57,22 @@ public: EntityItemPointer getCameraEntityPointer() const { return _cameraEntity; } -public slots: - QString getModeString() const; - void setModeString(const QString& mode); - - glm::quat getRotation() const { return _rotation; } - void setRotation(const glm::quat& rotation); - - glm::vec3 getPosition() const { return _position; } - void setPosition(const glm::vec3& position); - - glm::quat getOrientation() const { return getRotation(); } - void setOrientation(const glm::quat& orientation) { setRotation(orientation); } - const glm::mat4& getTransform() const { return _transform; } void setTransform(const glm::mat4& transform); const glm::mat4& getProjection() const { return _projection; } void setProjection(const glm::mat4& projection); +public slots: + QString getModeString() const; + void setModeString(const QString& mode); + + glm::vec3 getPosition() const { return _position; } + void setPosition(const glm::vec3& position); + + glm::quat getOrientation() const { return _orientation; } + void setOrientation(const glm::quat& orientation); + QUuid getCameraEntity() const; void setCameraEntity(QUuid entityID); @@ -105,7 +102,7 @@ private: // derived glm::vec3 _position; - glm::quat _rotation; + glm::quat _orientation; bool _isKeepLookingAt{ false }; glm::vec3 _lookingAt; EntityItemPointer _cameraEntity; From 68be819ea25eecc159d1034869098a2f8e125b91 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Mon, 27 Jun 2016 14:07:13 -0700 Subject: [PATCH 170/171] fix crash when hmd context menu triggers a modal --- .../resources/qml/menus/MenuMouseHandler.qml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/menus/MenuMouseHandler.qml b/interface/resources/qml/menus/MenuMouseHandler.qml index 9ba158cb28..48574d41e5 100644 --- a/interface/resources/qml/menus/MenuMouseHandler.qml +++ b/interface/resources/qml/menus/MenuMouseHandler.qml @@ -39,6 +39,19 @@ Item { onSelected: d.handleSelection(subMenu, currentItem, item) } } + property var delay: Timer { // No setTimeout in QML. + property var menuItem: null; + interval: 0 + repeat: false + running: false + function trigger(item) { // Capture item and schedule asynchronous Timer. + menuItem = item; + start(); + } + onTriggered: { + menuItem.trigger(); // Now trigger the item. + } + } function toModel(items) { var result = modelMaker.createObject(desktop); @@ -128,7 +141,8 @@ Item { case MenuItemType.Item: console.log("Triggering " + item.text) - item.trigger(); + // Don't block waiting for modal dialogs and such that the menu might open. + delay.trigger(item); clearMenus(); break; } From 79cfd2e9dc4f67d0bd1167c86f7e9a501e551f66 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 28 Jun 2016 11:40:49 +1200 Subject: [PATCH 171/171] Reduce users window flicker when restart script --- scripts/system/users.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/system/users.js b/scripts/system/users.js index f23a0ea215..d25e1b76b4 100644 --- a/scripts/system/users.js +++ b/scripts/system/users.js @@ -599,8 +599,6 @@ var usersWindow = (function () { usersRequest.ontimeout = pollUsersTimedOut; usersRequest.onreadystatechange = processUsers; usersRequest.send(); - - checkLoggedIn(); } processUsers = function () { @@ -646,6 +644,8 @@ var usersWindow = (function () { updateUsersDisplay(); updateOverlayPositions(); + checkLoggedIn(); + } else { print("Error: Request for users status returned " + usersRequest.status + " " + usersRequest.statusText); usersTimer = Script.setTimeout(pollUsers, HTTP_GET_TIMEOUT); // Try again after a longer delay.