From 85bda1da8e3663fdfc663359283a00cebe6c2622 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 17 Jan 2018 09:57:20 +1300 Subject: [PATCH 01/52] Add Window browseChanged signal as replacement for openFileChanged --- .../src/scripting/WindowScriptingInterface.cpp | 3 ++- .../src/scripting/WindowScriptingInterface.h | 16 +++++++++++++--- scripts/system/edit.js | 6 +++--- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index e36b84ac96..74c5b274bd 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -277,7 +277,8 @@ void WindowScriptingInterface::browseAsync(const QString& title, const QString& if (!result.isEmpty()) { setPreviousBrowseLocation(QFileInfo(result).absolutePath()); } - emit openFileChanged(result); + emit browseChanged(result); + emit openFileChanged(result); // Deprecated signal; to be removed in due course. }); } diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index bfad5644bf..86334d1d6d 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -197,18 +197,19 @@ public slots: /**jsdoc * Prompt the user to choose a file. Displays a non-modal dialog that navigates the directory tree. A - * {@link Window.openFileChanged|openFileChanged} signal is emitted when a file is chosen; no signal is emitted if the user + * {@link Window.browseChanged|browseChanged} signal is emitted when a file is chosen; no signal is emitted if the user * cancels the dialog. + * @deprecated A deprecated {@link Window.openFileChanged|openFileChanged} signal is also emitted when a file is chosen. * @function Window.browseAsync * @param {string} title="" - The title to display at the top of the dialog. * @param {string} directory="" - The initial directory to start browsing at. * @param {string} nameFilter="" - The types of files to display. Examples: "*.json" and * "Images (*.png *.jpg *.svg)". All files are displayed if a filter isn't specified. * @example Ask the user to choose an image file without waiting for the answer. - * function onOpenFileChanged(filename) { + * function onBrowseChanged(filename) { * print("File: " + filename); * } - * Window.openFileChanged.connect(onOpenFileChanged); + * Window.browseChanged.connect(onBrowseChanged); * * Window.browseAsync("Select Image File", Paths.resources, "Images (*.png *.jpg *.svg)"); * print("Script continues without waiting"); @@ -652,9 +653,18 @@ signals: */ void saveFileChanged(QString filename); + /**jsdoc + * Triggered when the user chooses a file in a {@link Window.browseAsync|browseAsync} dialog. + * @function Window.browseChanged + * @param {string} filename - The path and name of the file the user chose in the dialog. + * @returns {Signal} + */ + void browseChanged(QString filename); + /**jsdoc * Triggered when the user chooses a file in a {@link Window.browseAsync|browseAsync} dialog. * @function Window.openFileChanged + * @deprecated This signal is being replaced with {@link Window.browseChanged|browseChanged} and will be removed. * @param {string} filename - The path and name of the file the user chose in the dialog. * @returns {Signal} */ diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 87cd3e0faf..92ccdf6565 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -448,7 +448,7 @@ var toolBar = (function () { }); addButton("importEntitiesButton", "assets-01.svg", function() { - Window.openFileChanged.connect(onFileOpenChanged); + Window.browseChanged.connect(onFileOpenChanged); Window.browseAsync("Select Model to Import", "", "*.json"); }); @@ -1497,7 +1497,7 @@ function onFileOpenChanged(filename) { // disconnect the event, otherwise the requests will stack up try { // Not all calls to onFileOpenChanged() connect an event. - Window.openFileChanged.disconnect(onFileOpenChanged); + Window.browseChanged.disconnect(onFileOpenChanged); } catch (e) { // Ignore. } @@ -1549,7 +1549,7 @@ function handeMenuEvent(menuItem) { } } else if (menuItem === "Import Entities" || menuItem === "Import Entities from URL") { if (menuItem === "Import Entities") { - Window.openFileChanged.connect(onFileOpenChanged); + Window.browseChanged.connect(onFileOpenChanged); Window.browseAsync("Select Model to Import", "", "*.json"); } else { Window.promptTextChanged.connect(onPromptTextChanged); From ee2936d13345fe22a357bbd826c0cdc378cc0dbe Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 18 Jan 2018 14:07:07 +1300 Subject: [PATCH 02/52] Add Pointers API function that disables hover events for nominated items --- interface/src/raypick/PointerScriptingInterface.cpp | 4 ++++ interface/src/raypick/PointerScriptingInterface.h | 8 ++++++++ libraries/pointers/src/Pointer.cpp | 11 +++++++++++ libraries/pointers/src/Pointer.h | 3 +++ libraries/pointers/src/PointerManager.cpp | 7 +++++++ libraries/pointers/src/PointerManager.h | 1 + 6 files changed, 34 insertions(+) diff --git a/interface/src/raypick/PointerScriptingInterface.cpp b/interface/src/raypick/PointerScriptingInterface.cpp index ac5a467e76..8e50b1d629 100644 --- a/interface/src/raypick/PointerScriptingInterface.cpp +++ b/interface/src/raypick/PointerScriptingInterface.cpp @@ -24,6 +24,10 @@ void PointerScriptingInterface::setIncludeItems(unsigned int uid, const QScriptV DependencyManager::get()->setIncludeItems(uid, qVectorQUuidFromScriptValue(includeItems)); } +void PointerScriptingInterface::setNonHoverItems(unsigned int uid, const QScriptValue& nonHoverItems) const { + DependencyManager::get()->setNonHoverItems(uid, qVectorQUuidFromScriptValue(nonHoverItems)); +} + unsigned int PointerScriptingInterface::createPointer(const PickQuery::PickType& type, const QVariant& properties) { // Interaction with managers should always happen on the main thread if (QThread::currentThread() != qApp->thread()) { diff --git a/interface/src/raypick/PointerScriptingInterface.h b/interface/src/raypick/PointerScriptingInterface.h index 1cc7b56503..4791fd802e 100644 --- a/interface/src/raypick/PointerScriptingInterface.h +++ b/interface/src/raypick/PointerScriptingInterface.h @@ -191,6 +191,14 @@ public: */ Q_INVOKABLE void setIncludeItems(unsigned int uid, const QScriptValue& includeEntities) const; + /**jsdoc + * Sets a list of Entity IDs, Overlay IDs, and/or Avatar IDs that the pointer should not send hover events to. + * @function Pointers.setNonHoverItems + * @param {number} uid - The ID of the Pointer, as returned by {@link Pointers.createPointer}. + * @param {Uuid[]} nonHoverItems - A list of IDs to that hover events should not be sent to. + */ + Q_INVOKABLE void setNonHoverItems(unsigned int uid, const QScriptValue& nonHoverItems) const; + /**jsdoc * Lock a Pointer onto a specific object (overlay, entity, or avatar). Optionally, provide an offset in object-space, otherwise the Pointer will lock on to the center of the object. * Not used by Stylus Pointers. diff --git a/libraries/pointers/src/Pointer.cpp b/libraries/pointers/src/Pointer.cpp index 5307e17355..ead3c22687 100644 --- a/libraries/pointers/src/Pointer.cpp +++ b/libraries/pointers/src/Pointer.cpp @@ -52,6 +52,12 @@ void Pointer::setIncludeItems(const QVector& includeItems) const { DependencyManager::get()->setIncludeItems(_pickUID, includeItems); } +void Pointer::setNonHoverItems(const QVector& nonHoverItems) { + withWriteLock([&] { + _nonHoverItems = nonHoverItems; + }); +} + bool Pointer::isLeftHand() const { return DependencyManager::get()->isLeftHand(_pickUID); } @@ -96,6 +102,11 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin // Hover events bool doHover = shouldHover(pickResult); + + auto pickResultMap = pickResult->toVariantMap(); + auto uuid = QUuid(pickResultMap.value("objectID", "").toString()); + doHover = doHover && !_nonHoverItems.contains(uuid); + Pointer::PickedObject hoveredObject = getHoveredObject(pickResult); PointerEvent hoveredEvent = buildPointerEvent(hoveredObject, pickResult); hoveredEvent.setType(PointerEvent::Move); diff --git a/libraries/pointers/src/Pointer.h b/libraries/pointers/src/Pointer.h index 3197c80cad..6cb366e92a 100644 --- a/libraries/pointers/src/Pointer.h +++ b/libraries/pointers/src/Pointer.h @@ -54,6 +54,8 @@ public: virtual void setIgnoreItems(const QVector& ignoreItems) const; virtual void setIncludeItems(const QVector& includeItems) const; + void setNonHoverItems(const QVector& nonHoverItems); + bool isLeftHand() const; bool isRightHand() const; bool isMouse() const; @@ -102,6 +104,7 @@ private: PointerEvent::Button chooseButton(const std::string& button); + QVector _nonHoverItems; }; #endif // hifi_Pick_h diff --git a/libraries/pointers/src/PointerManager.cpp b/libraries/pointers/src/PointerManager.cpp index be890da392..f2953ce8c8 100644 --- a/libraries/pointers/src/PointerManager.cpp +++ b/libraries/pointers/src/PointerManager.cpp @@ -108,6 +108,13 @@ void PointerManager::setIncludeItems(unsigned int uid, const QVector& inc } } +void PointerManager::setNonHoverItems(unsigned int uid, const QVector& nonHoverItems) const { + auto pointer = find(uid); + if (pointer) { + pointer->setNonHoverItems(nonHoverItems); + } +} + void PointerManager::setLength(unsigned int uid, float length) const { auto pointer = find(uid); if (pointer) { diff --git a/libraries/pointers/src/PointerManager.h b/libraries/pointers/src/PointerManager.h index b98558622f..a2a1e9dcd2 100644 --- a/libraries/pointers/src/PointerManager.h +++ b/libraries/pointers/src/PointerManager.h @@ -34,6 +34,7 @@ public: void setPrecisionPicking(unsigned int uid, bool precisionPicking) const; void setIgnoreItems(unsigned int uid, const QVector& ignoreEntities) const; void setIncludeItems(unsigned int uid, const QVector& includeEntities) const; + void setNonHoverItems(unsigned int uid, const QVector& nonHoverItems) const; void setLength(unsigned int uid, float length) const; void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) const; From 5878e36ea8fc38c2c1e904e9398af9efa467c3da Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 18 Jan 2018 14:24:23 +1300 Subject: [PATCH 03/52] Display both lasers --- .../controllerModules/webSurfaceLaserInput.js | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index 3d9d7979d5..19b5330492 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -87,20 +87,11 @@ Script.include("/~/system/libraries/controllers.js"); return MyAvatar.getDominantHand() === "right" ? 1 : 0; }; - this.dominantHandOverride = false; - this.isReady = function(controllerData) { - var otherModuleRunning = this.getOtherModule().running; - otherModuleRunning = otherModuleRunning && this.getDominantHand() !== this.hand; // Auto-swap to dominant hand. var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE && controllerData.triggerValues[this.otherHand] <= TRIGGER_OFF_VALUE; - if ((!otherModuleRunning || isTriggerPressed) - && (this.isPointingAtOverlay(controllerData) || this.isPointingAtWebEntity(controllerData))) { + if (this.isPointingAtOverlay(controllerData) || this.isPointingAtWebEntity(controllerData)) { this.updateAllwaysOn(); - if (isTriggerPressed) { - this.dominantHandOverride = true; // Override dominant hand. - this.getOtherModule().dominantHandOverride = false; - } if (this.parameters.handLaser.allwaysOn || isTriggerPressed) { return makeRunningValues(true, [], []); } @@ -109,11 +100,8 @@ Script.include("/~/system/libraries/controllers.js"); }; this.run = function(controllerData, deltaTime) { - var otherModuleRunning = this.getOtherModule().running; - otherModuleRunning = otherModuleRunning && this.getDominantHand() !== this.hand; // Auto-swap to dominant hand. - otherModuleRunning = otherModuleRunning || this.getOtherModule().dominantHandOverride; // Override dominant hand. var grabModuleNeedsToRun = this.grabModuleWantsNearbyOverlay(controllerData); - if (!otherModuleRunning && !grabModuleNeedsToRun && (controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE + if (!grabModuleNeedsToRun && (controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE || this.parameters.handLaser.allwaysOn && (this.isPointingAtOverlay(controllerData) || this.isPointingAtWebEntity(controllerData)))) { this.running = true; @@ -121,7 +109,6 @@ Script.include("/~/system/libraries/controllers.js"); } this.deleteContextOverlay(); this.running = false; - this.dominantHandOverride = false; return makeRunningValues(false, [], []); }; } From 4eb2a5cf5e900a80abda46677038b0e88de53445 Mon Sep 17 00:00:00 2001 From: amantley Date: Wed, 17 Jan 2018 19:25:20 -0800 Subject: [PATCH 04/52] added a property check in renderableModelEntity for the animation url this fixes the bug stopping you from changing animations on an entity --- .../src/RenderableModelEntityItem.cpp | 14 +++++++++++++- .../src/RenderableModelEntityItem.h | 3 +++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 827507c3aa..684540453b 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1394,7 +1394,19 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce // That is where _currentFrame and _lastAnimated were updated. if (_animating) { DETAILED_PROFILE_RANGE(simulation_physics, "Animate"); - if (!jointsMapped()) { + // check animation url change + auto newprops = entity->getAnimationProperties(); + if (newprops != _previousAnimationProperties) { + if (newprops.getURL() != _previousAnimationProperties.getURL()) { + _animation = DependencyManager::get()->getAnimation(entity->getAnimationURL()); + _jointMappingCompleted = false; + mapJoints(entity, model->getJointNames()); + } + _previousAnimationProperties = newprops; + } + // + if (!jointsMapped() || _animation->getURL().toString() != entity->getAnimationURL()) { + qCDebug(entitiesrenderer) << "changed animation or started animation"; mapJoints(entity, model->getJointNames()); } if (!(entity->getAnimationFirstFrame() < 0) && !(entity->getAnimationFirstFrame() > entity->getAnimationLastFrame())) { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index b3988e0239..ce4d44daf8 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -193,6 +193,9 @@ private: bool _animating { false }; uint64_t _lastAnimated { 0 }; + //fix test + AnimationPropertyGroup _previousAnimationProperties; + render::ItemKey _itemKey { render::ItemKey::Builder().withTypeMeta() }; }; From 403dd3d7d029a73c63e5e1c4f101d40efeff06ce Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 18 Jan 2018 22:31:12 +1300 Subject: [PATCH 05/52] Only first laser highlights --- .../controllers/controllerDispatcher.js | 64 +++++++++++++++++++ .../controllerModules/webSurfaceLaserInput.js | 45 ++++++++++++- 2 files changed, 107 insertions(+), 2 deletions(-) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 16f1d086b7..15b025a7ea 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -44,7 +44,12 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); this.highVarianceCount = 0; this.veryhighVarianceCount = 0; this.tabletID = null; + this.TABLET_UI_UUIDS = []; this.blacklist = []; + this.leftPointerNonHoverItem = null; + this.leftPointerNonHoverItemChanged = false; + this.rightPointerNonHoverItem = null; + this.rightPointerNonHoverItemChanged = false; this.pointerManager = new PointerManager(); // a module can occupy one or more "activity" slots while it's running. If all the required slots for a module are @@ -122,6 +127,10 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); return getControllerWorldLocation(Controller.Standard.RightHand, true); }; + this.isTabletID = function (uuid) { + return _this.TABLET_UI_UUIDS.indexOf(uuid) !== -1; + }; + this.updateTimings = function () { _this.intervalCount++; var thisInterval = Date.now(); @@ -148,11 +157,35 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); this.setIgnorePointerItems = function() { if (HMD.tabletID !== this.tabletID) { this.tabletID = HMD.tabletID; + this.TABLET_UI_UUIDS = [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID, HMD.homeButtonHighlightID]; Pointers.setIgnoreItems(_this.leftPointer, _this.blacklist); Pointers.setIgnoreItems(_this.rightPointer, _this.blacklist); } }; + this.setNonHoverItems = function () { + if (_this.leftPointerNonHoverItemChanged) { + if (_this.leftPointerNonHoverItem === null) { + Pointers.setNonHoverItems(_this.leftPointer, []); + } else if (_this.isTabletID(_this.leftPointerNonHoverItem)) { + Pointers.setNonHoverItems(_this.leftPointer, _this.TABLET_UI_UUIDS); + } else { + Pointers.setNonHoverItems(_this.leftPointer, [_this.leftPointerNonHoverItem]); + } + _this.leftPointerNonHoverItemChanged = false; + } + if (_this.rightPointerNonHoverItemChanged) { + if (_this.rightPointerNonHoverItem === null) { + Pointers.setNonHoverItems(_this.rightPointer, []); + } else if (_this.isTabletID(_this.rightPointerNonHoverItem)) { + Pointers.setNonHoverItems(_this.rightPointer, _this.TABLET_UI_UUIDS); + } else { + Pointers.setNonHoverItems(_this.rightPointer, [_this.rightPointerNonHoverItem]); + } + _this.rightPointerNonHoverItemChanged = false; + } + }; + this.update = function () { try { _this.updateInternal(); @@ -324,6 +357,19 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); _this.runningPluginNames[orderedPluginName] = true; _this.markSlots(candidatePlugin, orderedPluginName); _this.pointerManager.makePointerVisible(candidatePlugin.parameters.handLaser); + + if (candidatePlugin.parameters.handLaser.nonHoverItem !== undefined) { + if (candidatePlugin.parameters.handLaser.hand === LEFT_HAND + && _this.leftPointerNonHoverItem !== candidatePlugin.parameters.handLaser.nonHoverItem) { + _this.leftPointerNonHoverItem = candidatePlugin.parameters.handLaser.nonHoverItem; + _this.leftPointerNonHoverItemChanged = true; + } else if (candidatePlugin.parameters.handLaser.hand === RIGHT_HAND + && _this.rightPointerNonHoverItem !== candidatePlugin.parameters.handLaser.nonHoverItem) { + _this.rightPointerNonHoverItem = candidatePlugin.parameters.handLaser.nonHoverItem; + _this.rightPointerNonHoverItemChanged = true; + } + } + if (DEBUG) { print("controllerDispatcher running " + orderedPluginName); } @@ -354,6 +400,21 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Script.beginProfileRange("dispatch.run." + runningPluginName); } var runningness = plugin.run(controllerData, deltaTime); + + if (runningness.active) { + if (plugin.parameters.handLaser.nonHoverItem !== undefined) { + if (plugin.parameters.handLaser.hand === LEFT_HAND + && _this.leftPointerNonHoverItem !== plugin.parameters.handLaser.nonHoverItem) { + _this.leftPointerNonHoverItem = plugin.parameters.handLaser.nonHoverItem; + _this.leftPointerNonHoverItemChanged = true; + } else if (plugin.parameters.handLaser.hand === RIGHT_HAND + && _this.rightPointerNonHoverItem !== plugin.parameters.handLaser.nonHoverItem) { + _this.rightPointerNonHoverItem = plugin.parameters.handLaser.nonHoverItem; + _this.rightPointerNonHoverItemChanged = true; + } + } + } + if (!runningness.active) { // plugin is finished running, for now. remove it from the list // of running plugins and mark its activity-slots as "not in use" @@ -372,6 +433,9 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); } } _this.pointerManager.updatePointersRenderState(controllerData.triggerClicks, controllerData.triggerValues); + + _this.setNonHoverItems(); + if (PROFILE) { Script.endProfileRange("dispatch.run"); } diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index 19b5330492..5b9afa3a2a 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -87,28 +87,69 @@ Script.include("/~/system/libraries/controllers.js"); return MyAvatar.getDominantHand() === "right" ? 1 : 0; }; + this.hoverItem = null; + + this.isTabletID = function (uuid) { + return [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID, HMD.homeButtonHightlightID].indexOf(uuid) !== -1; + }; + this.isReady = function(controllerData) { var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE && controllerData.triggerValues[this.otherHand] <= TRIGGER_OFF_VALUE; if (this.isPointingAtOverlay(controllerData) || this.isPointingAtWebEntity(controllerData)) { this.updateAllwaysOn(); if (this.parameters.handLaser.allwaysOn || isTriggerPressed) { + var pointingAt = controllerData.rayPicks[this.hand].objectID; + if (this.isTabletID(pointingAt)) { + pointingAt = HMD.tabletID; + } + + if (pointingAt !== this.getOtherModule().hoverItem) { + this.hoverItem = pointingAt; + this.getOtherModule().parameters.handLaser.nonHoverItem = pointingAt; + } else { + this.hoverItem = null; + this.getOtherModule().parameters.handLaser.nonHoverItem = null; + } + return makeRunningValues(true, [], []); } } + + this.hoverItem = null; + this.getOtherModule().parameters.handLaser.nonHoverItem = null; + return makeRunningValues(false, [], []); }; this.run = function(controllerData, deltaTime) { var grabModuleNeedsToRun = this.grabModuleWantsNearbyOverlay(controllerData); - if (!grabModuleNeedsToRun && (controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE - || this.parameters.handLaser.allwaysOn + var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE; + if (!grabModuleNeedsToRun && (isTriggerPressed || this.parameters.handLaser.allwaysOn && (this.isPointingAtOverlay(controllerData) || this.isPointingAtWebEntity(controllerData)))) { this.running = true; + + var pointingAt = controllerData.rayPicks[this.hand].objectID; + if (this.isTabletID(pointingAt)) { + pointingAt = HMD.tabletID; + } + + if (pointingAt !== this.getOtherModule().hoverItem || isTriggerPressed) { + this.hoverItem = pointingAt; + this.getOtherModule().parameters.handLaser.nonHoverItem = pointingAt; + } else { + this.hoverItem = null; + this.getOtherModule().parameters.handLaser.nonHoverItem = null; + } + return makeRunningValues(true, [], []); } this.deleteContextOverlay(); this.running = false; + + this.hoverItem = null; + this.getOtherModule().parameters.handLaser.nonHoverItem = null; + return makeRunningValues(false, [], []); }; } From 9f484a7f57e870bf4d3691dce47d3cf80243571b Mon Sep 17 00:00:00 2001 From: amantley Date: Thu, 18 Jan 2018 08:54:05 -0800 Subject: [PATCH 06/52] Removed extraneous comments in RenderableItemEntity.cpp and .h --- libraries/entities-renderer/src/RenderableModelEntityItem.cpp | 1 - libraries/entities-renderer/src/RenderableModelEntityItem.h | 2 -- 2 files changed, 3 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 684540453b..cdbdd31d71 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1404,7 +1404,6 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce } _previousAnimationProperties = newprops; } - // if (!jointsMapped() || _animation->getURL().toString() != entity->getAnimationURL()) { qCDebug(entitiesrenderer) << "changed animation or started animation"; mapJoints(entity, model->getJointNames()); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index ce4d44daf8..014c5bbf67 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -192,8 +192,6 @@ private: bool _shouldHighlight { false }; bool _animating { false }; uint64_t _lastAnimated { 0 }; - - //fix test AnimationPropertyGroup _previousAnimationProperties; render::ItemKey _itemKey { render::ItemKey::Builder().withTypeMeta() }; From c5efcf5d6ab6f859767e16c476dd06a630677882 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 18 Jan 2018 09:21:15 -0800 Subject: [PATCH 07/52] Bug fix for cocked head after playing an input recording. I thought this was the cause of the Manuscript case 11454 but apparently not. --- interface/src/avatar/MyAvatar.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 11496f727e..e93b897013 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2019,8 +2019,7 @@ void MyAvatar::updateOrientation(float deltaTime) { _smoothOrientationTimer = 0.0f; } - getHead()->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime); - + Head* head = getHead(); auto headPose = getControllerPoseInAvatarFrame(controller::Action::HEAD); if (headPose.isValid()) { glm::quat localOrientation = headPose.rotation * Quaternions::Y_180; @@ -2032,6 +2031,10 @@ void MyAvatar::updateOrientation(float deltaTime) { head->setBaseYaw(YAW(euler)); head->setBasePitch(PITCH(euler)); head->setBaseRoll(ROLL(euler)); + } else { + head->setBaseYaw(0.0f); + head->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime); + head->setBaseRoll(0.0f); } } From d88ff701b5f9c21e5b3363d9edacea59af513311 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 18 Jan 2018 10:48:35 -0800 Subject: [PATCH 08/52] reduce calls to isPointOnDesktopWindow --- .../controllerModules/farActionGrabEntity.js | 25 +++++++++++-------- .../controllerModules/hudOverlayPointer.js | 2 +- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 88195d7024..32bf7316a9 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -370,20 +370,23 @@ Script.include("/~/system/libraries/Xform.js"); }; this.isReady = function (controllerData) { - if (this.notPointingAtEntity(controllerData)) { - return makeRunningValues(false, [], []); - } + if (HMD.active) { + if (this.notPointingAtEntity(controllerData)) { + return makeRunningValues(false, [], []); + } - this.distanceHolding = false; - this.distanceRotating = false; + this.distanceHolding = false; + this.distanceRotating = false; - if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { - this.prepareDistanceRotatingData(controllerData); - return makeRunningValues(true, [], []); - } else { - this.destroyContextOverlay(); - return makeRunningValues(false, [], []); + if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { + this.prepareDistanceRotatingData(controllerData); + return makeRunningValues(true, [], []); + } else { + this.destroyContextOverlay(); + return makeRunningValues(false, [], []); + } } + return makeRunningValues(false, [], []); }; this.run = function (controllerData) { diff --git a/scripts/system/controllers/controllerModules/hudOverlayPointer.js b/scripts/system/controllers/controllerModules/hudOverlayPointer.js index a7a186b07a..afc9256875 100644 --- a/scripts/system/controllers/controllerModules/hudOverlayPointer.js +++ b/scripts/system/controllers/controllerModules/hudOverlayPointer.js @@ -89,7 +89,7 @@ this.isReady = function (controllerData) { var otherModuleRunning = this.getOtherModule().running; - if (!otherModuleRunning) { + if (!otherModuleRunning && HMD.active) { if (this.processLaser(controllerData)) { this.running = true; return ControllerDispatcherUtils.makeRunningValues(true, [], []); From 1bda3faaf277d928b9d211211c89e76893524f45 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 18 Jan 2018 11:14:56 -0800 Subject: [PATCH 09/52] Fix the Space Bubble button flash behavior --- scripts/system/bubble.js | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/scripts/system/bubble.js b/scripts/system/bubble.js index 4ea684ff06..98bc7d319f 100644 --- a/scripts/system/bubble.js +++ b/scripts/system/bubble.js @@ -18,8 +18,6 @@ var bubbleOverlayTimestamp; // Used for flashing the HUD button upon activation var bubbleButtonFlashState = false; - // Used for flashing the HUD button upon activation - var bubbleButtonTimestamp; // Affects bubble height var BUBBLE_HEIGHT_SCALE = 0.15; // The bubble model itself @@ -36,6 +34,7 @@ var bubbleActivateSound = SoundCache.getSound(Script.resolvePath("assets/sounds/bubble.wav")); // Is the update() function connected? var updateConnected = false; + var bubbleFlashTimer = false; var BUBBLE_VISIBLE_DURATION_MS = 3000; var BUBBLE_RAISE_ANIMATION_DURATION_MS = 750; @@ -81,9 +80,16 @@ visible: true }); bubbleOverlayTimestamp = Date.now(); - bubbleButtonTimestamp = bubbleOverlayTimestamp; Script.update.connect(update); updateConnected = true; + + // Flash button + if (!bubbleFlashTimer) { + bubbleFlashTimer = Script.setInterval(function () { + writeButtonProperties(bubbleButtonFlashState); + bubbleButtonFlashState = !bubbleButtonFlashState; + }, 500); + } } // Called from the C++ scripting interface to show the bubble overlay @@ -103,12 +109,6 @@ var delay = (timestamp - bubbleOverlayTimestamp); var overlayAlpha = 1.0 - (delay / BUBBLE_VISIBLE_DURATION_MS); if (overlayAlpha > 0) { - // Flash button - if ((timestamp - bubbleButtonTimestamp) >= BUBBLE_VISIBLE_DURATION_MS) { - writeButtonProperties(bubbleButtonFlashState); - bubbleButtonTimestamp = timestamp; - bubbleButtonFlashState = !bubbleButtonFlashState; - } if (delay < BUBBLE_RAISE_ANIMATION_DURATION_MS) { Overlays.editOverlay(bubbleOverlay, { @@ -157,8 +157,11 @@ Script.update.disconnect(update); updateConnected = false; } - var bubbleActive = Users.getIgnoreRadiusEnabled(); - writeButtonProperties(bubbleActive); + if (bubbleFlashTimer) { + Script.clearTimeout(bubbleFlashTimer); + bubbleFlashTimer = false; + } + writeButtonProperties(Users.getIgnoreRadiusEnabled()); } } @@ -166,6 +169,10 @@ // NOTE: the c++ calls this with just the first param -- we added a second // just for not logging the initial state of the bubble when we startup. function onBubbleToggled(enabled, doNotLog) { + if (bubbleFlashTimer) { + Script.clearTimeout(bubbleFlashTimer); + bubbleFlashTimer = false; + } writeButtonProperties(enabled); if (doNotLog !== true) { UserActivityLogger.bubbleToggled(enabled); @@ -200,6 +207,10 @@ // Cleanup the tablet button and overlays when script is stopped Script.scriptEnding.connect(function () { button.clicked.disconnect(Users.toggleIgnoreRadius); + if (bubbleFlashTimer) { + Script.clearTimeout(bubbleFlashTimer); + bubbleFlashTimer = false; + } if (tablet) { tablet.removeButton(button); } From 13d8d643f7f284c3d38183aab3a820efc85cbcc1 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 18 Jan 2018 12:05:16 -0800 Subject: [PATCH 10/52] Also rate limit the bubble sound --- scripts/system/bubble.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/scripts/system/bubble.js b/scripts/system/bubble.js index 98bc7d319f..994bde49eb 100644 --- a/scripts/system/bubble.js +++ b/scripts/system/bubble.js @@ -16,6 +16,8 @@ var button; // Used for animating and disappearing the bubble var bubbleOverlayTimestamp; + // Used for rate limiting the bubble sound + var lastBubbleSoundTimestamp = 0; // Used for flashing the HUD button upon activation var bubbleButtonFlashState = false; // Affects bubble height @@ -38,6 +40,7 @@ var BUBBLE_VISIBLE_DURATION_MS = 3000; var BUBBLE_RAISE_ANIMATION_DURATION_MS = 750; + var BUBBLE_SOUND_RATE_LIMIT_MS = 15000; // Hides the bubble model overlay and resets the button flash state function hideOverlays() { @@ -49,11 +52,15 @@ // Make the bubble overlay visible, set its position, and play the sound function createOverlays() { - Audio.playSound(bubbleActivateSound, { - position: { x: MyAvatar.position.x, y: MyAvatar.position.y, z: MyAvatar.position.z }, - localOnly: true, - volume: 0.2 - }); + var nowTimestamp = Date.now(); + if (nowTimestamp - lastBubbleSoundTimestamp >= BUBBLE_SOUND_RATE_LIMIT_MS) { + Audio.playSound(bubbleActivateSound, { + position: { x: MyAvatar.position.x, y: MyAvatar.position.y, z: MyAvatar.position.z }, + localOnly: true, + volume: 0.2 + }); + lastBubbleSoundTimestamp = nowTimestamp; + } hideOverlays(); if (updateConnected === true) { updateConnected = false; @@ -79,7 +86,7 @@ }, visible: true }); - bubbleOverlayTimestamp = Date.now(); + bubbleOverlayTimestamp = nowTimestamp; Script.update.connect(update); updateConnected = true; From 1c97272767e3806c3fa9aa35d9474097c57e9934 Mon Sep 17 00:00:00 2001 From: amantley Date: Thu, 18 Jan 2018 12:09:28 -0800 Subject: [PATCH 11/52] Cleaned up the code a bit so that there is no need for a new class variable for properties to verify the change of url --- .../src/RenderableModelEntityItem.cpp | 18 +++++++----------- .../src/RenderableModelEntityItem.h | 1 - 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index cdbdd31d71..8b5a23b787 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1394,18 +1394,14 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce // That is where _currentFrame and _lastAnimated were updated. if (_animating) { DETAILED_PROFILE_RANGE(simulation_physics, "Animate"); - // check animation url change - auto newprops = entity->getAnimationProperties(); - if (newprops != _previousAnimationProperties) { - if (newprops.getURL() != _previousAnimationProperties.getURL()) { - _animation = DependencyManager::get()->getAnimation(entity->getAnimationURL()); - _jointMappingCompleted = false; - mapJoints(entity, model->getJointNames()); - } - _previousAnimationProperties = newprops; + + if (!jointsMapped()) { + mapJoints(entity, model->getJointNames()); } - if (!jointsMapped() || _animation->getURL().toString() != entity->getAnimationURL()) { - qCDebug(entitiesrenderer) << "changed animation or started animation"; + //else the joints have been mapped before but we have new animation to load + else if (_animation && (_animation->getURL().toString() != entity->getAnimationURL())) { + _animation = DependencyManager::get()->getAnimation(entity->getAnimationURL()); + _jointMappingCompleted = false; mapJoints(entity, model->getJointNames()); } if (!(entity->getAnimationFirstFrame() < 0) && !(entity->getAnimationFirstFrame() > entity->getAnimationLastFrame())) { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 014c5bbf67..b3988e0239 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -192,7 +192,6 @@ private: bool _shouldHighlight { false }; bool _animating { false }; uint64_t _lastAnimated { 0 }; - AnimationPropertyGroup _previousAnimationProperties; render::ItemKey _itemKey { render::ItemKey::Builder().withTypeMeta() }; }; From a74b093b60c3541c96e541bba44d5d799361469e Mon Sep 17 00:00:00 2001 From: Alexander Ivash Date: Thu, 11 Jan 2018 21:36:55 +0300 Subject: [PATCH 12/52] FB11109 Preview disabled image swap (Commerce only) --- .../resources/images/preview-privacy.png | Bin 0 -> 51845 bytes interface/src/Application.cpp | 3 ++ .../display-plugins/hmd/HmdDisplayPlugin.cpp | 14 ++---- libraries/ui/src/DesktopPreviewProvider.cpp | 45 ++++++++++++++++++ libraries/ui/src/DesktopPreviewProvider.h | 41 ++++++++++++++++ scripts/system/commerce/wallet.js | 8 +++- 6 files changed, 99 insertions(+), 12 deletions(-) create mode 100644 interface/resources/images/preview-privacy.png create mode 100644 libraries/ui/src/DesktopPreviewProvider.cpp create mode 100644 libraries/ui/src/DesktopPreviewProvider.h diff --git a/interface/resources/images/preview-privacy.png b/interface/resources/images/preview-privacy.png new file mode 100644 index 0000000000000000000000000000000000000000..1a556b37f25f02cf0f3e1c1ec294725daef6cea1 GIT binary patch literal 51845 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 literal 0 HcmV?d00001 diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 6849bd07b5..85545e9def 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -209,6 +209,7 @@ #include "commerce/QmlCommerce.h" #include "webbrowser/WebBrowserSuggestionsEngine.h" +#include // On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU // FIXME seems to be broken. @@ -632,6 +633,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); DependencyManager::set(std::bind(&Application::getUserAgent, qApp)); DependencyManager::set(); DependencyManager::set(ScriptEngine::CLIENT_SCRIPT); @@ -5758,6 +5760,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe scriptEngine->registerFunction("OverlayWindow", QmlWindowClass::constructor); scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance()); + scriptEngine->registerGlobalObject("DesktopPreviewProvider", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Stats", Stats::getInstance()); scriptEngine->registerGlobalObject("Settings", SettingsScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("Snapshot", DependencyManager::get().data()); diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 1d7fee38eb..90bb83a663 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -33,6 +33,7 @@ #include "../Logging.h" #include "../CompositorHelper.h" +#include "DesktopPreviewProvider.h" #include "render-utils/hmd_ui_vert.h" #include "render-utils/hmd_ui_frag.h" @@ -254,17 +255,9 @@ void HmdDisplayPlugin::internalPresent() { swapBuffers(); } else if (_clearPreviewFlag) { - QImage image; - if (_vsyncEnabled) { - image = QImage(PathUtils::resourcesPath() + "images/preview.png"); - } else { - image = QImage(PathUtils::resourcesPath() + "images/preview-disabled.png"); - } - image = image.mirrored(); - image = image.convertToFormat(QImage::Format_RGBA8888); - if (!_previewTexture) { - _previewTexture = gpu::Texture::createStrict( + QImage image = DesktopPreviewProvider::getInstance()->getPreviewDisabledImage(_vsyncEnabled); + _previewTexture = gpu::Texture::createStrict( gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA), image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, @@ -274,7 +267,6 @@ void HmdDisplayPlugin::internalPresent() { _previewTexture->setStoredMipFormat(gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA)); _previewTexture->assignStoredMip(0, image.byteCount(), image.constBits()); _previewTexture->setAutoGenerateMips(true); - } auto viewport = getViewportForSourceSize(uvec2(_previewTexture->getDimensions())); diff --git a/libraries/ui/src/DesktopPreviewProvider.cpp b/libraries/ui/src/DesktopPreviewProvider.cpp new file mode 100644 index 0000000000..4eeb058a57 --- /dev/null +++ b/libraries/ui/src/DesktopPreviewProvider.cpp @@ -0,0 +1,45 @@ +#include "DesktopPreviewProvider.h" +#include +#include +#include +#include + +DesktopPreviewProvider::DesktopPreviewProvider() { +} + +QSharedPointer DesktopPreviewProvider::getInstance() { + static QSharedPointer instance = DependencyManager::get(); + return instance; +} + +QImage DesktopPreviewProvider::getPreviewDisabledImage(bool vsyncEnabled) const { + + auto imageIndex = vsyncEnabled ? VSYNC : m_previewDisabledReason; + assert(imageIndex >= 0 && imageIndex <= VSYNC); + + static const QString imagePaths[] = { + "images/preview-disabled.png", // USER + "images/preview-privacy.png", // WALLET + "images/preview.png", // VSYNC + }; + + return !m_previewDisabled[imageIndex].isNull() ? m_previewDisabled[imageIndex] : loadPreviewImage(m_previewDisabled[imageIndex], PathUtils::resourcesPath() + imagePaths[imageIndex]); +} + +void DesktopPreviewProvider::setPreviewDisabledReason(PreviewDisabledReasons reason) { + m_previewDisabledReason = reason; +} + +void DesktopPreviewProvider::setPreviewDisabledReason(const QString& reasonString) { + PreviewDisabledReasons reason = USER; + bool ok = false; + + reason = (PreviewDisabledReasons) QMetaEnum::fromType().keyToValue(reasonString.toLatin1().data(), &ok); + if (ok) { + setPreviewDisabledReason(reason); + } +} + +QImage& DesktopPreviewProvider::loadPreviewImage(QImage& image, const QString& path) const { + return image = QImage(path).mirrored().convertToFormat(QImage::Format_RGBA8888); +} \ No newline at end of file diff --git a/libraries/ui/src/DesktopPreviewProvider.h b/libraries/ui/src/DesktopPreviewProvider.h new file mode 100644 index 0000000000..2204f26378 --- /dev/null +++ b/libraries/ui/src/DesktopPreviewProvider.h @@ -0,0 +1,41 @@ +// +// Created by Alexander Ivash on 2018/01/08 +// Copyright 2018 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 +#include + +class DesktopPreviewProvider : public QObject, public Dependency { + SINGLETON_DEPENDENCY + Q_OBJECT + + DesktopPreviewProvider(); + DesktopPreviewProvider(const DesktopPreviewProvider& other) = delete; +public: + enum PreviewDisabledReasons { + USER = 0, + SECURE_SCREEN + }; + Q_ENUM(PreviewDisabledReasons) + + int VSYNC { 2 }; + + static QSharedPointer DesktopPreviewProvider::getInstance(); + + QImage getPreviewDisabledImage(bool vsyncEnabled) const; + void setPreviewDisabledReason(PreviewDisabledReasons reason); + +public slots: + void setPreviewDisabledReason(const QString& reason); + +private: + QImage& loadPreviewImage(QImage& image, const QString& path) const; + + PreviewDisabledReasons m_previewDisabledReason = { USER }; + + mutable QImage m_previewDisabled[3]; +}; \ No newline at end of file diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 0826325a57..f93d6714f1 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -562,9 +562,11 @@ break; case 'disableHmdPreview': isHmdPreviewDisabled = Menu.isOptionChecked("Disable Preview"); + DesktopPreviewProvider.setPreviewDisabledReason("SECURE_SCREEN"); Menu.setIsOptionChecked("Disable Preview", true); break; case 'maybeEnableHmdPreview': + DesktopPreviewProvider.setPreviewDisabledReason("USER"); Menu.setIsOptionChecked("Disable Preview", isHmdPreviewDisabled); break; case 'passphraseReset': @@ -635,7 +637,11 @@ // -Called when the TabletScriptingInterface::screenChanged() signal is emitted. The "type" argument can be either the string // value of "Home", "Web", "Menu", "QML", or "Closed". The "url" argument is only valid for Web and QML. function onTabletScreenChanged(type, url) { - onWalletScreen = (type === "QML" && url === WALLET_QML_SOURCE); + var onWalletScreenNow = (type === "QML" && url === WALLET_QML_SOURCE); + if (!onWalletScreenNow && onWalletScreen) { + DesktopPreviewProvider.setPreviewDisabledReason("USER"); + } + onWalletScreen = onWalletScreenNow; wireEventBridge(onWalletScreen); // Change button to active when window is first openend, false otherwise. if (button) { From 744da710a313a18ad80fe16b5cd0b79f5a093ab4 Mon Sep 17 00:00:00 2001 From: Alexander Ivash Date: Tue, 16 Jan 2018 01:41:23 +0300 Subject: [PATCH 13/52] move VSYNC into 'PreviewDisabledReasons' enum --- libraries/ui/src/DesktopPreviewProvider.cpp | 4 ++++ libraries/ui/src/DesktopPreviewProvider.h | 5 ++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/libraries/ui/src/DesktopPreviewProvider.cpp b/libraries/ui/src/DesktopPreviewProvider.cpp index 4eeb058a57..92a8d9e055 100644 --- a/libraries/ui/src/DesktopPreviewProvider.cpp +++ b/libraries/ui/src/DesktopPreviewProvider.cpp @@ -27,6 +27,10 @@ QImage DesktopPreviewProvider::getPreviewDisabledImage(bool vsyncEnabled) const } void DesktopPreviewProvider::setPreviewDisabledReason(PreviewDisabledReasons reason) { + if (reason == VSYNC) { + return; // Not settable via this interface, as VSYNC is controlled by HMD plugin.. + } + m_previewDisabledReason = reason; } diff --git a/libraries/ui/src/DesktopPreviewProvider.h b/libraries/ui/src/DesktopPreviewProvider.h index 2204f26378..40a4640f85 100644 --- a/libraries/ui/src/DesktopPreviewProvider.h +++ b/libraries/ui/src/DesktopPreviewProvider.h @@ -18,12 +18,11 @@ class DesktopPreviewProvider : public QObject, public Dependency { public: enum PreviewDisabledReasons { USER = 0, - SECURE_SCREEN + SECURE_SCREEN, + VSYNC // Not settable via this interface, as VSYNC is controlled by HMD plugin.. }; Q_ENUM(PreviewDisabledReasons) - int VSYNC { 2 }; - static QSharedPointer DesktopPreviewProvider::getInstance(); QImage getPreviewDisabledImage(bool vsyncEnabled) const; From 576ae227a86a9644962d82588028349116aa6b44 Mon Sep 17 00:00:00 2001 From: Alexander Ivash Date: Tue, 16 Jan 2018 01:51:46 +0300 Subject: [PATCH 14/52] move imagePaths into .h so simplify matching enum values to image files --- libraries/ui/src/DesktopPreviewProvider.cpp | 6 ------ libraries/ui/src/DesktopPreviewProvider.h | 7 +++++++ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/libraries/ui/src/DesktopPreviewProvider.cpp b/libraries/ui/src/DesktopPreviewProvider.cpp index 92a8d9e055..d86e1f951f 100644 --- a/libraries/ui/src/DesktopPreviewProvider.cpp +++ b/libraries/ui/src/DesktopPreviewProvider.cpp @@ -17,12 +17,6 @@ QImage DesktopPreviewProvider::getPreviewDisabledImage(bool vsyncEnabled) const auto imageIndex = vsyncEnabled ? VSYNC : m_previewDisabledReason; assert(imageIndex >= 0 && imageIndex <= VSYNC); - static const QString imagePaths[] = { - "images/preview-disabled.png", // USER - "images/preview-privacy.png", // WALLET - "images/preview.png", // VSYNC - }; - return !m_previewDisabled[imageIndex].isNull() ? m_previewDisabled[imageIndex] : loadPreviewImage(m_previewDisabled[imageIndex], PathUtils::resourcesPath() + imagePaths[imageIndex]); } diff --git a/libraries/ui/src/DesktopPreviewProvider.h b/libraries/ui/src/DesktopPreviewProvider.h index 40a4640f85..db87009205 100644 --- a/libraries/ui/src/DesktopPreviewProvider.h +++ b/libraries/ui/src/DesktopPreviewProvider.h @@ -15,6 +15,13 @@ class DesktopPreviewProvider : public QObject, public Dependency { DesktopPreviewProvider(); DesktopPreviewProvider(const DesktopPreviewProvider& other) = delete; + + constexpr static char* imagePaths[] = { + "images/preview-disabled.png", // USER + "images/preview-privacy.png", // SECURE_SCREEN + "images/preview.png", // VSYNC + }; + public: enum PreviewDisabledReasons { USER = 0, From be3789e70a67e90f1fd921bdea1b88903948a506 Mon Sep 17 00:00:00 2001 From: Alexander Ivash Date: Tue, 16 Jan 2018 03:18:38 +0300 Subject: [PATCH 15/52] show warning on attempt to force 'VSYNC' preview reason --- libraries/ui/src/DesktopPreviewProvider.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/ui/src/DesktopPreviewProvider.cpp b/libraries/ui/src/DesktopPreviewProvider.cpp index d86e1f951f..4ae70a1eb0 100644 --- a/libraries/ui/src/DesktopPreviewProvider.cpp +++ b/libraries/ui/src/DesktopPreviewProvider.cpp @@ -22,6 +22,7 @@ QImage DesktopPreviewProvider::getPreviewDisabledImage(bool vsyncEnabled) const void DesktopPreviewProvider::setPreviewDisabledReason(PreviewDisabledReasons reason) { if (reason == VSYNC) { + qDebug() << "Preview disabled reason can't be forced to " << QMetaEnum::fromType().valueToKey(reason); return; // Not settable via this interface, as VSYNC is controlled by HMD plugin.. } From 29625953f6ec662e93585f014e2dfe68d5ef0616 Mon Sep 17 00:00:00 2001 From: Alexander Ivash Date: Wed, 17 Jan 2018 23:06:55 +0300 Subject: [PATCH 16/52] update privacy-specific disabled preview image --- .../resources/images/preview-privacy.png | Bin 51845 -> 52759 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/interface/resources/images/preview-privacy.png b/interface/resources/images/preview-privacy.png index 1a556b37f25f02cf0f3e1c1ec294725daef6cea1..1a718de23357eec659b05e590af7d1c2165d531c 100644 GIT binary patch literal 52759 zcmeFZXH-*byEZxzMVB-yNE6g0i1ZqIStu+-KtXyFDWQiRdJvW%BA_C@Dgx4bF9A_H zgwT8F2_*?Nv_QTw@m+hLANzd2&p2cA>lqpIne!=ky{`Kv;+d8j{bkn6007Xd|NTe@ z0Kg$N^`&zF0L;rwZ36(n;`!LnQx|6I3Ac2&0hFy_uWY!~oh_j@IyRQpzHVJMasWX6 z!2X4yr=jLk87r8xpe6W>ppUaF0RG7-`nXzJIofz~y|RJYyU5=qHX!eE*;~usHIUF0 z)^t^|v9tf%&)r7XPwR!1pQDwu^<6~;E;%0=A7@u*8&69vA7>{Q4;df%yJzo}aq*A= ze-^sSb@qs-qx{|9i!#)F#-##tx8afyloYTM5f|oyNDGQc3X4mK@NV|JS)PD(*Ixo-p?pFqqT7 zxA)8r<_Yt#gSm34sDQU6!Sz7X(#qZi{E-(t;Qy@G#@*iA##+@K=FD|=LNfOM!zv`j zABjAE^jQ3n@?!|N17%ee;YW`}q{JbjV#*L{(YybdYyCg${y%di{$J+`DR9XNf!F*0 zxbA;{(s`r`4*yGd6}bLe{B2yUD+^bkl@;10t*b^GgT)(YNm;F@ryt|iSWcmNUN{>wEM(jk!ZusBNoe?lxWtGYAvds zdBHmJokocAb<=_8A$de9t$jIU?Z+Q@^k~3+Yn{`F`ns`S~dO z=KX!SCbcM5Jy6R50La8TN}m5c1b%S=G{1)rE>Q{p9w{kb3a0u!y!;nH_j~m0%DI=n zN5LUi0P5ev8~*^eevdM_&bRy?0T=H4?<@cJEdN)CfdADV;QxPS13h&G0D!1fd<}*W zqMw|c?B{}&Eur{o-9Zfbs%GtIeRJ$D`=W*HaX|{!da+?m>zA1>0KoUxpry3qH%r}r zl(O#AGPGx@7kG>**1c3yIPB7(nnmCll)xI6q;uB=V+xJ^~0MhUJ%*82UMChcsK2hnv^tdcSi~}*9IPw-YX27&a-+pi(M-mJ@MWc zFY{U-Wj6P*B+fR))uU&F^P3hq*%4a{zx+uXT~b5WwWQ`ZDo|R^cfBk7QXmvk4Km8b zX#c&ysi@C?N1Aqxksu~5wB*T<1h@aiE}YO;I7x|kCJ+(7W*#Pf{F*zdu){ z0sx$OmnW9cK)kIG-~+8W?od44ir`82+3w;YA}Ocaj^%8=qUnUSfV!C|Hb}8~lix_b z-u?_6<5Dr@cH$&f>wlI1wY%WZmoMqIbzAT&mNEJ2Mu#MAQt@SXW)3jioVA}`pQ8LdHF=UTRo19!7H zXUc7Qrt3Tq+p!r=Q`G{aN$(SL*<>vUBW}liNn*K0Mffk_x9d+xQwD{GSJ$=u#WqOk zf2m$yM)<8HL4zpAn2dn!F1;9!U9?(8(9x>4;)$nc%eo z=PY749gBPG1?HuV`9o|5V(Ft=^X{MqUgpFw@}BB@953T(j%JEze;HLz0(g<*eVGiP z06y&t0|3D83XEv5ma~DyWyLfT>emTP`?K4Q$e@$Sln8GaHYFvcBZiZbMht+?Hu_q% zzmM=zXwc{q>qA_2c6N?SzQkB%-SsI|Ek)S!uX1WH7Stw}vVpez-tfnJYWg$leGrUk zbAa!uNJN!RkeKiG(rnXC&{ln3p8aH{JznO-Ix`-?o z*KVByfXpwIRaIVAIfEq@$e6$lD_SOgsm1qVU+Z3|wzHg+IU`-zn})Px@uVpjrQ4uc z$0W?G`|2$o?K+)@Ozdm@vcz8BO7&Ro3m5rEBn%?Wf6jTW4d?lKH}22+!|{`GVlhvv zbre3X4&~Mq6o^6lH#%4M3op+_u|ZThNru;^i;qhkoyJSQShm>%K=1=FDjG<8Rc3VM zk{67|RMLH^laq4LQ2|8>E*Fp@SJI-cn40-6$0;69prYkT9|6$emE0P&2H}{!WcP*vtzR-|c z2M4^HH}}Pi+c62hKLfXLdirpWXU{fK(%IOM&*fzB(xwZJwHkjGqw{jH zJ?fZ*r-k_-i>ZKMRc~EQv`4WS$Oy-4aVuGx>k$r{1CLq1?Wlw^ zoU}3qx#GTr#}6_^3g`(^`V>#++gW;Q;~|E%u9p4OTFK(Pe!WMtFIz2hpC)~RCj5$I zuBoopmubx}`fMSDByY3U%Q%h}%H?LrEPCqd4wYKA`E~0egHDccWhzx*d(3a zo_dR36$gNUPB0)-Biz$_Wr#G*bdiC?IJB2UGd;8I( z|K%>6;Gy>NzEI=l4_MGnirhtZG27m`*3fl{PhF>L^3XKsUO4-<3s$GHw|KO%m83# z4F=JfDcSl%l3M!=(n}vwT!76_oVHa*tkV)SJAY6w#lds5plC z-q&}X`{~>JAzpQ@A!C+pVQGP+2|f5kz1O-5qx|Rhl^W9eOY~1O6q;5TP)TCBW)dCq z?ecD?*!RS{;=@@thWQ|KLJt5S_}lE6@}JM+E7GsB?Mn_kCM{2tTOfnfI0JVxl|vnH zQ%ha39nl=eQ<&50yu7^Wnr%l_3Y#$dQ8?ltT$b4Au}_#`If+UC`*cpgzkC6g$Zu4> z6lpTuPJia?sGwC^e`o(DP4sk|#BjW$XvP$sPA+C?bfnO-+7#?`C7;iv70( zdfG(&NgK%}W4%dYNK#eFn|oIR`xGW_-vD6arFTe;|Ku7uAnyAy`w;4|IRz zu9xM*r-0A;Xpw=Lm)@afdjCEVgJ=n+QVC~}-y7ELh+>1cUSQr`8{tD>FEZXHzh(@W zYrvei)a`UV5Miksk-Oljs&%;CBUEBowou+9Sm4*Y--wkzIofk{b}sVkWhE5b59KUR z_{=u?`hIO5=56g!jb`^e1)K@i@h|70QMcglo3FL>q@@&1poa(a> zu|8T9dDCWenld^%%99ZwN)|!5>M5KKd{I$6LdTfHXS`01_fd6Y7AGSXfkb~ykJvMb zr&Js#B2qq5kI>b|#>U&{TTYJl8V(jB6ND_xOAa$sRKn<_Dde33f=+XHj6BIu{1xmd z_DL2qEgIUJh;DU3C5d6TIyi;ye?o8Eox^4znx_w&{P(LLSHf8q83X31efl_%y=J2DovK99x>2VF8O}!iwE$I0 zMj&azekiBNsDc}6a-BzuZe_IiGu2ppovjnQM!)yQxZC~=94o!D)D_!yAT~MW9mmas z(N2?*Ks5^p2xL=Nw0Tz@6vLa=D~GgtW)EUB$krqpl`uLMh>PiVVL`O_h^d&jEH>Pwc;NDwq@@OCEIR@nCCAH4~6{l{uCHLWv8fxq>iUUWp?j+|fF4e6{R zySr?aHv{QpPyM+LgT==lO8vEli+lJ&m>K}YZ=7wxb-N=7U71t3A2gpb9@BI!Boo(T zaq8Z`(l536{$(^K--8!>wXZEiOBHn&N*OCQ^Ph6AS+*yb5&gzwXSbsLycG0C{_sf< zFsXJNE0zmS7PWcec{uAm!&Zi;PB8N}!ZZgK5ZHPtro2a*&Ac47y{<+hhZ$H@hDDHB zt*a%;Ho+puH$x#XKi;Ne5Mwx(+?e)o#$aAhI;+o6B`?OjunreE^>JZwSo!aCFI8yy zuiNd0KhhdG|7isPrL3!<9>^bRCs)=tC0ZZc;0p=ZMXMFWThVCA;wy!f@IHYKqE$~w z)ee-Npv_RuCH8_?ZH2lQ>koS*Qq6cjQX%(7O?u)5dMf_l>zDK*OdNsU|tAfCE zv7UR|@fW!tJn25Ky!j%T1Rio0k&~MyT3%BNHEfj2!HwPXvEy)B(VtJucA#bGu!r(~ z6Zla#hh-e^?pi7CdV-9Bw>pj%0K^X{@n@mjMT&ZC{Lwe2o*dO@mftG^4!7m{I6Qhq z_TFjrbaeF>d61CPhgFVa#TG~fC?$)^8)}g@Om`41OX#d1c7D94EsL*=zQGqR8^4F2 z+8@*qDyr`(L7c2JTLg%BZH;Z{|Jc{-K|o-$p@j1=vctoL`+f2QeFDo8{L^vG60%=36B+}5`!`mtH z{v>|$Mk$nAh*74jP6<0f%6YOfpN%-v0B;Ss;OihC``Tt;fCZW=;=a_mq0b~>e8app zzd~t$eN3pH$vqp*&><~(&##@erAs0=Yt{Z#O=#j#wrVWgYaA;>9#efKv9Qs8a(ITHF9kD7CMP! zQOXo}DvIRw}+1fd}qYCF~!E?ld~6GVj0a& ztG_mMgw=$yxT>vv;`5U7Cd<{F2LPr0YiBmi(BXU>zjVdJXI~f*+QKcm*s2Bi?^^n z_?{KrJ?qo;={|ZJQ(BZ?p_EXQB<*3F&f77iXEU|MB_?U=8$EGhq$Lf`78yhZ1gD?r z?2Shw5w2F08z*k#rIxZGFMku+xFV0PU=_o`?SAjnb6#rsv$BX4<5U}ncR-)i5DoCY?Jm_ z85U;rKK&y~cJUotW7MQW!p~p-5j_B0xeunJ@3K}NKh>65{U+DYxcP&Y-lse8XqQFS z+bKN?;xavMmiAQ8mi+U&w%qYXh2B0MgQOg6Ew;^7m^lJ5tP&1C-(JKH8u=!y4&_22W$w$}&QsOdg0qcY>!TgF{rUa(#rmZdYZs#<1?Eljhpr7jKB9lt z(P}@|M*{!@&%oCX2sp36?Wa*y$CUk(kS_ymjXB zJpRek&24{YA$Hjk6Wu9B*%>V|;SAVpHN?XTv0dC>oy|PakHa~Ge7yN0nS~g55YgOy zt?QreYvhwWNE;Jnqj{dspX{Yz<@{t>tN|c1_l&5y*l>Dsteq|=jfw~HB)C_(>* z@NSoOkSOZv>1McMgrTtL8iyWLVy-kvMYQ(-0 z;%l2^UhipN-YFFj%L6noyyN@ITPs&Sc$4=&H?CN-eTxbJSWRIn006Kr&F(kvbGUcj z@vR0Ul!ol^vTraoD!QQfoqn{AAW z#KxW5zDpm4XEoCQDqN@_ul#det5d9EAI!q*cVQ&MHzJl`6xB}wT|aELvCg2x937gl z@9gME%WBr(jj#7w*OJ@%`9AbBWp8&#+oHl&b8VG#srj%EMwn@U_=P@(H8R{LA7dz| zYa<0&39|>(06?z=GG6Sauhi$Ct8whDY9nM&(d(l{Q{^@q#MvgZ|RkBd+H$VN;BRo zMal0^TgAx7o1Q?dTwf*3ebNxjJHo%t)<|f#4T>g78$PZL@G5Q~)Nd^ICQGcZ=4NQk zKHTn%xX$A}+X|~&jvsF5C+Yw|re)GORseVq?X5XrJnJ)%sf;=7hlkOze1abTiewHa zu3w{k6Vy$K35bOSOj_NNA3Z@AZxT< zt(jJiLf(m|q5%LuCNRc+b|o&P#oFgO*=N)UT2)z@>d`MP9is>*OzSi)4jM|^Z+GCZ z%QNJGmo8o6mPL{9Q}B)P=X;J9!HDD6jR62qLR&u_VCA2b@!lBUZz2Zj z<~;4JX@z9*m*|)Gj`hruFn?|6wYoa_Dz*x{r*}3{GuqZe0hrc6$@Vyh#(# z&}?6;RoKY$%k^%n*$E^fM$y0LS0rZ7IyyRP=WFw!`)5c}(8&=!yVT}@3Zwk#!6Hg#y>L4Q z?~Jpc>{*0D_TB|_2i}6Xn2x<4OhA&CIb^-ht3$zPMT&=phKBBiZ0g)1t@xOWE4@j&RdLxG399RXp<{EU&d$zg)t&P# z2^K+zeGm-$Q|jBEPiZFdMoTO>O>;^;rxL-WUK`Z7-ItJAA1mg{a@YAEH~I7*Q4hPQ z>p1QdSe&d?Pr3B`Ez_Xm*}R;&TSq<`)DV(CmK*j(N| z4lOgv)R=)(xmQQAL4uazZH*>5%Czv0E# z$4Z1^oS(Tnr~a3#eTGh#_g5g#Ga`2fHCS}dC$LJKfb@f&f!W@6kC58Bi>}y4zrKCp z*9s-#Ohe6@TS#1BTV~CRJLmfg8qJ=3vJx0083Lpk^!8S65dv2I5rs^b76&>583l#7|ZYdzt@whF6b zOECAd_vVv@6Ucidh_teH*FtKW{b>*F5w6C7kmZ~#ULSY%?+Kabr1Z(oh5!&u1Eqfn z0Kqq8;eE$4r{vE*hpqm~kOTllFEcx26qJv}C3s+~;^?exZg z=wCsWe6c**gsB>sH(yVJ5X{+Gpym@ldrY*-!0VSeIQJ6$VpAh8=_P58m1(arvv?|o zxQrn#DMmrvKC5fTw_DQX{muRHRrx~^AO^vDc8b$$BL#}vA7jV_IFeRn$ndIjsnL~1 zi5I90pM?mafO%RZd08klJ)8>4ze;y(v6c_WO6qqljAd&i5N4Z>Ify=LB)6QWN#!qF zL+qa0x-M;YGr!aVd<|1pqiu1bH(}$c*P|c0$-HtWZw; z!9}M^qs*zvNkSbqeZP6XDZ$jeO9*k;V@F`q2RgLAvH7PwMf3#vFGfKK&BuiNW35!F zG8m>A%`aIGxE^K*Hf)q{+}oIKlC2Ymc`LE;QRH_ARD|krUpMrPO-vGmEYjt)oaz>E ziRa__jdZ-ObmvOjbblVdF>tXic?g5kyv1}(%Iun@0)S61z?6ZESRX4v;;W~cchZRG z-^6uYo}3F@ZPBx0`*O*rCtiT)i}E)Q*!pD`9e4F)r9>4^S2huoWo&E=<^)atl+zQC z=4zp4{O3BCTH9QYU_+q=GH620u-sbx@LC7=Pr+r{y--z&M~@2DR8?KbsI0EmPqV0(d$xl`Y4?W)3SW#bQ?J&Lc+=WyX8Bij6bnSBSA3mU_LyQF;>JuTs`WspTtxJ6qPLDn137lEmms{gG7)dL0mpj z#0t!*y@hbDyAny3@Dn?=wn@W7Sm`;!_HqxLFfDC2kh!t=G)Z*%^q8ofcCMbRt=Av| zn{6~iL$gigBq<(-jj|pqz4=9-Q48BWLd3?_M%>p(2HB0$F$;wJ#)$Im6aD1*Mzf3R z3a9jXGK>K5A@&TnY`yJ=`_kmUzcS&zPnanxDq0>hF!x>l#cUBU|Asl;Z!NFNaV%aj z!X>^pNQU?PWVuaGt=rf<_{Ls(yh612CSJwQic1Jb+|6%(|ZrN-{| zi1bBEX7w9L3}Yy#`#ax|^w(Zx_m{}R3DfyhuD;^^(%94vPJ|p25W@y>nQM7HaXjmb zO)DxYvMe>QH+(fFJ~_~vBo-?s4Ugn2u3bQ$t|PzdT5?uyS^LDQWemK>{K2wsz1kvl z|EE5cwhJ8q47^3$X94+7wMZtru#87~llMQQ(o3;q=7cG6#2{X+TCF|mYw*S&tr;Mb zv38JY*EY*=j(Af*$}c3FpjGrL$#n4DO}=X9Dg8Yu-CV7x{x0@LtrW?kwP+7)zb1Wp zf$HKs{)JcEf}oh+F3z$o%|UX=i(u?d!r^0mubU2yKoNg@mz(yJnJL2+oi*Q=jn;7 z#h6D29+LzNOEcF9LphqX3**(*)urdF9RM(My2u$&niqnLFMA=;n;0Xr_8pz22I)_e za~v&PV&-7@@wXJq2~8a=Lm|+w#=?PS+D`il04R=N#1OG1N#{eX0;@i9XKUrd1xp{O1MAqRwf$a}m%gfEbU21_0 zf=s)rR6dpV`mq+4Pp##L`x0L5GGpw8ab?0Or5|Qs`%)5~AE!W1u}NZ3$>Bc4(Q58R zWuMiS-TjvuA+ciLv1I2DI}p`21!VvL^3Jk;Mke~8{L#~uRJ?R@V@~l66J|?Zhf0mS zn=Q(F^L-FZDz+3<)(L1vTA87JDC(Tq=;)|-2M7GcOyJS(DtcjTJ7!|4#H5CRR^xPr z?V7y%hP0Hm+1a^l zb9E&J1}3c|_hMMYY}bbK!05s?m4@G(AAXO$#qa@J>RwiiLcL{M*kX*L!m*TY&eKD5 zOwfz&=cgv5oeYYc{(_M`)!evg_x$-DG27nnlid|4h2sXyiJ))B3y#B=J>%xN>3(Zn zJQ=A?iy)=Gk_1)Q8`f1eVC=6HjUV$?9cjJ=@<7VJFD^c{Rzh|!;#XT<-SfPHhm=pT zqhW^@AC?{;KRaET59c(mb}~vY-3eoJsUzZFVBc+)mZi} z!`<1zL7hFW#r7yRal3&AQqQdWY@@HH{gXon507{XVX|ud^q3eEc=Zle{`$I(YaOT0 z-0L)1xc&@$w9rWT^K7R<<2tb*Ib(GA_&fk)@|?gT?f?MbS1kN6Qh+A)EgtrLH(T-7K8l{%E1m&QQjwII2}~D?*!&Rs4ZWsM|e@pVqwIhfgznSCTe3j!Ae~ z%OC&9Y3#H!0nypWJX#=uA(i4w+L+KYF=`GtG=Vk6_3lkI965~_jE;^r5b6sY8dSm= zIK*t9ZtsNh{GrvqnAX_3z2koq0C21Ss$%fe{rKz9LfWZ`q-Rd2Yt~0>>sUp~jv{fQ zB6rOaB$c;f5OPM>ChfkwEL@TBTkw4soJ&k->g$fNA6 z<_lW*fVG&e?zzpr9L*G$roB zPz+kZS~z)w?9UL?Qt?D=v-gB`DH^9h0|Yz@+Rqd~Bks3~kUq+L-lHvYLq|@vxXTZ+G)y z$SaT?cJGzmBxpvUcPKNJLA~|z^$ZBi{9;Ni<@DGhaBt*^1?^kXpOW13Z=+Zx*2b-3 ziY5oXK8}1P_O1qg7dIYnxrF&>l*#z%hM2e2*!uJ`$ip%I1{%3o@2_aPHi&HX`O1`C zo+}mJRXkl(G)J85$%P+HV-?0mN8_KCvU-TjjX$p2>6euqN8!k*t`UT8iNCmtKne({ zhQ$k*FwqB62OjPIVP3Enm}y66s^Ffk3>M)agHAq8SQ9QtIRwNBQGLb0_t_p~-ngK)h z5ummb!Gzn6&sO{udr|xE!zfrl7On9iib6)^ru#i)*I@w}ko;Lf$6{|M@Vf!Rau z7ZBwb!q{=RYN6}raad(os9{aMhES8|T+8c=BWp;r)kAEC5Xa{sN06|M26Z*_%YEv( z0aOsF0<59*W!R3;4!HZ4hE1B|eVYB|%7}qQ;N0tr4pNiR9J2937U}hX>quamZ{iL= zK%VwModHK?$xFYw?`=RgcD&X{i_99T9kh=~c-g?C-F6FNUHD|FWt(!tPw?C;&Z_uX z4Jt25vCuc4kftD*0FU%Vy4!LGi<}f66BCm*+ZLAP{v9l;WImXdS?Ek#17~ZXMS{=;xl@W89u5DvfB;zkJ!pt@% zDxfO#cGyK-7bJE|=97Lvcw1*Z#Y6&4I_DB9@&m*jf+n@g_|B5}Wp` z*QHEqTmm;+FUCfm>PJ0n6X9h9lZhSi-EpfJn;Uzh0cfk+2=W%QxzCp)&cnVwG27nF zgW;JR%@h)TDz}~$0B~19s}IO48Qx~qK=hRG85B1j6B>5dtwA%B=B1zadJGV}FFH5J zZenaa>Gge^BtTm$` z=XAJ}`nbiw)a`NkGdqB>0G#=oHhY5^>({v%Q8zz@8NEEzEP0!G zNeareSBDA|tiR@(IptAk_<$`^v8M!%3EVF>YuM&2&Cb4`dcVQ!O`T;U#GkYwNB@`^ z5af`W_h@(*Y*mHKu3Y}WEM%UFeFiF-NC&&F%9nimak2AMuA3X5A0Yui$?7bb@=PIr zG+CSO{378v+M6Z|LH%oKjy_UD9sLqLkq3_cU*vA?%Irg((Ql1^BRx-j1w10tv~ zIwN7;glU!(yZY*+j0z#RVMC+)ayNde20IbB(wmeA9TPC9xFzW{p7|X-YTmOA?|;|m ze^Js{WYYhD@}m-M_Db)I<;Qg-xd*v1QNe>!E=HanDFhz9=nmG^9qez8?$TiKzQ;e` zr%1co=)3c2bh~zoTQh3+>1a?2%7d^K6V?slZRr=&3`nFFpS3P;JQ4?nxAkfRV z`_Ih?#OO3J3_g4Ac?C#VY6bu$?E!Iz;Rjh@8BC<`vp+VbN{Wh{i9xN$<=ZL3p7st7 zm$~H`%#U|huWN;}H^$dLyczASumR=5A*M3Uo6wv@kgE5CT4P>6NsAHCX1guTo}G!# zEZTOgweneVZN|ArmMuxiIZ4f-w;1~$Y^dCl3s;Nmf5g$J?0Ac^^g&fkLat*j=&j%{ z<0mPcvg}L4gg6Ge;7~{_K#+YVRoW``lje?(0497src2-9*6U2M)~>M7D}fI_8A#j7 z*XDfw!~o@Dq>(&g zf1JxC#G3-vFFifuay%P^-$m*>S65YCAcCmcpRQ<~p7CP>{mWNg1MAw}S5lV$+1Ov!Ad)Z&csp$QHg$TOIN1YuvjVx>>DDPug0I z9(5o;>6k6nEPW4y8o&ECGk?6)J3b+8LweQHH6_Z_+`d5e%l?vg$1d?y_4t{>{&wKa z29{T-=gy2TcULt6cVBtUrd6KvbU=h`btN`63Kr!mTuK{e2LL7L88JtHht=Y7528(U z0Ef)Uc{ZcK>?m6Dki+nKa$#B@CrkU+R=YJ8O;wEuE-`Jx+qZ>{eT-#X*H&@T-a0fH z?-kpWBm1whKSElPSm>3r+SpQ7>dj~c60SXmZ$E@^KA3o7_U=_I=HVrZg!e*RcXt7U z&-VJIGW}O@m^i6;EQy==+VFrQFj>ay=IpS;>64ov${VE)V)338OJr16uaJz98^2d% znBI)fWlPZ;QC4l=MMn>#m(i)sby5hEzpMvz+H%9MPGSDfFtl|LLQ( z)48Q#W2`l+aa(V1*N>s`Hn%K*dKCJ7!Vb?IJ5DMJ>yzWo^qW+I8 zWAhjqQmlSJOgd**Ce!(LpFKZvvVxaR(hK`PpqXG?v~Zucy3& zB@WZo|03VBWYL4;ci?LQ!)Pf&>E?3NBE|^ zSM)ZRztc_t?h>Ev&$6=AT7rVQ&Ft*=bLvUTM)ZYs5yhz_E0#H_GT1Z(eqE%OsSSJ>0^F*y%T935q(c0euc&2+ei5{ogsdL=sB?}y2{U&N;SNq6m@w*if4w*-bdRpSI(YD49x=*l%K_nB)p5q%M z6L*Yo$=I(~Z#SjFy$sr0&_f%^4IBWFDR}1WAd$bE%34Z3>px1NbQEs{@K-v*u#Se6 z5~D8SL0T>u9-qH1TI8EjO;0RlKwnOc#LDHepNOz-<#6|zxgOsaLmw$HN0D%PGMJ?p zv%_FWKa33kf>VDxQVp8tttsz2c_`sCgNN47?PrdS^1i&E>`T?evQUOmvO=f5n1V?e z{e;MImKqC(*X|r88ut|a4oiH-EZ;)wbg444~kZ}XhiYYCgsrf+mnqJ-1g}|PqjpQ4w01({%`vZK# z^7};wlBpjjt05D5SHKB1XR+~sQn}U464G0k#>K&*TkL{o!>5;y)YJel5Cv)<;Q5tI z<=h^RM&wr-HGG;A&g>iKlla+Zv{-NhTp-~tSiF>3;pxha-OO-jI$h}vw$ek+hqI`? zeOxC^8xnU8eAnLZcfHGZa~d(8eg6X^C($~Aw|!JmCM-qwKDOb7{0o1Bt26)*%y5Q` zJcDw)58`{%pr?YFYf}!Td1JrcpC)T%qMt^VHs&mx_jN<>gBi|avG zsbyPx45y;^#`sRZEb*V~TDX@6H7+wpo%dqEdfs2}Zmy3O9b*(fG1A$Ea<(yw&HnW6 zI+8U7Yj>5PzxT+=vI;xJK@NQniK8rHwoem<=a*u&3E$FTvyF$%#{`pVr-?R3#p%&e zeO_E&#`(zCVEduqnEyuxQSJAp~i^Z-LS z!GIjRUQZz3$pHf44$Wyi+mg&#loA}B|M_&3=_K8sPzO>E-_&FM_SQzspt}Pqj5B`g zMPP50;gwXLlQ=5y3h`%GI9zNNd79JSgys~gTlxgHtTgWQJASAzsd1U9L7w2_4_^nsIV8hHUm+9n|@g zIy$VGz=s;Nx`z_0X5925~LFV?8*pK(rf{6YYf66fildGMP?!M61*O#RhlP=*n8mp}U zb_s-Ks8yJN5DhS+aVCfyOGzZ@>6LE267uYRGkl}0gY;93L(XS2gL0Swk&rk68&=a_ zcn74=MHIMz|HnY$x8gEP*fgU#b%!ttfky#{Jr<|Kxe6npml3haDJfuwmxxvSG#nGy z04Hj3{9|~vROU=NaWVQ}<(iTf$5RBzN@bkx=7MEXBC1y8gDqNS!GQe+%n9lBSJ2;j zd07Jh@LdS>Pe9#L7r(A9YOX_9OWKAoT?b9~wf)#`{qybB<=Fb&L5)~tm?(90lx0J_ z4yc!@X9$#%m+xTr`{c1Qs}8UTPma9V76}mzJlahXgZ8iVrGO2f!*7DDJd_i*i!M6d z1x=#Ey)zT>&N~}frZcvkvbER&wpsXKWidv>TlNkP$ueGRd?k(LetT=<*e=eq+xL8U zwn(E%lVcMZVLFCEOmpBdQ&1+iR_ei8K}oyloYGLx{j-BlJFCS}YOq0(*_&D9{dS#e zX1s;JAzw#a=W&^=TwTr0U{F-gX`FX-2y}gaCRC)~3iv#G8#CPZYLytllMdobwJl>uCrdFD`Umk2#t$5DPXY=R*~Xs<+D-N5wB*o+#OejN+Ho=Iuv;I z4EM)gff}~-CwMVj2^-mn(0!HgQ$-R%zBjtQne*V!%$I@Xt&WecE8TEZS2}*?D7YCL zqWM>dp5UF}?BEDx2R*^dosXXjcS{)aor^el!CPmP_r|nzq@%;Mr8=gni@Taj&pT^j zNUfLVL&^lXJxw%7tVXQC-@C%Ue|@jRccnM!u)i0+!Y)WW2|ywd9USltB;~Z8j}%|i zXmfJ3r${+O?Qq(N&e@^f8h|#lC71;)9SoKi=cn{NwPq_O_d*rKg2+CBiQ~|WKqevc zM#||iQSj3$UM3c_S4t|!YoO6)?ND<`CCqOq#hh|9#<)`l)`c>g`^={&mfSHXOtZhr=C^YLI~~R2S){)Lq6)=LrqO zQvw`$JQvD%Nc)ka`tG16A;@0pu7-?Vw;H+bWd#i0ElkuM^>m21@z z9rg6gbL^bqqX%g}TEoTKZeV$UXtTh*5ons%sBr4MRgAoMk3bE-MRQ|80pI1XLzLr^ z>FxmWrVj~?OZP#x4U{q!MtRR${fDnF-cFQ-6UNJ|IzGP**{wM;KC|yH&SY*=_T(W& z6Wo7+LCGvMBQR`t`g9*b*ORx|D1 zlY$|yfOSNmRo8i9qW>&~pqE$_=C~k60ecU2(P|c6BYN6$TR%UL@b?o;uVuLAtp8~4 z`)ouF04*(NC-Wfgk9#{Xt8mVsgN&1j4Eh;qx!nPkj@$klHVI~=pVzhj`2wrNmUew9 z3lw$i-k!sz`x9Uk5)5-RTH`WaYKbde*egMtbn#>~HazNMxCnZMvvUz=VmM{T8_|=; z5GP|Q@ada;`i*#J*i@sh$FKAC^J<5d&O`w_>5OmQsp*i_teled6`kHDUQ_?9*3T68?Zlthw7(Rq9Yo&h2>;5%ENE&ddFp|D z2RiG_-1eph)mS@jWpI&suJUX=+9}*R{M{ zkU-W%7W3-nuzQbNqSN2}ewE$bvmIZ^$L47=*`m_HdWYK8@MiLMPlAxe?HnX}hs8B# zu*Q{yDO=uFQNp$@TK6GOrjTIIFiL*kbf|>zUaY^<(<-N6{b$qvA@8f-qH4c)H=>~A ztAMnqC@2j>r;30mA%cXINDN2}-3%CjAn{5FLkkK>3rNEtDIG(1clW?Boa-4r=X0*> zd;WuS_HS;7z4y~=-RoZW@|Sl7>zlB7rIv-a}o0SwC5b2{QQo>>o1U&{%@e|d77f;*A&NLuT*2TGpP%QF9N(= zcsl~DPm6S-ulpAusGIh@U(8lCX_Q!9v}mZe{rSD8#n4z)1&${v{K83<0Cipw=v7fpaOzFDwOS;1%zaV1;bB%3cGM2*!qv;+r>O8X;^ z27XT3iVNB&y4HzIo=hAfakFePVm3*O$&HrmhH zo^2LeePBJ9x4yjmGqc84+}TNsmH{62VgRWpoiHhcH2n2OfyV2I`fQ6Dmi8cK{IWVb z1VQ@*>v%+`-d?ciVFVd^ni63-BQfT4IF7BG%=GSF2<}K_l9$n0>$N`wG}|D0JgM&% zX;0EJf}x_#hX{fg7{Cg95TC^+Xz^5wT5>6cBdI=sk~#Ht*7)(Z?Vh)Zokih9p<-P) z*`aM&+XrgdqouTP(bW;K`LyF&mmeum@Gt;NJ&s@>abX)AW*UMz-g>VNmO5TD+L~`zeX)8=yRc*x$R;BBa9^((uY&E$O98OwV zf29f}{L)4mn}gVJQ=}ag2Uln~%z}Nm-y0FkHxWYXkL3K>uLW91M3b`EKPUMq zL5LkIm)~|#%uVGB53>Wq)k<6F*_!jIW%iyI<(=N;_fR$|JrrAwrX+@-e+VXBw;b>b z;`{sRspVKW^38(~cuZ!dfq0sYk5&>9eL&6h)ma+5f!`TK_EF>x*^vOW%_PW)rT8|5 zG7sjYBabo39ONinu`HHii52*~oyije@$1fgmfg!rY(B+SXAfmvZIqOhhX3iVOkVUL zhM*MRv+AGZ5Cq+$wfNUWO~Ta%gV)~~MB`nCX{%7Yuy=M^5qNF3JRXv;R;7=f2`>>atj~!si-)^#x_@En?zGc}GigoW)0AH}L!UzT-J< z>Ylyd(SX=|?i*Dvpx{YC!6dGELq7zF^OErcW2H79+{=ULY8|Xi-nrCFNZvWT1VJfO zgbmm$^D@ssASNwdW5j;y9@`|8KeAPDL{#~dc##!69X2%y-I?< z|AgQST$eH}S0{vAE}O3&nj0eh7j-obUL*zVYE$w`x02$5T8fABIWEypdvrAio2` zzWbV=n+H2yiJ75Nuyy!$XOHd_Y#2O*4dW@LuYd%u9MO1nSHjszoquh@Spwa?Rr5ET z2!fuRd#KsV4^3k;26!rRlcGxR!*#{E_9t?m*txq9ESPJA=P3Rt{OjbFd^{RHL{0=j zendk$U@z$QT*=6yELC~y8wg8R4HLQcHVmvx7Xs_|d5XQ~$QRc)^(wc?A*l8*VGnv; z{I>c}WXBG;bCd9XhH{~wi(4O4QneunQatz58Y5VoFQ$J(P5&hI4nXYICrGPdX+kSw(H?7m zYn;b`4f$&PwQtvV6RK(*&zh|_L=bdS{SxpD(XiO7(eaLZxRyDZ;dG@Q7bcbxyPu2- z;q5|p5Ck1af;?Sl&D`W20&i4 zqssi!^S)S%ChtzPswnp;ho>yv4R3^5?__)3^T9!jfT}!(saJOZHsbFS`p+c@^4Ot! z^|IrPq&=pZYSCBPBG{*XA4N7LKw|f_lr~IUrd{msCiPn5=1mAHhQRkczj@}HpdeH{ z_rdVj!(m*dyu(ve*5qEAN=^LFy$&?13c@U%s59n_%J3f}2KPap%bnCw>&?N}@u3$W zXl2N)Cso0d2!gl(P0jmwIir!QILn*r)t5gM3K!xSS0vaZbQWu{p(kD~8ZD2}@`)z_ zF~(&a$=>j%$=rFJn{;W1rAtwoY7#4%Y0j4+h+?Q_(|b`0oL5>$dl_=4dl+>kuK>Xc ze~paW!nsA#eYY%+I$8t&V7uKQLHmviO9Bv;98=OAaafB(NA*g6;FmLubDxG17X1=} z1Q-JRNFcu^GS1umydin;GIYGZ$6`0-MZ;L(#mK3**v(lePi(}Ac1h)`eH1f$MNo{f zV86pY;%f06fAqT)BIxzMV6t>SFnL6rw!xTZWE{JH9eFiiX^$zO#>B-&D~d+UoNCG& z5p`)={#YuK@w(L7$GRK4tB=WXl5}Cj)DT2bTN7jGiZX&AzelFW())dyr>s$QieYif zh=sH&=OO?%O}b5{p9*dlnzwm+d10EG!&u|bs0ON1onwd&m23R>D%m!6f_17=^K*CI5XW&NKXSX4nA&?) zfAV-i3_Xm&(73rAw?*o8;_l7+ekSvc!RZQeA=7r`q~yi+Bq6A{N-&Vc@e^p_*NEksOIc+lM^jr z0TK|A?=fyb!^Ggj6SRo^r|=Z1GTK~f7;^F)mu?VR@L4lAk@!>D=%0% z1ZK?pOXS!248-lm#1kbFK#qX-ie~b0{@HQ9H)Nno#6xURe5QDpyt=x2TK&1WcojIW zkIj>}XPZ|qLC|0Bke9SXun`dz-^lO@+=7@dw#_&R2Jb|ktY=^XhHnM3I4+>zHA@Qo z#}kJ&F=nrUm9D{e8cl@RL8~2;LeR=+Mp~LYKLkCvNAn|!ZbIP#onjWUAD@oEi0m$Q z8CSXg5==f!1qe_}GOp!U^No}7O{CjmXQDsWPL_fXcQxDS3j2B>Hw(6WtCZUQA_gPA_}id*GZIchUztipy!y_!J32LVA&U* zr5fCRfp7by!w@N)a4hCpPbrq*+^Z~Ue-Y%w{me)@-m0e#^o_qp_Y&g1U?GgxHAj9X zBL>PyH9n`MY!LKu$LcB4rvJ&Ox+fx2dv16fws-RCsvj|xZlPHV0GL*d&m{5N%j@f^ zLQ``b9ONzGFDjmIe>}pznZpr_olz``JrN^@pcQ=Efk_~1Iz0s4JmGJ>tKib=%Q&4y zJXQG9_iQN~Z;9QRZ@IY`qI-4}e&+t?5`9$Yh1|=rJ^0Azv)!mCSbhki_!}!$HBAW` zmYVwfyTCE;D8oU8bW?q-EfXWabU)!a%m~@#HNk$$$_r5E*>Jg<(o(VNaMVNR zw4EO3`uGj8&@a2{qg(X%y+bgA&ct235Y%Tz^%{cineS;SR(f+K{DhFp&y0_24y)Cb zLazt1IQ|9xw&Q%OGu4!RKwq7eUt@I$&bQA(XsW#{h7?S20=DXcje#rX*S$+Ig(v$Q zd^Xy3 zJP~~;8826prTFAoMjSf>1daTAD}@MxUKyK+Y~|<`?X_D9n>K}H=T&fVF$syWj!MAD ziUpGye@K@?kO0I5oa$H4nJ5FuO@=M21k77$Sw)ztkNfkH9o9xP6WzF@374dQe%C<| z#7kf_bkC5r`BF=7)#{*+`z7H~o|yOv+W98$PB`v1*rP-V_K>Ag;0@1AHHK=VlaaxN z*$Mj(g^ce(pvVIlD}Ft>YJD+md`JJvQ}wu{4Su{rL$+xnoH7l*-108rCtsu4E{gj| z*^P;x!aN&G`6Ev(HW1VL1e*=)E-9?*rIScW6RpV_@0W)VneW{O2!e8zKu>kg2$jE< ze&FhN@ z@Zfbt@F`GQ`^JO5d)v_$i6N+%j$n9verEb;1k2cc1%e=3LbAU=FeT3r4Qa^)?70(| zl+ddr(8kxrOe%ex9D7f|+au2Hs)xcu5n{#mRId-2ASfk~@cNO@&fV(=0MacMAlTFZ z@cT>v@M|+^uWwqAdGv!bznGWsesZq%vyboB4=BoJ!52Isd?$FtZNe*Z6kmG&8EU4(bI{y5S7J~c;<~g`h6bObgxL%#B8IOFrl?LWlJZKEK{_mgspPMP6 zA&f9Z!khnRBZ{x~C+E7!6GrCTNe8I)b%Jee$r}6+07K^k0j~3xr(C*!3HSpC=kt;N z#q`%-UWXf!rK7B*5M(J&7-3%-YYbM1wt6{?(0GllOXj;a`&&05Xj4G zp`*!CJ_Pe^g%oU4=pYe^x5IwEML5l2xIb@WuClO)z;tTha`p|OBzBu zw6AmcqG-2326H{NT+V037$8-qywNPp&xN{DwpIf|Xd~yXx*I|q!)KT!uNWh>S+yI) znUtxy$qoE8OLfAtzP=RSaiXOltMNvhi%DK~J*f)EZ;OxC;BqIQjd7`Owiespm(fE|3g!9Ad1|r{v*3^22mHzaO6I-M zzlYuO)IRv#WNL&t2Qgn|V+9oirQ{Ok)`)_xC{w^Ew&U)=8(wRIr?3c_(>=`YVwdj% z3f|f~+0F}G3)}N28*p50r>4g(rG+Q?;<1&k>%BVc1C8{?RkougDoA6QPN(5AppjEd zpxZu>VxsvpnP-f~*OBx0@v>t$Ww(Ro+hXA9_>S;1X+O!=UqH{V4le>$xDfyZaw9apA@YxH$n|y|=^C$N` z#w1@4+fCrV5O(-<@UhnbJ_~?UmSThtzaDkvUp=ghV8sqpiBSdi#;WF|*`WEio9Vv@pq) zf_kcWafg{Uql3c9lj=z?(EGb5^I~Xh2Z*K)nTET#xq)N}kh~~nGt}MRPZu_15W9BW zqIOwH!)|VIr3ZZp_@220Voo<%7V%W3;#GTAt9UHxLw484%pRU9b0YhllIhqkx}q~h zLif48Vd4bqC88Z4Uvprc|9I#TtqLcLT>>BZhioZ{Wc+o$zN^vUSRRljXUR?Y8|{Co z-@v#d+xa7>4#Y0Eujzj!;w0%tT2H1FieN`3GcBbTgO|jLO!pnYIcj@8Od8i47D|=n z|41_0DirlI+NaMS{3OYXEMIu$vTJ3R2#IrX;pmRR=>Q{4Ebca1qQyY#&Zx^V>q%i3 z49jH6xI(x)8`eKP<#OKbLG)SPT(UpOyr&Vy)FBCc<7Wi0s5lHsdC~BfsrWKNi}uhW zz)#IB$9Q4%>7_r!49ZgI1h&hP$#}*2D-Ol*`}TL1K%;aBA9xUvOd+1dLM|DJw>Sts z2ytAckY`O&^m0X7lv|J_7^5bil@db`VNU)f~|7LN6B{~C58Gadp z9$266z;T6d#&EhFD9Ne1TPgKRVlZ9j5iPyov62sRi515z{11FjiMco}(EHPAR<&#@df?9(QN8auD;43F+Nqp_SY%sTdAM z`HhDKu=TUiOTMMO1NQ9_!oJO$&6Cs7T#S8}!u@8dSkD20-cZ;-uKSX}tXS6{I*0{F z^mRh4+tQrwQY9*!D+>u@UbKFX*~g~eTf?qI)BUtZllc-F{~zPcp`X!S>jhat9zV}z zsumwoZ)2{3z91w!$XSFW2%|PIXg@Pi_+sjDEMEK0pS{C$t$ZH+ydhTToLCRpelupY zw=l1{DSnsw2F~ZF?y&vB6jB7>X;K8kr1*0HqkZ9XLrN74;%U9ezMM-N1VQKj^#%7G zy(_Ot|jfvU@6a0fK5@pT``9{Vg@=i6E$8@F#(bV0AG8f}jF7!oR>} zc@^|Exc+18fb0L6o4@jbRX#GXF4oA#dr;W;_HpW%%+r2-Yv72TPLr@f9h1lLv@r7w%Y)i+r z1p}Ox2J{i3V&VYIlUgrsiR9$umXegL+55rYktkJKSy^d+xYv!1UOo_dJu*$s@ELty z`8fUw@f1?z1$@I6971N7Eg>kS(R?O#Vq&5Wav#hytnM16Tj~gM3o+iTSj~%6lf0k9 zys@-YwwxZH+KLU}E}X^*70-zYVhcaA-A#M2dK-fL($7EcR?ewv+L-+Zm4^70T<6u? z(g4P-xt2(J9^HSU%_>lR5)#iXm@TLIUsojjJTxqKG}n@qa6?w3QU48lywc;i*V<4q z{k3}k`$;-Eh{FT>!u2|j?sM^O=bE*$f`1rCrn!g5$9<;#>6`L3jE}uAs$%o#`~AlM z5KLr7Cm>jmo6keSH=17Y(~M!!?3TxAJ6zVO?h5A}W@l&T_CzVS{;ZeNmR)ojUp-W; zlKEek6_lu3v;~-chcgNKibBwXBw#Y77=FKMqX!u{Jy<9R5!YK{M zEJNCK?6&)=JenmA7C#V_5cGf%9HiYwOjNwgm&`wz9%v1{68dNFcz+cWt(l=liJ5G7 zIC0_F&o_6EC@>KpD=_TaIy8#+_IX&td2IWHTFi!C1XrzdCcMIC7wg7` zBmNiL^irz{PNKs<%7e398~R1^o&Lrr^c#GF*D+N!f7U zPfZ~5_kDOvx4OC-g~>+g_=kqD$swCU7y*r;A&7=kHLkCx=W6~~g$rOo&VB#{m2Jne zXXx9%yt^6#V};D0dMj+do0)g^@s3&_Ep5&RIg^!@mG!KvEP~@TTaI-Hv!6#wZ1wSb zBwszLtTy{40$*lpH!6kG&#*UDzuGl<;$~d%`j0=ok@(TLDZQa{kI&&aeScJ`75ZFXh47D;0l$U@Z zUVK@61lHWD!_DCY1@;H;Q;QW~fUJwx#BLHsO^O$-cdhMi&=gQjz+n-h7AAmBz ze_ZTLtbG0F{S_JxT06O)T|kk7#Q~W27j+FkF)E@#Ht6*H$8{y2hqC~|i{{p`7+D@v zptRcTmkT}FM9W?AlaiF|m7~q9Nwq%|-eaBHXh8`}@0L|AbR|zt!e4JCipDzh&;g>5 zZ6^ybDbdmC=$1#fc`kS6w|{T^CaL-Lx}DzkG7h^tn5(aIJtlxdCDwkwUnk$7T<8kZ ziC2nlm1&jN*b}{-7(u6n_V)5<|C!}h93LHrYTO>kn|4y@XO&8**5NHecrz{UMBUfi z34hwS!OF_I&8IpnBO98OloY)BH0fR0=*d1sd!kh7!uWluu6Hgj7Ou8(mf-l-1VCr; zmteDd%1-s(Vy$;8r@w#yH8C+^M%Fv@kd&3yuH2+P;3(}F{?a`~OG&A@_s6sPZ*Edj zQXn_=NQ8Gr<*gv6dQyT#!uIIPY;C|5G7+?{C+l&Su)8(EJ$6&Vz|GA~U0uD<0(Z1c z!=Vx@hL~swqQP{?;I+$&2FDSTvWH7Y;7|(b5#o5u z)8qZ+WNS0LB|yyxTNqATcRyY^I_qvFi&MLxm8P;I<0ZQK=}|H)Wm70~sq=C#IJFQu zIl2z5_cI#S1OMpieqljx4E>sIVn#?GjyYjojAZB6NW!%3bz52TfPiKkuCda0NuKSv zmQ=R8zdu?yN#ac*hb#(cG#3@YY`yU~tOj-qz1{kmuOc%ojostqSGdpG!6-sqpRX!2 zDJjW4e(&CPknMQIZ?zaItId6#p+yt7yP<=ODN;2FdPNp@ts|}t&3&EUG#&nyp&I`+ zCrJHfP4BpZP*?3GdV{S$)JOp&N$U+i_irRHb|7F5GYzmVX_zhi^mzX??zH%MZ{7P#xvhqQ{?jzrOVy1KeHRp#pzS-4S3xvqT@Q_KDcKB+S)KKUDAnE&nErsFlP z@phBZFR_t09CMnbsoZX2j{NhEo6juM3n6eQyz&`=c^3umh>Mm8#2K|%k0A6*?W#`? zM#7`5dH4R$uF0}O za)68J#N|%?H%UAO%Bj$-7KQ|c@aarjzOPR_07XZt-0+W&3Olcw+8{DnTYX84)v5` z-zhs2Bw#!kilPd8zKJ`hs$J!4_m+?Em{Y)J>yS4BCbB)?u+c!)Lc8&bxim19mK{xo z7&jsYHbJSI-GvV2k+_fx<0ne`2eN|0|7U!tu^#o{|zMlj$PZn@*3l2=t(DS zgeEo{f@mU3ODBL#ed-H)aNGgu?q-x~;SICEHT4!Y3E67O%CqUs!R!HYnOk$-A8pU>K138IXn$&&nc$XsiazE#vl{XO>N$`nzaHOO^&eWIjLHVy ze4}bfJ+)?6zRHB5sQa1`rOcu>Pg-p^%NLW(Zcl^xbdwlvlb{X}j-;wci7Nr*z!F;@0?#9?G)07yGTlJT6NWAg34yZVc;Kdk{9Q?H%D-&dt z_QoA;!{D7&n>8m9G~}Zy>R@1=4BVNO;~Z^h14Q~h9kwd7mWUa*yp@A^cgn7?rR)$17wJqV9+XZ)n(b=S;PZn1ngom z+O#>W?lD&f?P{_S)1X2Z^-#Ph-K9Ue3Jr7tmDU3}2*c75IQ=8JM(Rkt;{PsaejYw}*U9sA{5j8PJ3CzAC;~-F|d$abJ04 zti*OST{XUL`2FtS4LSw1e4m#UlyG1)eAIIK8+m&CeZGv8Lp6@p*xlW&m-{JvOOAEx9Za)+wIW3A10#Ep6(0pm6Kx zbwxJMWQWcuIp7=;$WSS&=5<*gsAWM_4AbVY+#8?7bI>a3Z#s%K8C3i4i&H%l#r$3W zqIBb@ogC(o-XC*GYCBz4{e_edR22(x*1dfN?MK_Q>YGJ2!-cE)Xjxdl9rE_*b2xm_ z7}sc=;LyYfv^Hxx%*rb(Pk_c{v6ZHfsH397dnt#FN?b^@=#0wEf#OjBk!l!lx=$^FDVK z6g)c-8MdFkZ_d@%*6Po#4Hn~h(x_2yf=E$KX=$lv*pR)-lx5c)@h>gmt158jc?+A4 zi?u-TP#w7w$zg;Xb2PbRUt!VvT^SG=>c6B8<{4K0RM*kbDOANRf0r$ScZC@Pg{vcJ zfN?MK=cbZ+dU^uEBlEVejg^2Qj1|HJ`GC3@iBfKP`lWU)5qC#R5!C7;PC0w76!M#t z?o3m{#A>fTfAXs~Dppf#to%TkJ)rm39$Sz7 zkY+&Spe4NNcnY7|s;3U`syb%8ha;h@=`emZT`-5mbgHYZ=}NV?LV#h_3Fu5AWeQwLY4!S$X?QjMcy%z( zP~TlUBhhsPXiyn@Z#Ql`nrz#3)Kf0A_dTCxyFTW%S>5tz%xk-Uy;GTZ%D!&A_=~;P zB2bs|vY4Ls8`4N4o0_x5Gm3AN&5|X*J4!oZ1W87Wa>U!^FQth;tgRy(*O8MD1f45! zCJV!|GbcMZeXX)|wDF_%7p5|$>(-W+hpRlCYI=X=blCeel9&_kB9H3X)Y{;{W^Sa@ zp)L6*=^$M-Ues<(95GP^)N?u$B>H3Yu8z67xQx4w+O~*-x)stMJ3UHlGA=89_9vIf zudASh?jpy_o#2W3u+JuFk)35`hS$l(Q22I-QEn&Dy6WK32ltKEV9LXZuay z>$4_(wXb5fBT?VcSP5rx@|YNY;~Mj}uTq;;FcvX!i~^5u;8 z^yy#b=2{|S_zdH^{OUj3+HLy7^?STT%omnIkJIHhR#qgJQa+?EE^brGC5l*U7n;7R zikEojeha^p#&&AiCq}mSQpb?)P^H34($~Hi-fN&8djlbjn5f!gEZb@2UOiiXglA-^ z?L+(5N5r_40mgL-fpg*Ks>N^^&|iv*7z-v!Zk9zP`y438U%q!hoTx^F6?+)9H6kAPiY>)1BZ)?<{t?q?bU>v81Tm}teHc{=cNDsSN z%sp?CgPF{Q|55{r)9N18w?}18A3w4h(3inGq1AYTT6?7txT9^WOK`2I5$(W2m#}EQ z?y|4z{bC7*e;)#l5wns(UpMLzgI zcIzbw8d@d9KhUuBfPz{v8e$!l8wN^DGU-<>)yD{;j zBUmzCd(S?K`K=V9?JF-hV+$WDDK$sk*KBP~te+EFuJ%S#rXP|>;GOUh_09*M*u$Q5 z@rELF@{L##&$m@TZJlAE;k&~!8$X2Q)S7%dyH4U6iM)WH4SVizWq%W7$kh{4|F-n$ z^bjU32)#XhU44Vai*<8633>YsH0td(nxa)s)9#I^IUy$@6HO~9A>fO7ni2KdQ*!uI zHK~HFy;K>~k>XB~73Rwo1&1^Kx7MpIR2Acg@3LO<>`ahYw)(hVN!RtZPp4pXe4JIr zSU$n#de~w@iLrK4k_U1ywJYbO5%V)P0_naN+B49ALs$q3D-FIpa3 zY)qJ}R%hPH+(5LUn7?R`sU?Uz^nO=XzOE0)EovcePdnqMc=b!1U0mkT7y8Bcj2Mj^ z)3>TS4T&M>bv^-dT>rt7-`cetukKOLz+(^@gp7Ag0`c5rKHmjcvc!}hu@GmnoVLrD zzD8C2oUif~tG<|<7ImG$L-|M<%ju(fb7E_H(V?E6FIKtsOjF-Bs&RDv-YYxg%hpVj zid~##R;PB751jG1dJ&*`TX?!QToh@Cg2B}aIc%zU3RPWyx|1;BuRfq*LxMB`Laxo0 zKI&Z}>>)zont^zV-EiCr&E*&=DJhK50LV%J8-od62VOCwJNC%s1#?(}w9oP0owpqg zURa`EyFY%J58kLrlycknt{mAKmvy-hbL)#~ycmLyrPzbP6S1WXH@WZnY9T)4g3n%$ z5+|a5JDJ<@Fjv2nLt^`Adv;ce;ozl^yggoRK0kGa(elU*LW1;JMFQETI~^n+MgFbR zDzjf0Nx%3SrMrPi#yd;0Zld5x9>eA_``l<1AHc`NEz0@U5|L`PpD|rZ`=^<&dau{H zC_o`OXnVFf<8B5@{$q2TuvIVMK)7$uxZlcZr>|!aU;k)!Yvb#Y4Xg-DrM@}OZD~Dm z9Q;^XPwmaf&{ZWCL64m|VaTZ3)1|&8Hu8G!)5&E_Mn)y51)yJI^HV(X9s`N=F2!DU zT58RjM1r%&r#t&&5ClaLrrmmG!7DN2k8v$N+!nnZ!a`HKSHcUO7CSNOax&u!xZN(} zVoMEr&`r)n3P3F+)Hiect#_s>&9Lq_}tQoS3RNf<6f#B z2SN`WEohh8_$qnyKyh1uTltETDLJaGx-GV&BGn)RQM}W%z!VC8Q(Kq8P?yZ$l!BsW zvUmDthHCr*J;nbH`&qL0-|zEV><%T7B(rPG$qVZ2>=Y&sITk8Mc+WDM@Wh1AM2L{G zI(Lf<8tm@p`^r$x0VJoqeM=<*x4F~CuRdt-A3q&AGtoX878V9__BZhxn=`~ehE2X5 z{UhbJVNhykxFb3NVCH8re5yNDI9<&0kH*2B{(PiJ&5Bv`#>R$u+t+oZQKPDj-DY&Z zI`z?Z+t{b1q$KAXlcr%39C}X%qhx-V6j=}CC#Wrg*(FWPhW)D~MB0(u z1g0Q-irX0bv>yIip!e%+T6>%bHyOU*^;nq$h;SVwkOH!q&)KGu(9(6lf>Fk#+_|^1 zz8-1}>cH|e^p3O+fqJ}MvM01^wxd&W*-z8^I@lx+KX-9NDCycTyf3vaT9Ud)1 z40QSp)OIKuWWG8h@QjzARn*`36wZL8H#)2sG;ccX^k#pvtp|XaWfxw(!aE)uv+;Ct z8r$*==n3DmQw>;FC=moH{`LVj?t>`qCf?E4Pr{83Tz3~b&Nk0Z(O|;nzGsy~;=CqL zCM0=5{mk@uJ5Y|O1EyEw?V+yTm8$SL;t67=A&3pX6_{4TA{-f=UTHI4u^-Hx2j+U6 zKRNd+HTmtk&%QRO`pnQIKVe3V!T-F!)W3Lr(*-*pvuLdnx$*R~!Qq?De)M?DaKIu9 zKfp>T{J;qT2+`(^OUriy&GS*qNZ6$Yk+Z^ENRpXn1W|xV_|1)#-A*aQryBOQue?&H zdzeTYS=nE6QICL}`-Q23F-Ac9B4kWkO1ad1!#yvpcY0%9|NL-o&j5*?_r)*z)_7xg zBvseP%AFd6=@7mrqjCqo!e1`NV4vw%x~~6fzENp2Txf5uq|{uz_@l)b02uhniT%9J z&d!w{H%#jw0K#XSDp#_ydxH$K8{-XqPxrUy1D6p#Dy*uJWwqgG+=M}=ITDkK>lHaKuj78;}G|}l8 zXoxmAd$(!l8K69X(AR%xRWcL`1M&AU?cP!0ldYZKii&oFx%0~K$Ez9Y$;gJQN-9ax z5DQV z_{Bq<)c&3mDyW^MACT;#nnfJm8m+vosYIX5E7bMkGJ8>kEG}5qF!Y>lT z?IqqE^>^$l0lZ2&kIdiI^wUj5P&PWWaXqaMZm(kzZtcLn($Hnu(D!L*zqM1TQ_Ql? zNUS6hFhQCB<9OhE{7c%9fWy4Cr}0Bx@)~3Y3?>VaojBgl2O-sMPg|H5(oC?3g&MPC zE+;Gs?f7@o=xSt4KaoyV$5>6B*Wj2Vh_7o9h+)Nv!QPgcuqn%~kV+8^eyP_RF7GdXk% zrNYN4yF2$WyIC^qDLyu(z`r0Si!(vZwJYjEpi8Ow6`#hGVJz`WmB)rM6YkTcKXbwi zfQ0Ee($vjIwmX%XY3X>6Dv?htfmpoN{|_De>p-lbJR4$v8_$&9B=Oic{c8BeUi{kF%IzreDdmfqCx*;i6R!DEE6sASYY}PRy z;;=e$krxah$nO|<0}ocYS#F9Y6Hzw`*P_a}cQxwYPTkXqL1pnd@g!`Cuo3IrNtq_; zS87RC)=M`Mt#wIEdt4q{RPb6DgMYqfG;Pa-bBd_pxLaj%XUv{s3Kz*TP^nY=T-4fe zI;wV`%`a2Bp+h*Syqxlki5E2*!T#=eitg_h7&Jw{5}7Mwn>Sux?wEV4kc2 zQh1?Gg%8H!aAalj$8LDKl`qSlYO1jQrEn}dwQiNHsO#HCb-G-nKRI3Wj6d&ptb;C* z0oq@Pa#L-1Fe~RmS!Wem!=u>PA^dyYe~G`jJer1aeIFFdQ(H3SFauOaDr7p^kF!v( z`4dY-+q=jXA5hX~WoT25Uir{l?924^GJVO{%^}=Z|G?~x(sqC0s?_vDBdw?&(d|Ef zRVJCEL)Hy?p6HRg;Hhp7-(>Nt=p5^(>v}Lu{M_LRYJ(D?E&G>$?8o%G|MJo;$+0{z zW65Cw^|u4x>chFhqWLX&RTjubb7=ltzroFwe4)fXX)tc_AEcHzxy}Ck%|2c8!1D4gNIc}ax3)hp_S}5&KXo$v4y|6UIcwK1v|@)(t?Jfh50#s zhRzA*cA6JQW&!ac-s0rDC&6-dOM~^=4$Z61ukqRkZoI%i!7K4Hp?p*Uda)5O373l((ZYp#Xe!@&Pz;AORmpQGGVP{`=MO3e!%O)W*$+-tg3evx8j`LKw_3L&T#kO3V>~25I7pF>*Hma zK_b6Z`G;6Flo*4hM5G6QyCt`Ovo!mJw|Mj9GnznSCZtBtGABvrM28i+g)b=s=Q1;t zkoOC&+=u@oqXAsJghUH){U^=g|EHU8j155&>v*MGjE}HId+dU!_>?FS;1z;IjT9GJ zi|qa{Z(UeM^Xp%-Wa2eeN+qs&x_@l$Pg}Jyxrj#|9MZd(;X6+xE~xYTC$puStnN&+ z{J)&FrPR+Gm76ttXCxl8inmHF-75sw zi}QWft#3HS?Sy-Q^v$$S5`ILGVztWO3di|YGn0jO6sqF|>UuBltPi&+xoU#=b}>dx zzruN0RcySkXZvipvNT0CtO7bB-XAY)lc{`|A7wV{5<%$}XLC(MYp8>$nCuZ%=Dmw8 zeghtZr7Haxj6A`KmXetbTkLC?`vUD@Vygx`1lkqv{5W4d-X z76eXC3~gjKcRRmjsPAszGAa+-lgvFo9ta+;kH&;ygVep>IJXZzp;D^DPqKO)Zmgs8 zbi{v}zp?l{^*eIJ>UU5`wWX5!U4uyH{+^4@miceaP)tX})Y1h*XH`Pcu+sNhGOb!2 zu1atbf= zxFO@Y$IgcRnpDp4ky!epp7*I+1XjvD#Z&{b3K$$M`W>}~Rv zjumQ1W;A=(Gf#lJvKeWtvfkpo#y4QDzP}YGqV9)(Y(y_^{;iKP-lrAis$b2c>Qc-Z zlv(03bY#jsn&8b?(S@D16xJL>eb{&`Oj1 zRSC?#jFToJY8LUWP=vQ>SCUL16|1MC(z?U?-Z$J{tkvXBEJ0x7sGe=Xw1(?ltwyU{ zIr}wN+|jm)UCpY__Pc!>bB5ES6$=aXMoXKLLU)zf5#u7w&-pcH?Udf46ENPGA}yHL zN>c&s(Y9vtvDQkMA`F-O=Z-DYMyO_KS^@k%bE%OPKEXENGlHQ?DCTTr`N|36Fx!{+ z3gqV^a7h@deL4q^@wlV;m;6{wo^?6?NDQ(2mp%OZCWt}w>>kq_^ZD$q6?Nfz*iJP@ zI267xV`MW7nVMBvP18Tx3hu0hz^(Ax0P8=)?Sdj6QN!X-r>#xuR+>U`%w;@WB41@k z{+`RVnCa-e?=@pHB*t7bB{uo z^ltlek3664M*3#aZEUlYg%~$+Y1()=tCZ_ZLy-8R1n%&l`=rftcka#~{=R!FqdDQm z7R3t7Z)u;MQ3j}3<90^Gps+Mi~dYkT#nE1KA6?^K+F4uK% zcTjCGDYZn-V?B5;*dz3gDW=ouKz{v1L+Wii*O-J#!2qM^t38{NuB2@hx`$rhEh?1{ z_7v7N_lyT!2S+!irNW2hxb{T)`Q9e>rk9TAR?a6SZG~uUCQVp56d%t#X40qK*oe2S z+{ksjP6=*uMen&3snoGsgIN>fk~R#wW-iXj?&XsbvHjzn|aSe>%r0*OFGMHWgsn1Wy~U)Wf1oO&pdh5l&PT-etj@L^6K4dO3y0$9K|~$dcjuG zr5)a=l4QHX+~2z_sEKTf`St9(^p3R1mhtfqV=k#5g-35bXbFEodHC-yzs%skwZpo; zBhwcZBHP>^XT4AezVpA@d+)cT+c*9n8+JF;GShNqR%WK=#D(5vj+{Bla;Bz;;8x6C zS(*dQGPhWvsF@SNful0_AVpJfrKpI46ZiTYulxJ`{12ZW-u%cR#{s_1>%6Y(JkRIz zkS>@n7Eh)w^5rjkgXF=?&8Z8~Wfmq}gDNeEw?5*hBD+kz0kv5ztfD zV=p*B4x4%y1evZc8a#~=+n*b62;~ve3fmkDI%Gn%g&agX{sFw-5{1=xwbAN<^DAv) zWj#E0@uvzf`5eD|U$@NcP|1?1QQ@1c5wN;jEk#m$yQAvoidER*Bv0gX5*o6d6|Q0N z?k|wk8RP=;CXnd*Km4vYUL9x=aLl$?ox>z?ulv5-4*4$>r9yf|(>6Zj?oIElk#Zrs z#|7-(^@h&<{tN@Db}B(mqwTR(#ts>OZ!9!z#W%e@I_rHUN6HD7(cHI~tOlTJf@K(f z+)L?;ytcr6FhhQ4ZYx=)deqN|MG25`&f(2=d~kyYa5}o@HoN}Sh;`-dGem{;6Ccc% zB>OAUZs(u4nbSir;JGZ0!v|P5{=lMBV)y#f8dIEK-nXhZaA5Av$VCPR3~!yC_{Bk+ zsB9P1kEhi~wjp%CJ}k&K7r(h(6_UVD%<){!`qRIfzB1w$y;mvvN4=OWUiXJDIc`is z+KosY4HSliRE<`vfaC=O?P!HpM5rs%AIuIYx%*Fu)RY`3^P;_B$u&Jl$DM^;I39+Oa=Av< zt>U*+6*Uj9eW=4A-&4oVfj|oqpfjN55M`o)!}h>xvc72TWVFy!bFzfK=<~lX8Ld(~ z8q6~STEVN+Q)ddFM1({v|NX%Sl8q_i`e5MiU|n<0s<#>EmXx8LH@6gfUJ@O)+5v3j zF?+?pAPuaxQOQqMXuf%8f4Kd$ybA<#ng<0MCuFHJVJw$&c->maB6RNm|=?qk*)0RN3j&~A;2_C zw&w4vLf+(hRr{-|$G4+%#P3NduH9LE`fXS>;-iEf4CEUZqSu>tZcZ*@CEj;W)qwAg z;o}f1k(jt8AQ81~;-APCOjZNiH|_h&qsf3{p6s>D5eYvHahYw5{-`aBJ%atQWV-n= zhU?pd2EF6fac&oi;^xmiNmhAl)LSc9Ad~|Sne1bu!#5~fdp+I}ig*e;K?`}S*@{&s za)~#zr3fCYxm3S)B^g$BBatueZKPR_dSI7K^|VE=&TX2`lV`5A)0i2f#9M~0=O`CX zBNOUc!T`!-Blgl=0>=hs<3VYe+tT2qp%MyU%T)Y z7sfDh%;d_y_vZf4$lHI@0NAIJlTO}MczuZWs}{t z+gyb?rsw+H^L*8k-xunl@>62cob%&z=!h;)DAjg+HI-tV^@yQrPYoL2l;EKe)}uxC z2UTZu4zKn@xI8e(Wc6I6g#=JF4bru+tYx$BR)mvSfmV7mV^_!STZSh5nr7U%^2T>r zPjKh(h6mIw%(APS!tKQ8mneo|wQ(}+;|w+sAm!?PVbCJN6;_mZi^n*(r+N!4tD=<> z*)eeBJAUyW(2K$IAWqQZ&+Piu&oQEg9Dlpyn)VKMm&82Jmex$gv0V_9i>Dukh5$h| z?oWa+c4NdMYAQq>T-tlx)f$eT;GTAwwfZG z@{Irx^gy-*R*dkTk>id9x3Jds^oJO35o)#f1YodecE~ovOy{@FG#hH_Htj_FscK9Hf;@ncG|j#kuASq9@7}2C4r*M> zspt}N@Y~y3QEiv09_6(T>C+YXXx2)zX2ASHj^!qupD5UVX*oOn+eTPZ*ZE*8=}^_I zF=TbRLqF(VZ{7(q>zcVfY`iJ*aA|*hX@=58o7fUoyL8etsY!yvwNdw6-bD+m7dKvo zW$T@*+KjtyIa16n4QeRP{*LP&@i0=Xw{3t8hzGtqtjWx$@2PA19Y*{z1eym!F*I4B!;P`4|l#pph_+=wHi_yK- z9elAPUM!~8X{RXuaAml4L%*Z?Umv6tL0^>5H-jxdxpx?@I6!dLe z$dv>1{bs)8S1C@Pzu#XfNw{Bf>2f@Hu1~wP-vgcqBO7_uue7o!0r0l=Ub8lHj2JW= zd^JZ!A0Jcx_tE!RocFQ`zd%ei@5;XxlN|b}4^m6_Z z5UFTB$B6A2vN9`;%E`C1d)x&UMr#gkwMPcxF3&&(K|?reb^CE?0lSP|1D$8> zH{!9i>J9UZ+D@*{qp5hfRW;cS5PCFJ?LF4!y<~rf^6bkqa+zp2yKgj8kCF6?Wctrx z<7R`Lhzz)4OE}}*6>{hX^M0A!v!e7bT5XB2%$F8QrR<--lw#H{VOXP)hx=sVc$*!K zrZtUye3hmf1vOykM2(ukX&!9Rig-oU1Kcx>Y8Tp9(8$ZTUCo7Wuiownb#ySVdgWe^ z8F!(vKEk7L*@B?|G`dFm9am?KzsQm&%jP0Y(23- zPr0ej&+?Oe@B1%J+f2rU!NWXHn@}MLiQT0jAN;SU70pf$H>m1rz5B=R;4%CG;{}Y3 zB-+<+`MFx?w}>>M^03&<4>nydS68d0F_m5g57=`7=4}z{){)9%%2@x`#$>`)oL*Ml zDRqgkEtfI6nR>GKzTxD2Oa2KE=<#`ur=S-@#T!pM*^?`6VrF8A!fFa3nHls*)2N^V zB3;hAVg1j@sp`cVphxBnxRz@y)?nmYgVIBfX+!?brB_cx>@sl-P4u^vwuX;p_WC?U zmf4{scx*r(@4MvTvViI+8$K9brTDIj@N49RKr(GlDBdu1jdO}M3K=2^%e;f{94*>l!F zi?M47-uH$QqVP2krt~;?JiA=!qLSbBra!m8f-q-wxsOi24c(cQ@eVc$E!(=jqrlRcAnLLbXpe~x-YYJG4))o8YSz6!T$#D$fH(;k7v8Bef`t#PwWj=<nK* z6c#>}I)ux~cN5~XbES%0pQ?&!D#%C%2J}s zHZV-Q>kf7nfNJ4Eq{hLFq~|v<|jtxS@{+CKzHr@d)Nw0=FuvGNBvmU$xKXy?Zk)JKB7z>sXpphnn4@ zs!1!me#NCEH8CW?#?!kA``~ot^*+mdIn39zjzmIsT_Rtf@P)-f#aqQ*9;>b&Z&<2+ zHN}?)WY*OqVqwaodS_JWlac#Fk%Ox~)X;=}#gcb`&ouS=@pfnya?cit#&Zt4*Q$}- zg0elmny2kQFns3C{n4ILWRGlzYQw?jg)w{3C3hC5@`&b0_0Q2M?9ojBc;fQU)B5=; zBd1ZaksI6^GMHsTb%PA+VnNnezOT`In3_wbBpG4sh^0K}?EV(`er6FD+p#p5FBHUI z;>L#RXd$X9dBXl41&-?2O)`u?KSPR&78?4fmA7dgol|Yrc@TF@%v1+%02r zDjb8x0%o1)O~5XKpZK z&rA9xa>+q*bFNDy)oue2161z;6Gy_zHv%%ukb%(gV7iE^&t=1_5N9nUbC?S}diKrw z8@VuX7W(G~V`ZOtBqIK(r+=y`jS@_Qgl+(ae1;Vn2F8Naz~#R`+6!rINd;p11zW&S z)#`LdQaIDayv!NLc12tg04~X?Mi*sek7^pORdG*?6VwPP@t+EgiR^ppbUgh#cwy3=`d@Y%# zBls<295u)Mmx@;sG!?xv;Sg0H9!G(jO(oPW+z@axl~JMw#*KfJenUcoq2{6rs-1-` z(V90G=2GD@^1OSv-2HFgP!%Q_F)-zY>=;a{LosWH;_pd1>O;1h!q0$^6u*KD6{{;{ zsKA<-ov;hAwy?mrFlU zxMSfhj~v&5X{1U@Xg>PpO+8{k=f3BY26zLxP`hKy{A$r35tH33#fL{g-_W4jpzk3G z=!N{4a4C?mSZV!gr>5~$tpc84;SuKN_p6^L$!6O7f1`LDfYB+35C#f7{(K^Q)02J1 zm+UKvYNy=ZWUC}`*>``L%%}eZT-gC&edOT*5RUT3!0BSmh2BviBwkfQLPsRvCVZN? z06?V8xH9C9DJ{RB!z@Srqua}f#acSIuC_O#?jkoANuGfXJ(kcgG`?c94bL6B?d*7!LpD{*)F} zw5B@sw!pt*RjH;)wZrOE-%oRzn1#0?5nIcXe5?1z+L!wiD)DEZvi{=#-DP*dmcNaD z>*GCK<~h8r8%$1(+)j&B>ue6l9#)n4?NH2%3ySGY5E=VSaF4>&QQPDC?hA>yVGf^+ zA_#&*cYAYAE+Pb)r}IA`T}2T_td-P_+ztJT9J~0@Xh(i0+rYw5Z$5E@HDZm_pJ~OMId_s1^jP}8b4kUj+SBa- zVwh?=XQZ&^^V=mnY0=r(3$v^JpCPM1mg?7fi%aV~mc|>v6JcvENsvdS{r+Hj*?nEAHAm z>`RJZ15VZ};w67HPeo$!+G13BX2R?E9w}E3OwFV+3A3ge9`U@f!uuk0Dl5Sd+PQCyhf9~QD3pl-68tMfyJOWoEz19_8k0-odI*LO(Ij z)~7_{2IMnZGn;l4=hnP%PA-O(qgpqaMz^%fB#Jl#+>i9A8)3`OvQT0b$P4|98 zoq$KJ^=CIXn}RT@39z7K{^nkMK>D%Qt3^=j&*P;*nX@|c>K5yXV&tcd?w@CPXHkZ{ zhqKl`7T4KIaa6uV+~DQF2f4eW^zk+H<{1s&A616%FvnRZ;$ly#5%CDU>;AcKHW%iZ zb@0&qlQGZ}>5CS@{Yd5BlkH^>2j@qa9!GRsufM3gGsyoME&ADb-kCAY54K%dZLN*l z7zKt6~*r}a=93&7`tb+m)&eB>3g46; zYLQPAk|M4DjpD(UuD2&kz|`j^XZ*j*ib~2HL$8WgC-=SUT)S;cMh@_<(KREN z0-~H0;6W14Y_Bi%ct@-ua&u>63{Xl<+43Wtn3;`$F>-rAXBA}wJuTiY6c~%!EE{c5 z`nrMwkEcgG99Ipo8J+7%hegF%4vp8PyIGrAbny?7V`m=i0m^mjz6}#K@L_g(<z5diWR7igsJvG!C0^S0VPa&_x0Ips^LY_ zhxtLz+Vp^mbn#W7BE6#$H}>bQA;W3rV1HM8ErXON`7ufR;JI3kiw0GFbuIT$Gie`n zbdiFCs-JvD|E!m!Tp%zg#Z{)ZRKMm$WDzJQCRS#EV#D5xny`&btPzT5$j;v+?k8H+ z_sbdmK?QL@3kg;am4etNIRY$`=HB!3eNz3PMF0fO=sGy}o%~%dRIT55TbZB{SWw$@Xk0epy|vZhM9o|MHE|8vvM zCWF^EY;M(1AN01Q_K*8nT~Yh;=uc^-7pe6HtqT}RReN96CFN6~T)(Gasw6Ylp4+}L znu3irqp0>wy8FV~3IkmcZt|l3ds{1P@iI#dt0RN9Z6O-X&x;lS4Uy%LFLSt}?|Wy9 z1ie?S@=9BvU&~v5KF`mJczo*P#4J)rmkfkHSs0uetQy)sU{KIEF>2rv)pEgMBnRqM zJCk*SaV<}QtXEwQz|Cg%_=VO{7^S?Ff_t)$Xf7JjvwxTRz`oxZ?qijATHXXCdXFm= z1(&?+OI~xpJj&kfuvtgIJMDFzeK|(jK%?aG5Fu|jy1m; zs1%?ObGEc$gUS|<#Mv`9-Uv8sjncKiOihe&@=vE`$yoAlZ_h1nk`u7d`936`214@e z;q1Am0LrMOdVB}K-<;qP?Y`fmX36=)B@JF$>-KzmdKjmd{2ow?G_eRHDv;gf{$pcO zyt&BOF@2|UVR2C;=fxb&U_bC6=FzWho7K+W$xQQCG@CEfJ;1EmeWJa}U>pcB05S8m zxYUs68^-z9rsFPkDBAhVjx)8g9n~HOM3c2c@XQa9c=Msg;DFoKspcS_nuMN;^;M?^xQP<)!t z)UQ||Y+P&aSzzoee$ORpEMDZgllZIP5>28gx!2$sRT_B+NDv+1Z|jmDWwXrYon?9y z&nxM1E@=_{eqEE*)NDeZnD#NHtc7I^*fakoRW|oEB^ z2n2sGfen7s&fb#ismjog&X0MI6Cc*>fNqaapl>(Ff+hg(p8eg;i2$-mTaws+ZlFpj z_YGq+67O8n4es*st_^4TvgUUA@C_`&NUz3HJzutH@Z9H^#kz%3irTv*5e@!J@7t!P zeea^W>zhPHvEiL(8bCiPsAkLEsNR^0VPESyqPy63_N z7u*oc2wv*}7Zm=>#|F4Q2Hg#t<5{yoJsoc|-(3&yIhtgcJWGl^SR+5H9*Rm(x~JlR zbE$eVFfH70gQx!qhn@!Z)xv0T&SaL#V5R-NR=sF_mD$RO5dFw2P0MfO5Xto|eSsM7 zQsQEr!6c&g-b_;Q#p^UQ=I6ZR+ zh?AuB@dAEL`d;0HU+rJ`9(Lx0c`AHT0Ry=zyCd z3uv?)(zFjS=E-_sA|J`f^L7K_ed(2?zX|n_@SOlbA5B{n#xjnpvsspawPWKsl|{^{ zg{|8I$ph{WSb11}u%YMlcfjvyRn=l542c2-%v95n7)R>jr^}C4t?k&RVNkrtLbn zT%_jCuuIYmXxaVsLIBraB=a_rMHuPF?UKS~6#=}HeWI1n?A-*Gc@ye13Q zp_DkaDnY0Hv{a6d`sI);&ZE8OBRh4_X8?@mSPF8BYF<2%(4BC!l|p-gqHnQI0fupB zKD5J6jguBHz}_D1&mn5JCz=ixnnaP#HoKckNzy2Gp6Z?PN?%xVv+LL*iH>Z} zEip(Y7(5d!%I3^yDUxtXZUAa(ta~dXH;4mD7WG#@y5(t`TpBZY%<5A8>AlzL4O~Q| zuHkK{6S)bc)f-1Tl|0SGEq}SHTl+)T-=fzbOTnZnf__KhEW^IO5YXwqmmlH9oI?G~ z&5;Qw>Z#XV!iRpf5xQl&=w7C3{mh1GfrbngR(UPz$;juaq(@g{Rni@5q>6oo)k=d1 zf}26dU8(&Kf60SA9^eytw#laR?8`IV&3K2xKgVYxDj!YxwifbKxLDz`(`juQiK395QGZg;?0D5%p^S{;-`L2f-CipDIB~zf(;XjhM8sI9MPq)K)SApj z_;j*MI8Yuhv)7)O>74~V64>v@nTUwU`v|RVd)NpSqQ3>;OyHuA&Q5Qfb$5{`_zO_ljw@J;Cz!PEqf`(hCo0d;wER*K_YiWp6 z1$RnX}iu>giHcc4y=T0ALzw)2X%a|RYwpz=qUqnjff>cI-B`(Qy_9hK)+y) z`N?g9acO=@nw_G1s6c1EaK`AM#}|Y?R3#mhwp-h%Mi6L%_MwrVHqV&e9-r`u?#McYYOI6afRYCOA>I$$RmGLKgjm7@78hQp3~7nXY--oOTOtLMYz~7uYj|%n9s}?Ly+2Bb*@B z6aU}b*|r@Xs?ZF}_7hAOR{!!|r%j@z@BD^e8+9)AvuJ7h7-A*ETO&b!cdmBL97f~A zOLoPRm4`(6&2*Y@FPrrYTvQN$Zd6kA*H9j~-OSPLUy+4oz4ci#BiC$&o~m2@G{~_; zCcV1CdTgcflNzz|@e+% z#*{)F7Ge6H@Tjca)5R4-h4Jv}H!@zQP@W-on{t*oZZ*QJ7XJ0{CJlJ;XQj3%57~XF z!}xtZ_Sq5i!ao%#92$1(y`P!@H?E-M8n>q0yFRP!AUD!cYlPf@AyIeP>zD2Cdb%&^ zNeQd)SS9hQnCPGbDkPE04c~91wVdrzTF`5QooN=Cu{}hzVjQ``NmzJurodWo`stJG zU&Yb21`k6ELn6yRi?lF=h1E(xxQglC@&K84+Pf;>of~ETH1x#_;4m_9mo36V<4zU} zfmQYhE$Lu)JJ}My07Z#`BFo*)B{N)!R)fpNJr>7r+|py?3H>71^(mibO2ntbl2_5a zmrnDaZdfbHq3HgC4_2-@kSS-XADcdpkAASg_Rt-TC@cKaBGW50q39ke&u8B|I6C(* zddJ8#o(rcfsnI6wZMD6-q%5?ak(KDz3zSdifHGVf_Ge<_Tu}j9(B!#RiQ=mt|NpL7 zu0p=^9d`#aIkWISwJ8TOJD1hF;)GJ;8R?r~xJPrUX zr?U|3b*7-=Ct1_)jV)UY8`rtWHfM90XBKc}9akTR(82)XKOW-mP?^sVd-$?&nZxJv z14SB3_5M^9fusGb$#B+>xAC|Z!o-)k?CfAnMlM(+SWT&*9_<;0x#kzT_$VvU!5$Hd zy5-6C%R;WB;@?H*r7Se_o=%W%kr||3xAr zdc#V4-B;Llko?e+`1lj+x2eol z&Ou3wp@6`Gr*@}z(qCp7oh%|SG~)Hlnpo;p!Rg}?<{-g^`qp0JH~eh9N00qQrNq}0 zJBl2%59@n&lTf?-^R!uxIzDJ=d}|X>LT-+Snq{lXhpGt$Izyx%2R*8%UcfiELbABB z&MZ#2+rMZ@9m$2^O3I)o2TfxB%Bif$c!|X~;|0Kv!;U_cw+8PRVSHUY*&VncKR4)x zm!qGAkwC#zZbNwWL>H{Pqp3^0_5`-nO`4x z1!lp-q31QTI&@r*`OxA*QmwL0f_Jl8pBFLicXelr_&Q(-N=h+DRb<0d@Tdn~5;ORx zlk6k5hcpjPcm1@{4~MuR<*c720xoDm{6T_e)r`#|{-`8g;2ySU{*04sVmNcjQ*U2y zqg5ma6<~9Me__~}U5yln6o1~dTGE4YXMG#3fBqds4GmqTPv+m3LsHJb%Vr05=nBEx z$ZYNIXr2+Sul6qs)^SKMV)G*YW0S^`FN#1Ri-Wx;wgtPog>M=SY&IzmmS)5cmigk+ z-R_d+n^@?`I+Ki(YCC9eA~~xJ$Vs)||9J{}ap9)^VIJO1ALBz+JXsz2t%qA(qusCY z+n8!VLOYCyGqY6CR%l}Y$LZra&fM53BKzLO2M|u2mrJ~qDeAG%MnU0{43Xr)OEI-S zJaMN@;mDx3-nl7+X2a z3HA)YElnW$&WtF2A?Z=wh<~VDbxn8HC;lC2SSjEBI+;b7QqYEPR+?|999)J?1jE&8 zA9Enp7;Hp){eG`L{mPMel=BLyywy_x^uqr&5EdRXoDW85rXhM08+(Mv`&8;R=5=|U zwu_?_|3EBKo^w?66XhSy6e5Y+n`(kp<{fSPE;DcI!ZAx3*f`TMR&_b#)eh?NlayCtsu&iuSgy?;g$7aV+=2a4cpa4Q;F5!v2A(&wIIZtK~$) zcFRJB!+srNFX6}LHjo8%Ef9FcjBM+nx<)Q5bCPL$Imdj&$qNK0uCga$JsNyj8x>o! zR?wrAsdPM>Ef)omdDCai)NzevE8@3Srg9%b%P&;pTvg6Tn~dvofS#5B&-M>?!>69# z8lF%B6ARm@7GmL5#TexsQH+f;LFpk`Q$-CWx(?d(2wqUEIsK&r9Z8KGCA?i?ivhx8^VCUnyiNzQzb#aSyq=DX{f*bC~$ zjv&C{a5{4?z6Tl7p~g8X=1ZxX4RWAj$wzWmS{q?r-hGi?DX<*-PCdA|_qirJyXhMF zk0Fug7lq>|3O8(TQ65NRK6}^3poN@?gL1Rel(}XN@0@+eL(jl@#(lVdpIO12z{+uu zD)hhOBw0^>K<}3?_~e}CtRB46E2y>PXq+vx5_UUq6KA`mFu`v&aLH=X?ni)o;)a*7 z2j(_OFDkuSmN+)O#!shyarRF8N!ir(=rZ3t^J$_m1Yz$Dw#f=6zxUud9CN?Pq)d+4 z27M$V5^x$`)OQ-LvXDWKm>YzDD(a8KY8w@#=cEx;jxKw(j^wP&7?9=@ zp%T@OFWrb)ZqM};pemeUD$c3UGPU|TMv;tn*oNQgLsB#4sZWVUW_Sh&^uqrGu#_`B z&Iq-WEE=z)npfIc8%SI z{TJjNpk>H)u@aSfM0z)YYS1cMwt!G-mht$BJ-1>)?+SloI|Zk=4n(quh}w}Jj`CeY zWqZ|NGAEvKp+RAL`&7W4_y$WE$&rPg&CS91vFCJ0ZYVAMn_iHTh5%bHQX2#+7d!$? z`Mkz~D?Pw2PkNb&_%}w5B}T*h6w=l+0+hBzF4nTVL}Yv>6oXg~UpiLI5*W-zDDp9q z#W!=R5jXj+H`a$_#idq^5AuCDVAH!4@qQ3F5C(p;W#hFJPibuN!$Egt{q=hBS)BMl zdv%-(o7C@AUD!|T?ocEiYo>I)gys5d)JcFqk90s6K`#c?^=Dr>z|m!XSLTLv8#1{M zMN(f8%N7TIl>Ph280RWa-Jds2-R+!SgG14i)|C#P>I^p1nZ%;{aY`CH^o+3vdpd^h zR%?f>eQMI+<>UmZf*Z{F0rY~Rjgenn-EbbZe3Y-H} z?gi-DK+6goXc{0cH>AzOL!L(z{Pz{U1H>It2g# literal 51845 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 From 50217726c0d026ce4412a5fcb8706562e91851cf Mon Sep 17 00:00:00 2001 From: Alexander Ivash Date: Thu, 18 Jan 2018 22:29:07 +0300 Subject: [PATCH 17/52] remove extra qualification from DesktopPreviewProvider::getInstance() in .h --- libraries/ui/src/DesktopPreviewProvider.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ui/src/DesktopPreviewProvider.h b/libraries/ui/src/DesktopPreviewProvider.h index db87009205..07d39a6cdf 100644 --- a/libraries/ui/src/DesktopPreviewProvider.h +++ b/libraries/ui/src/DesktopPreviewProvider.h @@ -30,7 +30,7 @@ public: }; Q_ENUM(PreviewDisabledReasons) - static QSharedPointer DesktopPreviewProvider::getInstance(); + static QSharedPointer getInstance(); QImage getPreviewDisabledImage(bool vsyncEnabled) const; void setPreviewDisabledReason(PreviewDisabledReasons reason); From c8aded068814b238348a409516e2fd46a224edcf Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 18 Jan 2018 11:54:54 -0800 Subject: [PATCH 18/52] watch for attachment texture loading --- .../src/avatars-renderer/Avatar.cpp | 19 +++++++++++++++++-- .../src/avatars-renderer/Avatar.h | 1 + 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 95a8a9f1fd..500a24763d 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -791,10 +791,19 @@ bool Avatar::shouldRenderHead(const RenderArgs* renderArgs) const { // virtual void Avatar::simulateAttachments(float deltaTime) { + assert(_attachmentModels.size() == _attachmentModelsTexturesLoaded.size()); PerformanceTimer perfTimer("attachments"); for (int i = 0; i < (int)_attachmentModels.size(); i++) { const AttachmentData& attachment = _attachmentData.at(i); auto& model = _attachmentModels.at(i); + bool texturesLoaded = _attachmentModelsTexturesLoaded.at(i); + + // Watch for texture loading + if (!texturesLoaded && model->getGeometry() && model->getGeometry()->areTexturesLoaded()) { + _attachmentModelsTexturesLoaded[i] = true; + model->updateRenderItems(); + } + int jointIndex = getJointIndex(attachment.jointName); glm::vec3 jointPosition; glm::quat jointRotation; @@ -1319,6 +1328,7 @@ void Avatar::setAttachmentData(const QVector& attachmentData) { while ((int)_attachmentModels.size() > attachmentData.size()) { auto attachmentModel = _attachmentModels.back(); _attachmentModels.pop_back(); + _attachmentModelsTexturesLoaded.pop_back(); _attachmentsToRemove.push_back(attachmentModel); } @@ -1326,11 +1336,16 @@ void Avatar::setAttachmentData(const QVector& attachmentData) { if (i == (int)_attachmentModels.size()) { // if number of attachments has been increased, we need to allocate a new model _attachmentModels.push_back(allocateAttachmentModel(attachmentData[i].isSoft, _skeletonModel->getRig(), isMyAvatar())); - } - else if (i < oldAttachmentData.size() && oldAttachmentData[i].isSoft != attachmentData[i].isSoft) { + _attachmentModelsTexturesLoaded.push_back(false); + } else if (i < oldAttachmentData.size() && oldAttachmentData[i].isSoft != attachmentData[i].isSoft) { // if the attachment has changed type, we need to re-allocate a new one. _attachmentsToRemove.push_back(_attachmentModels[i]); _attachmentModels[i] = allocateAttachmentModel(attachmentData[i].isSoft, _skeletonModel->getRig(), isMyAvatar()); + _attachmentModelsTexturesLoaded[i] = false; + } + // If the model URL has changd, we need to wait for the textures to load + if (_attachmentModels[i]->getURL() != attachmentData[i].modelURL) { + _attachmentModelsTexturesLoaded[i] = false; } _attachmentModels[i]->setURL(attachmentData[i].modelURL); } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 3fe14f980f..c2b404a925 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -306,6 +306,7 @@ protected: glm::vec3 _skeletonOffset; std::vector> _attachmentModels; + std::vector _attachmentModelsTexturesLoaded; std::vector> _attachmentsToRemove; std::vector> _attachmentsToDelete; From 961ed88fb094515e95afd656ba2e920073cabfa5 Mon Sep 17 00:00:00 2001 From: amantley Date: Thu, 18 Jan 2018 12:56:24 -0800 Subject: [PATCH 19/52] fixed coding standards in RenderableModelEntityItem.cpp --- .../entities-renderer/src/RenderableModelEntityItem.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 8b5a23b787..fc1688974c 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1397,9 +1397,8 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce if (!jointsMapped()) { mapJoints(entity, model->getJointNames()); - } - //else the joints have been mapped before but we have new animation to load - else if (_animation && (_animation->getURL().toString() != entity->getAnimationURL())) { + //else the joint have been mapped before but we have a new animation to load + } else if (_animation && (_animation->getURL().toString() != entity->getAnimationURL())) { _animation = DependencyManager::get()->getAnimation(entity->getAnimationURL()); _jointMappingCompleted = false; mapJoints(entity, model->getJointNames()); From a97b4b010dbc2c32089000d153fea86ac0209e47 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 18 Jan 2018 16:51:28 -0800 Subject: [PATCH 20/52] suppress Interface notification for DS decryption error --- scripts/system/notifications.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index 11c083dacc..728760c1e7 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -539,8 +539,14 @@ return startingUp; } - function onDomainConnectionRefused(reason) { - createNotification("Connection refused: " + reason, NotificationType.CONNECTION_REFUSED); + function onDomainConnectionRefused(reason, reasonCode) { + // the "login error" reason means that the DS couldn't decrypt the username signature + // since this eventually resolves itself for good actors we don't need to show a notification for it + var LOGIN_ERROR_REASON_CODE = 2; + + if (reasonCode != LOGIN_ERROR_REASON_CODE) { + createNotification("Connection refused: " + reason, NotificationType.CONNECTION_REFUSED); + } } function onEditError(msg) { From c6429f4f33b437109c46abc48347394b69545eb7 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 18 Jan 2018 17:05:36 -0800 Subject: [PATCH 21/52] Path towards pixel perfection --- .../resources/qml/controls-uit/TextField.qml | 22 ++++++-- .../wallet/sendMoney/ConnectionItem.qml | 18 +++---- .../commerce/wallet/sendMoney/SendMoney.qml | 52 ++++++++++++++----- scripts/system/commerce/wallet.js | 16 ++++-- 4 files changed, 79 insertions(+), 29 deletions(-) diff --git a/interface/resources/qml/controls-uit/TextField.qml b/interface/resources/qml/controls-uit/TextField.qml index b942c8b4ab..e2552c24d0 100644 --- a/interface/resources/qml/controls-uit/TextField.qml +++ b/interface/resources/qml/controls-uit/TextField.qml @@ -24,10 +24,13 @@ TextField { property bool isSearchField: false property string label: "" property real controlHeight: height + (textFieldLabel.visible ? textFieldLabel.height + 1 : 0) + property bool hasDefocusedBorder: false; property bool hasRoundedBorder: false + property int roundedBorderRadius: 4 property bool error: false; property bool hasClearButton: false; - property string leftPlaceholderGlyph: ""; + property string leftPermanentGlyph: ""; + property string centerPlaceholderGlyph: ""; placeholderText: textField.placeholderText @@ -101,12 +104,12 @@ TextField { } } border.color: textField.error ? hifi.colors.redHighlight : - (textField.activeFocus ? hifi.colors.primaryHighlight : (isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray)) + (textField.activeFocus ? hifi.colors.primaryHighlight : (hasDefocusedBorder ? (isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray) : color)) border.width: textField.activeFocus || hasRoundedBorder || textField.error ? 1 : 0 - radius: isSearchField ? textField.height / 2 : (hasRoundedBorder ? 4 : 0) + radius: isSearchField ? textField.height / 2 : (hasRoundedBorder ? roundedBorderRadius : 0) HiFiGlyphs { - text: textField.leftPlaceholderGlyph; + text: textField.leftPermanentGlyph; color: textColor; size: hifi.fontSizes.textFieldSearchIcon; anchors.left: parent.left; @@ -115,6 +118,15 @@ TextField { visible: text; } + HiFiGlyphs { + text: textField.centerPlaceholderGlyph; + color: textColor; + size: parent.height; + anchors.horizontalCenter: parent.horizontalCenter; + anchors.verticalCenter: parent.verticalCenter; + visible: text && !textField.focus && textField.text === ""; + } + HiFiGlyphs { text: hifi.glyphs.search color: textColor @@ -145,7 +157,7 @@ TextField { placeholderTextColor: isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray selectedTextColor: hifi.colors.black selectionColor: hifi.colors.primaryHighlight - padding.left: ((isSearchField || textField.leftPlaceholderGlyph !== "") ? textField.height - 2 : 0) + hifi.dimensions.textPadding + padding.left: hasRoundedBorder ? textField.height / 2 : ((isSearchField || textField.leftPermanentGlyph !== "") ? textField.height - 2 : 0) + hifi.dimensions.textPadding padding.right: (hasClearButton ? textField.height - 2 : 0) + hifi.dimensions.textPadding } diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/ConnectionItem.qml b/interface/resources/qml/hifi/commerce/wallet/sendMoney/ConnectionItem.qml index c2d9ef3b19..33cd43bb05 100644 --- a/interface/resources/qml/hifi/commerce/wallet/sendMoney/ConnectionItem.qml +++ b/interface/resources/qml/hifi/commerce/wallet/sendMoney/ConnectionItem.qml @@ -29,13 +29,13 @@ Item { property string userName; property string profilePicUrl; - height: 65; + height: 75; width: parent.width; Rectangle { id: mainContainer; // Style - color: root.isSelected ? hifi.colors.faintGray : hifi.colors.white; + color: root.isSelected ? hifi.colors.faintGray80 : hifi.colors.white; // Size anchors.left: parent.left; anchors.right: parent.right; @@ -49,7 +49,7 @@ Item { anchors.verticalCenter: parent.verticalCenter; anchors.left: parent.left; anchors.leftMargin: 36; - height: root.height - 15; + height: 50; width: visible ? height : 0; clip: true; Image { @@ -83,15 +83,15 @@ Item { RalewaySemiBold { id: userName; anchors.left: avatarImage.right; - anchors.leftMargin: 16; + anchors.leftMargin: 12; anchors.top: parent.top; anchors.bottom: parent.bottom; anchors.right: chooseButton.visible ? chooseButton.left : parent.right; anchors.rightMargin: chooseButton.visible ? 10 : 0; // Text size - size: 20; + size: 18; // Style - color: hifi.colors.baseGray; + color: hifi.colors.blueAccent; text: root.userName; elide: Text.ElideRight; // Alignment @@ -107,9 +107,9 @@ Item { colorScheme: hifi.colorSchemes.dark; anchors.verticalCenter: parent.verticalCenter; anchors.right: parent.right; - anchors.rightMargin: 24; - height: root.height - 20; - width: 110; + anchors.rightMargin: 28; + height: 35; + width: 100; text: "CHOOSE"; onClicked: { var msg = { method: 'chooseConnection', userName: root.userName, profilePicUrl: root.profilePicUrl }; diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml b/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml index 9164ba7a8c..905fb548b2 100644 --- a/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml +++ b/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml @@ -105,7 +105,8 @@ Item { // Send Money Home BEGIN Item { id: sendMoneyHome; - visible: root.currentActiveView === "sendMoneyHome"; + z: 996; + visible: root.currentActiveView === "sendMoneyHome" || root.currentActiveView === "chooseRecipientConnection" || root.currentActiveView === "chooseRecipientNearby"; anchors.fill: parent; anchors.topMargin: root.parentAppTitleBarHeight; anchors.bottomMargin: root.parentAppNavBarHeight; @@ -299,9 +300,18 @@ Item { // Choose Recipient Connection BEGIN Rectangle { id: chooseRecipientConnection; + z: 997; visible: root.currentActiveView === "chooseRecipientConnection"; anchors.fill: parent; - color: "#AAAAAA"; + color: "#80000000"; + + // This object is always used in a popup or full-screen Wallet section. + // This MouseArea is used to prevent a user from being + // able to click on a button/mouseArea underneath the popup/section. + MouseArea { + anchors.fill: parent; + propagateComposedEvents: false; + } ListModel { id: connectionsModel; @@ -314,6 +324,8 @@ Item { anchors.centerIn: parent; width: parent.width - 30; height: parent.height - 30; + color: "#FFFFFF"; + radius: 8; RalewaySemiBold { id: chooseRecipientText_connections; @@ -322,11 +334,11 @@ Item { anchors.top: parent.top; anchors.topMargin: 26; anchors.left: parent.left; - anchors.leftMargin: 20; + anchors.leftMargin: 36; width: paintedWidth; height: 30; // Text size - size: 22; + size: 18; // Style color: hifi.colors.baseGray; } @@ -334,6 +346,7 @@ Item { HiFiGlyphs { id: closeGlyphButton_connections; text: hifi.glyphs.close; + color: hifi.colors.lightGrayText; size: 26; anchors.top: parent.top; anchors.topMargin: 10; @@ -363,7 +376,7 @@ Item { height: 40; // Anchors anchors.left: parent.left; - anchors.leftMargin: 16; + anchors.leftMargin: 36; anchors.right: parent.right; anchors.rightMargin: 16; anchors.top: chooseRecipientText_connections.bottom; @@ -374,8 +387,9 @@ Item { colorScheme: hifi.colorSchemes.faintGray; hasClearButton: true; hasRoundedBorder: true; + roundedBorderRadius: filterBar.height/2; anchors.fill: parent; - placeholderText: "filter recipients"; + centerPlaceholderGlyph: hifi.glyphs.search; onTextChanged: { buildFilteredConnectionsModel(); @@ -461,17 +475,28 @@ Item { // Choose Recipient Nearby BEGIN Rectangle { id: chooseRecipientNearby; + z: 997; + color: "#80000000"; property string selectedRecipient; visible: root.currentActiveView === "chooseRecipientNearby"; anchors.fill: parent; - color: "#AAAAAA"; + + // This object is always used in a popup or full-screen Wallet section. + // This MouseArea is used to prevent a user from being + // able to click on a button/mouseArea underneath the popup/section. + MouseArea { + anchors.fill: parent; + propagateComposedEvents: false; + } Rectangle { anchors.centerIn: parent; width: parent.width - 30; height: parent.height - 30; + color: "#FFFFFF"; + radius: 8; RalewaySemiBold { text: "Choose Recipient:"; @@ -483,7 +508,7 @@ Item { width: paintedWidth; height: 30; // Text size - size: 22; + size: 18; // Style color: hifi.colors.baseGray; } @@ -491,6 +516,7 @@ Item { HiFiGlyphs { id: closeGlyphButton_nearby; text: hifi.glyphs.close; + color: hifi.colors.lightGrayText; size: 26; anchors.top: parent.top; anchors.topMargin: 10; @@ -676,9 +702,9 @@ Item { // Choose Recipient Nearby END // Send Money Screen BEGIN - Rectangle { + Item { id: sendMoneyStep; - z: 997; + z: 996; property string referrer; // either "connections" or "nearby" property string selectedRecipientNodeID; @@ -688,12 +714,12 @@ Item { visible: root.currentActiveView === "sendMoneyStep"; anchors.fill: parent; - color: "#AAAAAA"; Rectangle { anchors.centerIn: parent; width: parent.width - 30; height: parent.height - 30; + color: "#AAAAAA"; RalewaySemiBold { id: sendMoneyText_sendMoneyStep; @@ -806,7 +832,7 @@ Item { anchors.right: parent.right; height: 50; // Style - leftPlaceholderGlyph: hifi.glyphs.hfc; + leftPermanentGlyph: hifi.glyphs.hfc; activeFocusOnPress: true; activeFocusOnTab: true; @@ -1049,6 +1075,7 @@ Item { HiFiGlyphs { id: closeGlyphButton_paymentSuccess; text: hifi.glyphs.close; + color: lightGrayText; size: 26; anchors.top: parent.top; anchors.topMargin: 10; @@ -1234,6 +1261,7 @@ Item { HiFiGlyphs { id: closeGlyphButton_paymentFailure; text: hifi.glyphs.close; + color: hifi.colors.lightGrayText; size: 26; anchors.top: parent.top; anchors.topMargin: 10; diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 0826325a57..691f3cfdc6 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -591,10 +591,16 @@ getConnectionData(false); break; case 'enable_ChooseRecipientNearbyMode': - Script.update.connect(updateOverlays); + if (!isUpdateOverlaysWired) { + Script.update.connect(updateOverlays); + isUpdateOverlaysWired = true; + } break; case 'disable_ChooseRecipientNearbyMode': - Script.update.disconnect(updateOverlays); + if (isUpdateOverlaysWired) { + Script.update.disconnect(updateOverlays); + isUpdateOverlaysWired = false; + } removeOverlays(); break; default: @@ -675,14 +681,18 @@ } } var isWired = false; + var isUpdateOverlaysWired = false; function off() { if (isWired) { // It is not ok to disconnect these twice, hence guard. Users.usernameFromIDReply.disconnect(usernameFromIDReply); - Script.update.disconnect(updateOverlays); Controller.mousePressEvent.disconnect(handleMouseEvent); Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent); isWired = false; } + if (isUpdateOverlaysWired) { + Script.update.disconnect(updateOverlays); + isUpdateOverlaysWired = false; + } triggerMapping.disable(); // It's ok if we disable twice. triggerPressMapping.disable(); // see above removeOverlays(); From 497e8c1d694cb8a4a011dcefa836a89bccc3d11a Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 18 Jan 2018 15:14:26 -0800 Subject: [PATCH 22/52] Fix BUILD_LINUX_CHEATSHEET.md formatting --- BUILD_LINUX_CHEATSHEET.md | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/BUILD_LINUX_CHEATSHEET.md b/BUILD_LINUX_CHEATSHEET.md index 7d77f5d685..9e7534418a 100644 --- a/BUILD_LINUX_CHEATSHEET.md +++ b/BUILD_LINUX_CHEATSHEET.md @@ -1,5 +1,7 @@ - # this guide is specific to Ubuntu 16.04. - # deb packages of High Fidelity domain server and assignment client are stored on debian.highfidelity.com +## This guide is specific to Ubuntu 16.04. +Deb packages of High Fidelity domain server and assignment client are stored on debian.highfidelity.com + +``` sudo su - apt-get -y update apt-get install -y software-properties-common @@ -8,20 +10,27 @@ add-apt-repository "deb http://debian.highfidelity.com stable main" apt-get -y update apt-get install -y hifi-domain-server apt-get install -y hifi-assignment-client +``` - # When installing master/dev builds, the packages are slightly different and you just need to change the last 2 steps to: +When installing master/dev builds, the packages are slightly different and you just need to change the last 2 steps to: +``` apt-get install -y hifi-dev-domain-server apt-get install -y hifi-dev-assignment-client +``` - # domain server and assignment clients should already be running. The processes are controlled via: +Domain server and assignment clients should already be running. The processes are controlled via: +``` systemctl start hifi-domain-server systemctl stop hifi-domain-server +``` - # Once the machine is setup and processes are running you should ensure that your firewall exposes port 40100 on TCP and all UDP ports. This will get your domain up and running and you could connect to it (for now) by using High Fidelity Interface and typing in the IP for the place name. (further customizations can be done via http://IPAddress:40100). - - # The server always depends on both hifi-domain-server and hifi-assignment-client running at the same time. - # As an additional step, you should ensure that your packages are automatically updated when a new version goes out. You could, for example, set the automatic update checks to happen every hour (though this could potentially result in the domain being unreachable for a whole hour by new clients when they are released - adjust the update checks accordingly). +Once the machine is setup and processes are running, you should ensure that your firewall exposes port 40100 on TCP and all UDP ports. This will get your domain up and running and you could connect to it (for now) by using High Fidelity Interface and typing in the IP for the place name. (Further customizations can be done via http://IPAddress:40100). + +The server always depends on both hifi-domain-server and hifi-assignment-client running at the same time. +As an additional step, you should ensure that your packages are automatically updated when a new version goes out. You could, for example, set the automatic update checks to happen every hour (though this could potentially result in the domain being unreachable for a whole hour by new clients when they are released - adjust the update checks accordingly). To do this you can modify /etc/crontab by adding the following lines +``` 0 */1 * * * root apt-get update 1 */1 * * * root apt-get install --only-upgrade -y hifi-domain-server 2 */1 * * * root apt-get install --only-upgrade -y hifi-assignment-client +``` From 6b5389a167166a61687403333e7c3bd651d70025 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 18 Jan 2018 17:37:26 -0800 Subject: [PATCH 23/52] go go go go go --- .../qml/hifi/commerce/wallet/Wallet.qml | 2 +- .../wallet/sendMoney/RecipientDisplay.qml | 6 +- .../commerce/wallet/sendMoney/SendMoney.qml | 539 +++++++++--------- .../wallet/sendMoney/images/connection.svg | 14 + .../wallet/sendMoney/images/loader.gif | Bin 0 -> 59412 bytes .../wallet/sendMoney/images/nearby.svg | 27 + 6 files changed, 320 insertions(+), 268 deletions(-) create mode 100644 interface/resources/qml/hifi/commerce/wallet/sendMoney/images/connection.svg create mode 100644 interface/resources/qml/hifi/commerce/wallet/sendMoney/images/loader.gif create mode 100644 interface/resources/qml/hifi/commerce/wallet/sendMoney/images/nearby.svg diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index da67569bc3..ae42b8e3e1 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -389,7 +389,7 @@ Rectangle { // Item { id: tabButtonsContainer; - visible: !needsLogIn.visible && root.activeView !== "passphraseChange" && root.activeView !== "securityImageChange"; + visible: !needsLogIn.visible && root.activeView !== "passphraseChange" && root.activeView !== "securityImageChange" && sendMoney.currentActiveView !== "sendMoneyStep"; property int numTabs: 5; // Size width: root.width; diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/RecipientDisplay.qml b/interface/resources/qml/hifi/commerce/wallet/sendMoney/RecipientDisplay.qml index 267cf0ed51..1e7494583f 100644 --- a/interface/resources/qml/hifi/commerce/wallet/sendMoney/RecipientDisplay.qml +++ b/interface/resources/qml/hifi/commerce/wallet/sendMoney/RecipientDisplay.qml @@ -46,7 +46,7 @@ Item { // Text size size: 18; // Style - color: hifi.colors.baseGray; + color: hifi.colors.white; verticalAlignment: Text.AlignBottom; elide: Text.ElideRight; } @@ -63,7 +63,7 @@ Item { // Text size size: 16; // Style - color: hifi.colors.baseGray; + color: hifi.colors.white; verticalAlignment: Text.AlignTop; elide: Text.ElideRight; } @@ -108,7 +108,7 @@ Item { // Text size size: 16; // Style - color: hifi.colors.baseGray; + color: hifi.colors.white; verticalAlignment: Text.AlignVCenter; elide: Text.ElideRight; } diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml b/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml index 905fb548b2..2e88b91f5d 100644 --- a/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml +++ b/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml @@ -29,8 +29,9 @@ Item { property int parentAppNavBarHeight; property string currentActiveView: "sendMoneyHome"; property string nextActiveView: ""; - property bool isCurrentlyFullScreen: chooseRecipientConnection.visible || - chooseRecipientNearby.visible || sendMoneyStep.visible || paymentSuccess.visible || paymentFailure.visible; + property bool shouldShowTopAndBottomOfWallet: chooseRecipientConnection.visible || + chooseRecipientNearby.visible || paymentSuccess.visible || paymentFailure.visible; + property bool shouldShowTopOfWallet: sendMoneyStep.visible; property bool isCurrentlySendingMoney: false; // This object is always used in a popup or full-screen Wallet section. @@ -38,9 +39,9 @@ Item { // able to click on a button/mouseArea underneath the popup/section. MouseArea { x: 0; - y: root.isCurrentlyFullScreen ? 0 : root.parentAppTitleBarHeight; + y: (root.shouldShowTopAndBottomOfWallet && !root.shouldShowTopOfWallet) ? 0 : root.parentAppTitleBarHeight; width: parent.width; - height: root.isCurrentlyFullScreen ? parent.height : parent.height - root.parentAppTitleBarHeight - root.parentAppNavBarHeight; + height: (root.shouldShowTopAndBottomOfWallet || root.shouldShowTopOfWallet) ? parent.height : parent.height - root.parentAppTitleBarHeight - root.parentAppNavBarHeight; propagateComposedEvents: false; } @@ -195,6 +196,17 @@ Item { anchors.right: parent.right; anchors.bottom: parent.bottom; height: 440; + + + LinearGradient { + anchors.fill: parent; + start: Qt.point(0, 0); + end: Qt.point(0, height); + gradient: Gradient { + GradientStop { position: 0.0; color: hifi.colors.white } + GradientStop { position: 1.0; color: hifi.colors.faintGray } + } + } RalewaySemiBold { id: sendMoneyText; @@ -224,12 +236,13 @@ Item { Image { anchors.top: parent.top; - source: "../images/wallet-bg.jpg"; - height: 60; + source: "./images/connection.svg"; + height: 70; width: parent.width; fillMode: Image.PreserveAspectFit; horizontalAlignment: Image.AlignHCenter; verticalAlignment: Image.AlignTop; + mipmap: true; } RalewaySemiBold { @@ -265,12 +278,13 @@ Item { Image { anchors.top: parent.top; - source: "../images/wallet-bg.jpg"; - height: 60; + source: "./images/nearby.svg"; + height: 70; width: parent.width; fillMode: Image.PreserveAspectFit; horizontalAlignment: Image.AlignHCenter; verticalAlignment: Image.AlignTop; + mipmap: true; } RalewaySemiBold { @@ -714,284 +728,281 @@ Item { visible: root.currentActiveView === "sendMoneyStep"; anchors.fill: parent; + anchors.topMargin: root.parentAppTitleBarHeight; - Rectangle { - anchors.centerIn: parent; - width: parent.width - 30; - height: parent.height - 30; - color: "#AAAAAA"; + RalewaySemiBold { + id: sendMoneyText_sendMoneyStep; + text: "Send Money"; + // Anchors + anchors.top: parent.top; + anchors.topMargin: 26; + anchors.left: parent.left; + anchors.leftMargin: 20; + width: paintedWidth; + height: 30; + // Text size + size: 22; + // Style + color: hifi.colors.white; + } + + Item { + id: sendToContainer; + anchors.top: sendMoneyText_sendMoneyStep.bottom; + anchors.topMargin: 20; + anchors.left: parent.left; + anchors.leftMargin: 20; + anchors.right: parent.right; + anchors.rightMargin: 20; + height: 80; RalewaySemiBold { - id: sendMoneyText_sendMoneyStep; - text: "Send Money To:"; + id: sendToText_sendMoneyStep; + text: "Send to:"; // Anchors anchors.top: parent.top; - anchors.topMargin: 26; anchors.left: parent.left; - anchors.leftMargin: 20; - width: paintedWidth; - height: 30; + anchors.bottom: parent.bottom; + width: 90; // Text size - size: 22; + size: 18; // Style - color: hifi.colors.baseGray; + color: hifi.colors.white; + verticalAlignment: Text.AlignVCenter; } - Item { - id: sendToContainer; - anchors.top: sendMoneyText_sendMoneyStep.bottom; - anchors.topMargin: 20; - anchors.left: parent.left; - anchors.leftMargin: 20; + RecipientDisplay { + anchors.top: parent.top; + anchors.left: sendToText_sendMoneyStep.right; + anchors.right: changeButton.left; + anchors.rightMargin: 12; + height: parent.height; + + displayName: sendMoneyStep.selectedRecipientDisplayName; + userName: sendMoneyStep.selectedRecipientUserName; + profilePic: sendMoneyStep.selectedRecipientProfilePic !== "" ? ((0 === sendMoneyStep.selectedRecipientProfilePic.indexOf("http")) ? + sendMoneyStep.selectedRecipientProfilePic : (Account.metaverseServerURL + sendMoneyStep.selectedRecipientProfilePic)) : ""; + isDisplayingNearby: sendMoneyStep.referrer === "nearby"; + } + + // "CHANGE" button + HifiControlsUit.Button { + id: changeButton; + color: hifi.buttons.none; + colorScheme: hifi.colorSchemes.white; anchors.right: parent.right; - anchors.rightMargin: 20; - height: 80; - - RalewaySemiBold { - id: sendToText_sendMoneyStep; - text: "Send To:"; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - anchors.bottom: parent.bottom; - width: 90; - // Text size - size: 18; - // Style - color: hifi.colors.baseGray; - verticalAlignment: Text.AlignVCenter; - } - - RecipientDisplay { - anchors.top: parent.top; - anchors.left: sendToText_sendMoneyStep.right; - anchors.right: changeButton.left; - anchors.rightMargin: 12; - height: parent.height; - - displayName: sendMoneyStep.selectedRecipientDisplayName; - userName: sendMoneyStep.selectedRecipientUserName; - profilePic: sendMoneyStep.selectedRecipientProfilePic !== "" ? ((0 === sendMoneyStep.selectedRecipientProfilePic.indexOf("http")) ? - sendMoneyStep.selectedRecipientProfilePic : (Account.metaverseServerURL + sendMoneyStep.selectedRecipientProfilePic)) : ""; - isDisplayingNearby: sendMoneyStep.referrer === "nearby"; - } - - // "CHANGE" button - HifiControlsUit.Button { - id: changeButton; - color: hifi.buttons.black; - colorScheme: hifi.colorSchemes.dark; - anchors.right: parent.right; - anchors.verticalCenter: parent.verticalCenter; - height: 35; - width: 120; - text: "CHANGE"; - onClicked: { - if (sendMoneyStep.referrer === "connections") { - root.nextActiveView = "chooseRecipientConnection"; - } else if (sendMoneyStep.referrer === "nearby") { - root.nextActiveView = "chooseRecipientNearby"; - } - resetSendMoneyData(); + anchors.verticalCenter: parent.verticalCenter; + height: 35; + width: 100; + text: "CHANGE"; + onClicked: { + if (sendMoneyStep.referrer === "connections") { + root.nextActiveView = "chooseRecipientConnection"; + } else if (sendMoneyStep.referrer === "nearby") { + root.nextActiveView = "chooseRecipientNearby"; } + resetSendMoneyData(); + } + } + } + + Item { + id: amountContainer; + anchors.top: sendToContainer.bottom; + anchors.topMargin: 2; + anchors.left: parent.left; + anchors.leftMargin: 20; + anchors.right: parent.right; + anchors.rightMargin: 20; + height: 80; + + RalewaySemiBold { + id: amountText; + text: "Amount:"; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.bottom: parent.bottom; + width: 90; + // Text size + size: 18; + // Style + color: hifi.colors.white; + verticalAlignment: Text.AlignVCenter; + } + + HifiControlsUit.TextField { + id: amountTextField; + colorScheme: hifi.colorSchemes.dark; + inputMethodHints: Qt.ImhDigitsOnly; + // Anchors + anchors.verticalCenter: parent.verticalCenter; + anchors.left: amountText.right; + anchors.right: parent.right; + height: 50; + // Style + leftPermanentGlyph: hifi.glyphs.hfc; + activeFocusOnPress: true; + activeFocusOnTab: true; + + validator: IntValidator { bottom: 0; } + + onAccepted: { + optionalMessage.focus = true; } } - Item { - id: amountContainer; - anchors.top: sendToContainer.bottom; + RalewaySemiBold { + id: amountTextFieldError; + // Anchors + anchors.top: amountTextField.bottom; anchors.topMargin: 2; - anchors.left: parent.left; - anchors.leftMargin: 20; - anchors.right: parent.right; - anchors.rightMargin: 20; - height: 80; - - RalewaySemiBold { - id: amountText; - text: "Amount:"; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - anchors.bottom: parent.bottom; - width: 90; - // Text size - size: 18; - // Style - color: hifi.colors.baseGray; - verticalAlignment: Text.AlignVCenter; - } - - HifiControlsUit.TextField { - id: amountTextField; - colorScheme: hifi.colorSchemes.light; - inputMethodHints: Qt.ImhDigitsOnly; - // Anchors - anchors.verticalCenter: parent.verticalCenter; - anchors.left: amountText.right; - anchors.right: parent.right; - height: 50; - // Style - leftPermanentGlyph: hifi.glyphs.hfc; - activeFocusOnPress: true; - activeFocusOnTab: true; - - validator: IntValidator { bottom: 0; } - - onAccepted: { - optionalMessage.focus = true; - } - } - - RalewaySemiBold { - id: amountTextFieldError; - // Anchors - anchors.top: amountTextField.bottom; - anchors.topMargin: 2; - anchors.left: amountTextField.left; - anchors.right: amountTextField.right; - height: 40; - // Text size - size: 16; - // Style - color: hifi.colors.baseGray; - verticalAlignment: Text.AlignTop; - horizontalAlignment: Text.AlignRight; - } + anchors.left: amountTextField.left; + anchors.right: amountTextField.right; + height: 40; + // Text size + size: 16; + // Style + color: hifi.colors.white; + verticalAlignment: Text.AlignTop; + horizontalAlignment: Text.AlignRight; } + } - Item { - id: messageContainer; - anchors.top: amountContainer.bottom; - anchors.topMargin: 16; - anchors.left: parent.left; - anchors.leftMargin: 20; - anchors.right: parent.right; - anchors.rightMargin: 20; - height: 140; + Item { + id: messageContainer; + anchors.top: amountContainer.bottom; + anchors.topMargin: 16; + anchors.left: parent.left; + anchors.leftMargin: 20; + anchors.right: parent.right; + anchors.rightMargin: 20; + height: 140; - FontLoader { id: firaSansSemiBold; source: "../../../../../fonts/FiraSans-SemiBold.ttf"; } - TextArea { - id: optionalMessage; - property int maximumLength: 70; - property string previousText: text; - placeholderText: "Optional Message"; - font.family: firaSansSemiBold.name; - font.pixelSize: 20; - // Anchors + FontLoader { id: firaSansSemiBold; source: "../../../../../fonts/FiraSans-SemiBold.ttf"; } + TextArea { + id: optionalMessage; + property int maximumLength: 72; + property string previousText: text; + placeholderText: "Optional Message (" + maximumLength + " character limit)"; + font.family: firaSansSemiBold.name; + font.pixelSize: 20; + // Anchors + anchors.fill: parent; + // Style + background: Rectangle { anchors.fill: parent; - // Style - background: Rectangle { - anchors.fill: parent; - color: optionalMessage.activeFocus ? hifi.colors.white : hifi.colors.textFieldLightBackground; - border.width: optionalMessage.activeFocus ? 1 : 0; - border.color: optionalMessage.activeFocus ? hifi.colors.primaryHighlight : hifi.colors.textFieldLightBackground; - } - color: hifi.colors.black; - textFormat: TextEdit.PlainText; - wrapMode: TextEdit.Wrap; - activeFocusOnPress: true; - activeFocusOnTab: true; - // Workaround for no max length on TextAreas - onTextChanged: { - if (text.length > maximumLength) { - var cursor = cursorPosition; - text = previousText; - if (cursor > text.length) { - cursorPosition = text.length; - } else { - cursorPosition = cursor-1; - } - } - previousText = text; - } + color: optionalMessage.activeFocus ? hifi.colors.black : hifi.colors.baseGrayShadow; + border.width: optionalMessage.activeFocus ? 1 : 0; + border.color: optionalMessage.activeFocus ? hifi.colors.primaryHighlight : hifi.colors.textFieldLightBackground; } - RalewaySemiBold { - id: optionalMessageCharacterCount; - text: optionalMessage.text.length + "/" + optionalMessage.maximumLength; - // Anchors - anchors.top: optionalMessage.bottom; - anchors.topMargin: 2; - anchors.right: optionalMessage.right; - height: paintedHeight; - // Text size - size: 16; - // Style - color: hifi.colors.baseGray; - verticalAlignment: Text.AlignTop; - horizontalAlignment: Text.AlignRight; + color: hifi.colors.white; + textFormat: TextEdit.PlainText; + wrapMode: TextEdit.Wrap; + activeFocusOnPress: true; + activeFocusOnTab: true; + // Workaround for no max length on TextAreas + onTextChanged: { + if (text.length > maximumLength) { + var cursor = cursorPosition; + text = previousText; + if (cursor > text.length) { + cursorPosition = text.length; + } else { + cursorPosition = cursor-1; + } + } + previousText = text; + } + } + RalewaySemiBold { + id: optionalMessageCharacterCount; + text: optionalMessage.text.length + "/" + optionalMessage.maximumLength; + // Anchors + anchors.top: optionalMessage.bottom; + anchors.topMargin: 4; + anchors.right: optionalMessage.right; + height: paintedHeight; + // Text size + size: 16; + // Style + color: optionalMessage.text.length === optionalMessage.maximumLength ? "#ea89a5" : hifi.colors.lightGrayText; + verticalAlignment: Text.AlignTop; + horizontalAlignment: Text.AlignRight; + } + } + + HifiControlsUit.CheckBox { + id: sendPubliclyCheckbox; + visible: true; // FIXME ONCE PARTICLE EFFECTS ARE IN + text: "Send Publicly" + // Anchors + anchors.top: messageContainer.bottom; + anchors.topMargin: 16; + anchors.left: parent.left; + anchors.leftMargin: 20; + anchors.right: parent.right; + anchors.rightMargin: 16; + boxSize: 24; + } + + Item { + id: bottomBarContainer; + anchors.left: parent.left; + anchors.leftMargin: 20; + anchors.right: parent.right; + anchors.rightMargin: 20; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 20; + height: 60; + + // "CANCEL" button + HifiControlsUit.Button { + id: cancelButton_sendMoneyStep; + color: hifi.buttons.noneBorderlessWhite; + colorScheme: hifi.colorSchemes.dark; + anchors.left: parent.left; + anchors.leftMargin: 24; + anchors.verticalCenter: parent.verticalCenter; + height: 40; + width: 150; + text: "CANCEL"; + onClicked: { + resetSendMoneyData(); + root.nextActiveView = "sendMoneyHome"; } } - Item { - id: bottomBarContainer; - anchors.top: messageContainer.bottom; - anchors.topMargin: 30; - anchors.left: parent.left; - anchors.leftMargin: 20; + // "SEND" button + HifiControlsUit.Button { + id: sendButton; + color: hifi.buttons.blue; + colorScheme: hifi.colorSchemes.dark; anchors.right: parent.right; - anchors.rightMargin: 20; - height: 80; - - HifiControlsUit.CheckBox { - id: sendPubliclyCheckbox; - visible: false; // FIXME ONCE PARTICLE EFFECTS ARE IN - text: "Send Publicly" - // Anchors - anchors.verticalCenter: parent.verticalCenter; - anchors.left: parent.left; - anchors.right: cancelButton_sendMoneyStep.left; - anchors.rightMargin: 16; - boxSize: 24; - } - - // "CANCEL" button - HifiControlsUit.Button { - id: cancelButton_sendMoneyStep; - color: hifi.buttons.noneBorderless; - colorScheme: hifi.colorSchemes.dark; - anchors.right: sendButton.left; - anchors.rightMargin: 16; - anchors.verticalCenter: parent.verticalCenter; - height: 35; - width: 100; - text: "CANCEL"; - onClicked: { - resetSendMoneyData(); - root.nextActiveView = "sendMoneyHome"; - } - } - - // "SEND" button - HifiControlsUit.Button { - id: sendButton; - color: hifi.buttons.blue; - colorScheme: hifi.colorSchemes.dark; - anchors.right: parent.right; - anchors.verticalCenter: parent.verticalCenter; - height: 35; - width: 100; - text: "SEND"; - onClicked: { - if (parseInt(amountTextField.text) > parseInt(balanceText.text)) { - amountTextField.focus = true; - amountTextField.error = true; - amountTextFieldError.text = "amount exceeds available funds"; - } else if (amountTextField.text === "" || parseInt(amountTextField.text) < 1) { - amountTextField.focus = true; - amountTextField.error = true; - amountTextFieldError.text = "invalid amount"; - } else { - amountTextFieldError.text = ""; - amountTextField.error = false; - root.isCurrentlySendingMoney = true; - amountTextField.focus = false; - optionalMessage.focus = false; - if (sendMoneyStep.referrer === "connections") { - Commerce.transferHfcToUsername(sendMoneyStep.selectedRecipientUserName, parseInt(amountTextField.text), optionalMessage.text); - } else if (sendMoneyStep.referrer === "nearby") { - Commerce.transferHfcToNode(sendMoneyStep.selectedRecipientNodeID, parseInt(amountTextField.text), optionalMessage.text); - } + anchors.rightMargin: 24; + anchors.verticalCenter: parent.verticalCenter; + height: 40; + width: 150; + text: "SUBMIT"; + onClicked: { + if (parseInt(amountTextField.text) > parseInt(balanceText.text)) { + amountTextField.focus = true; + amountTextField.error = true; + amountTextFieldError.text = "amount exceeds available funds"; + } else if (amountTextField.text === "" || parseInt(amountTextField.text) < 1) { + amountTextField.focus = true; + amountTextField.error = true; + amountTextFieldError.text = "invalid amount"; + } else { + amountTextFieldError.text = ""; + amountTextField.error = false; + root.isCurrentlySendingMoney = true; + amountTextField.focus = false; + optionalMessage.focus = false; + if (sendMoneyStep.referrer === "connections") { + Commerce.transferHfcToUsername(sendMoneyStep.selectedRecipientUserName, parseInt(amountTextField.text), optionalMessage.text); + } else if (sendMoneyStep.referrer === "nearby") { + Commerce.transferHfcToNode(sendMoneyStep.selectedRecipientNodeID, parseInt(amountTextField.text), optionalMessage.text); } } } @@ -1157,7 +1168,7 @@ Item { // Text size size: 18; // Style - color: hifi.colors.baseGray; + color: hifi.colors.white; verticalAlignment: Text.AlignVCenter; } @@ -1172,7 +1183,7 @@ Item { anchors.verticalCenter: parent.verticalCenter; height: 50; // Style - color: hifi.colors.baseGray; + color: hifi.colors.lightGrayText; } RalewaySemiBold { @@ -1186,7 +1197,7 @@ Item { height: 50; // Style size: 22; - color: hifi.colors.baseGray; + color: hifi.colors.darkGray; } } diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/connection.svg b/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/connection.svg new file mode 100644 index 0000000000..7c5403fda3 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/connection.svg @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/loader.gif b/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/loader.gif new file mode 100644 index 0000000000000000000000000000000000000000..0536bd1884f1cb4a814b729803b1996c8347d182 GIT binary patch literal 59412 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�)#8TsS*rVAU%J0tpt=B2=IG*djp3b$!Y|O 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 L8@}NiK8F7Q4CAa& literal 0 HcmV?d00001 diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/nearby.svg b/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/nearby.svg new file mode 100644 index 0000000000..dec87e658d --- /dev/null +++ b/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/nearby.svg @@ -0,0 +1,27 @@ + + + + + + + + + + From 3d3bfcf7a315a682972d741125007260c461af60 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 16 Jan 2018 12:35:03 -0800 Subject: [PATCH 24/52] check dirty flags when harvesting physics results --- libraries/entities/src/EntityItem.cpp | 47 +++++++++++++++++++++ libraries/entities/src/EntityItem.h | 8 ++++ libraries/physics/src/EntityMotionState.cpp | 33 ++++----------- libraries/shared/src/GLMHelpers.h | 1 + libraries/shared/src/SpatiallyNestable.cpp | 30 +++++++++++++ libraries/shared/src/SpatiallyNestable.h | 1 + 6 files changed, 95 insertions(+), 25 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index fe5213baa8..8ea1de7666 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1610,6 +1610,37 @@ void EntityItem::setPosition(const glm::vec3& value) { } } +void EntityItem::setWorldTransformAndVelocitiesUnlessDirtyFlags( + const glm::vec3& position, + const glm::quat& orientation, + const glm::vec3& linearVelocity, + const glm::vec3& angularVelocity) { + // only ever call this for harvesting results of physics simulation + // if a dirty bit is set then an update arrived (via script or network) overriding the physics simulation + uint32_t flags = _dirtyFlags & (Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES); + if (!flags) { + // flags are clear + setWorldTransform(position, orientation); + setWorldVelocity(linearVelocity); + setWorldAngularVelocity(angularVelocity); + setLastSimulated(usecTimestampNow()); + } else { + // only set properties NOT flagged + if (!(flags & Simulation::DIRTY_TRANSFORM)) { + setWorldTransform(position, orientation); + } + if (!(flags & Simulation::DIRTY_LINEAR_VELOCITY)) { + setWorldVelocity(linearVelocity); + } + if (!(flags & Simulation::DIRTY_ANGULAR_VELOCITY)) { + setWorldAngularVelocity(angularVelocity); + } + if (flags != (Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES)) { + setLastSimulated(usecTimestampNow()); + } + } +} + void EntityItem::setParentID(const QUuid& value) { QUuid oldParentID = getParentID(); if (oldParentID != value) { @@ -1739,6 +1770,22 @@ void EntityItem::setVelocity(const glm::vec3& value) { } } +void EntityItem::zeroAllVelocitiesUnlessDirtyFlags() { + uint32_t flags = _dirtyFlags & (Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES); + if (!flags) { + setWorldVelocity(glm::vec3(0.0f)); + setWorldAngularVelocity(glm::vec3(0.0f)); + } else { + if (!(flags & Simulation::DIRTY_LINEAR_VELOCITY)) { + setWorldVelocity(glm::vec3(0.0f)); + } + if (!(flags & Simulation::DIRTY_ANGULAR_VELOCITY)) { + setWorldAngularVelocity(glm::vec3(0.0f)); + } + } + _acceleration = glm::vec3(0.0f); +} + void EntityItem::setDamping(float value) { auto clampedDamping = glm::clamp(value, 0.0f, 1.0f); withWriteLock([&] { diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index ecfb7b5dcd..db2e7f7641 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -355,6 +355,7 @@ public: void setRotation(glm::quat orientation); void setVelocity(const glm::vec3& velocity); + void zeroAllVelocitiesUnlessDirtyFlags(); uint32_t getDirtyFlags() const; void markDirtyFlags(uint32_t mask); @@ -368,6 +369,13 @@ public: void* getPhysicsInfo() const { return _physicsInfo; } void setPhysicsInfo(void* data) { _physicsInfo = data; } + + void setWorldTransformAndVelocitiesUnlessDirtyFlags( + const glm::vec3& position, + const glm::quat& orientation, + const glm::vec3& linearVelocity, + const glm::vec3& angularVelocity); + EntityTreeElementPointer getElement() const { return _element; } EntityTreePointer getTree() const; virtual SpatialParentTree* getParentTree() const override; diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 33ac887f4f..fe3e242a70 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -256,25 +256,12 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { assert(_entity); assert(entityTreeIsLocked()); measureBodyAcceleration(); - bool positionSuccess; - _entity->setWorldPosition(bulletToGLM(worldTrans.getOrigin()) + ObjectMotionState::getWorldOffset(), positionSuccess, false); - if (!positionSuccess) { - static QString repeatedMessage = - LogHandler::getInstance().addRepeatedMessageRegex("EntityMotionState::setWorldTransform " - "setPosition failed.*"); - qCDebug(physics) << "EntityMotionState::setWorldTransform setPosition failed" << _entity->getID(); - } - bool orientationSuccess; - _entity->setWorldOrientation(bulletToGLM(worldTrans.getRotation()), orientationSuccess, false); - if (!orientationSuccess) { - static QString repeatedMessage = - LogHandler::getInstance().addRepeatedMessageRegex("EntityMotionState::setWorldTransform " - "setOrientation failed.*"); - qCDebug(physics) << "EntityMotionState::setWorldTransform setOrientation failed" << _entity->getID(); - } - _entity->setVelocity(getBodyLinearVelocity()); - _entity->setAngularVelocity(getBodyAngularVelocity()); - _entity->setLastSimulated(usecTimestampNow()); + + _entity->setWorldTransformAndVelocitiesUnlessDirtyFlags( + bulletToGLM(worldTrans.getOrigin()), + bulletToGLM(worldTrans.getRotation()), + getBodyLinearVelocity(), + getBodyAngularVelocity()); if (_entity->getSimulatorID().isNull()) { _loopsWithoutOwner++; @@ -530,9 +517,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ if (!_body->isActive()) { // make sure all derivatives are zero - _entity->setVelocity(Vectors::ZERO); - _entity->setAngularVelocity(Vectors::ZERO); - _entity->setAcceleration(Vectors::ZERO); + _entity->zeroAllVelocitiesUnlessDirtyFlags(); _numInactiveUpdates++; } else { glm::vec3 gravity = _entity->getGravity(); @@ -559,9 +544,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ if (movingSlowly) { // velocities might not be zero, but we'll fake them as such, which will hopefully help convince // other simulating observers to deactivate their own copies - glm::vec3 zero(0.0f); - _entity->setVelocity(zero); - _entity->setAngularVelocity(zero); + _entity->zeroAllVelocitiesUnlessDirtyFlags(); } } _numInactiveUpdates = 0; diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index 973998b927..4f761a4aac 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -253,6 +253,7 @@ glm::vec2 getFacingDir2D(const glm::mat4& m); inline bool isNaN(const glm::vec3& value) { return isNaN(value.x) || isNaN(value.y) || isNaN(value.z); } inline bool isNaN(const glm::quat& value) { return isNaN(value.w) || isNaN(value.x) || isNaN(value.y) || isNaN(value.z); } +inline bool isNaN(const glm::mat3& value) { return isNaN(value * glm::vec3(1.0f)); } glm::mat4 orthoInverse(const glm::mat4& m); diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index 20a5a76b73..324cee3417 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -464,6 +464,36 @@ glm::vec3 SpatiallyNestable::localToWorldDimensions(const glm::vec3& dimensions, return dimensions; } +void SpatiallyNestable::setWorldTransform(const glm::vec3& position, const glm::quat& orientation) { + // guard against introducing NaN into the transform + if (isNaN(orientation) || isNaN(position)) { + return; + } + + bool changed = false; + bool success = true; + Transform parentTransform = getParentTransform(success); + _transformLock.withWriteLock([&] { + Transform myWorldTransform; + Transform::mult(myWorldTransform, parentTransform, _transform); + if (myWorldTransform.getRotation() != orientation) { + changed = true; + myWorldTransform.setRotation(orientation); + } + if (myWorldTransform.getTranslation() != position) { + changed = true; + myWorldTransform.setTranslation(position); + } + if (changed) { + Transform::inverseMult(_transform, parentTransform, myWorldTransform); + _translationChanged = usecTimestampNow(); + } + }); + if (success && changed) { + locationChanged(false); + } +} + glm::vec3 SpatiallyNestable::getWorldPosition(bool& success) const { return getTransform(success).getTranslation(); } diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index 2a315e9230..090ca4c266 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -87,6 +87,7 @@ public: virtual Transform getParentTransform(bool& success, int depth = 0) const; + void setWorldTransform(const glm::vec3& position, const glm::quat& orientation); virtual glm::vec3 getWorldPosition(bool& success) const; virtual glm::vec3 getWorldPosition() const; virtual void setWorldPosition(const glm::vec3& position, bool& success, bool tellPhysics = true); From 75b5635d2fbf9c0fad8ee4a041061deeefc284d3 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 17 Jan 2018 13:51:07 -0800 Subject: [PATCH 25/52] less word salad --- libraries/entities/src/EntityItem.cpp | 47 ------------------ libraries/entities/src/EntityItem.h | 7 --- libraries/physics/src/EntityMotionState.cpp | 53 ++++++++++++++++++--- libraries/physics/src/EntityMotionState.h | 1 + 4 files changed, 47 insertions(+), 61 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 8ea1de7666..fe5213baa8 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1610,37 +1610,6 @@ void EntityItem::setPosition(const glm::vec3& value) { } } -void EntityItem::setWorldTransformAndVelocitiesUnlessDirtyFlags( - const glm::vec3& position, - const glm::quat& orientation, - const glm::vec3& linearVelocity, - const glm::vec3& angularVelocity) { - // only ever call this for harvesting results of physics simulation - // if a dirty bit is set then an update arrived (via script or network) overriding the physics simulation - uint32_t flags = _dirtyFlags & (Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES); - if (!flags) { - // flags are clear - setWorldTransform(position, orientation); - setWorldVelocity(linearVelocity); - setWorldAngularVelocity(angularVelocity); - setLastSimulated(usecTimestampNow()); - } else { - // only set properties NOT flagged - if (!(flags & Simulation::DIRTY_TRANSFORM)) { - setWorldTransform(position, orientation); - } - if (!(flags & Simulation::DIRTY_LINEAR_VELOCITY)) { - setWorldVelocity(linearVelocity); - } - if (!(flags & Simulation::DIRTY_ANGULAR_VELOCITY)) { - setWorldAngularVelocity(angularVelocity); - } - if (flags != (Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES)) { - setLastSimulated(usecTimestampNow()); - } - } -} - void EntityItem::setParentID(const QUuid& value) { QUuid oldParentID = getParentID(); if (oldParentID != value) { @@ -1770,22 +1739,6 @@ void EntityItem::setVelocity(const glm::vec3& value) { } } -void EntityItem::zeroAllVelocitiesUnlessDirtyFlags() { - uint32_t flags = _dirtyFlags & (Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES); - if (!flags) { - setWorldVelocity(glm::vec3(0.0f)); - setWorldAngularVelocity(glm::vec3(0.0f)); - } else { - if (!(flags & Simulation::DIRTY_LINEAR_VELOCITY)) { - setWorldVelocity(glm::vec3(0.0f)); - } - if (!(flags & Simulation::DIRTY_ANGULAR_VELOCITY)) { - setWorldAngularVelocity(glm::vec3(0.0f)); - } - } - _acceleration = glm::vec3(0.0f); -} - void EntityItem::setDamping(float value) { auto clampedDamping = glm::clamp(value, 0.0f, 1.0f); withWriteLock([&] { diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index db2e7f7641..f9559a375b 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -355,7 +355,6 @@ public: void setRotation(glm::quat orientation); void setVelocity(const glm::vec3& velocity); - void zeroAllVelocitiesUnlessDirtyFlags(); uint32_t getDirtyFlags() const; void markDirtyFlags(uint32_t mask); @@ -370,12 +369,6 @@ public: void setPhysicsInfo(void* data) { _physicsInfo = data; } - void setWorldTransformAndVelocitiesUnlessDirtyFlags( - const glm::vec3& position, - const glm::quat& orientation, - const glm::vec3& linearVelocity, - const glm::vec3& angularVelocity); - EntityTreeElementPointer getElement() const { return _element; } EntityTreePointer getTree() const; virtual SpatialParentTree* getParentTree() const override; diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index fe3e242a70..420da5a1e0 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -257,11 +257,31 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { assert(entityTreeIsLocked()); measureBodyAcceleration(); - _entity->setWorldTransformAndVelocitiesUnlessDirtyFlags( - bulletToGLM(worldTrans.getOrigin()), - bulletToGLM(worldTrans.getRotation()), - getBodyLinearVelocity(), - getBodyAngularVelocity()); + // If transform or velocities are flagged as dirty it means a network or scripted change + // occured between the beginning and end of the stepSimulation() and we DON'T want to apply + // these physics simulation results. + uint32_t flags = _entity->getDirtyFlags() & (Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES); + if (!flags) { + // flags are clear + _entity->setWorldTransform(bulletToGLM(worldTrans.getOrigin()), bulletToGLM(worldTrans.getRotation())); + _entity->setWorldVelocity(getBodyLinearVelocity()); + _entity->setWorldAngularVelocity(getBodyAngularVelocity()); + _entity->setLastSimulated(usecTimestampNow()); + } else { + // only set properties NOT flagged + if (!(flags & Simulation::DIRTY_TRANSFORM)) { + _entity->setWorldTransform(bulletToGLM(worldTrans.getOrigin()), bulletToGLM(worldTrans.getRotation())); + } + if (!(flags & Simulation::DIRTY_LINEAR_VELOCITY)) { + _entity->setWorldVelocity(getBodyLinearVelocity()); + } + if (!(flags & Simulation::DIRTY_ANGULAR_VELOCITY)) { + _entity->setWorldAngularVelocity(getBodyAngularVelocity()); + } + if (flags != (Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES)) { + _entity->setLastSimulated(usecTimestampNow()); + } + } if (_entity->getSimulatorID().isNull()) { _loopsWithoutOwner++; @@ -517,7 +537,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ if (!_body->isActive()) { // make sure all derivatives are zero - _entity->zeroAllVelocitiesUnlessDirtyFlags(); + zeroCleanObjectVelocities(); _numInactiveUpdates++; } else { glm::vec3 gravity = _entity->getGravity(); @@ -544,7 +564,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ if (movingSlowly) { // velocities might not be zero, but we'll fake them as such, which will hopefully help convince // other simulating observers to deactivate their own copies - _entity->zeroAllVelocitiesUnlessDirtyFlags(); + zeroCleanObjectVelocities(); } } _numInactiveUpdates = 0; @@ -801,3 +821,22 @@ bool EntityMotionState::shouldBeLocallyOwned() const { void EntityMotionState::upgradeOutgoingPriority(uint8_t priority) { _outgoingPriority = glm::max(_outgoingPriority, priority); } + +void EntityMotionState::zeroCleanObjectVelocities() const { + // If transform or velocities are flagged as dirty it means a network or scripted change + // occured between the beginning and end of the stepSimulation() and we DON'T want to apply + // these physics simulation results. + uint32_t flags = _entity->getDirtyFlags() & (Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES); + if (!flags) { + _entity->setWorldVelocity(glm::vec3(0.0f)); + _entity->setWorldAngularVelocity(glm::vec3(0.0f)); + } else { + if (!(flags & Simulation::DIRTY_LINEAR_VELOCITY)) { + _entity->setWorldVelocity(glm::vec3(0.0f)); + } + if (!(flags & Simulation::DIRTY_ANGULAR_VELOCITY)) { + _entity->setWorldAngularVelocity(glm::vec3(0.0f)); + } + } + _entity->setAcceleration(glm::vec3(0.0f)); +} diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index ddfd7e1e4c..784273d600 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -87,6 +87,7 @@ public: protected: // changes _outgoingPriority only if priority is larger void upgradeOutgoingPriority(uint8_t priority); + void zeroCleanObjectVelocities() const; #ifdef WANT_DEBUG_ENTITY_TREE_LOCKS bool entityTreeIsLocked() const; From 495c4f56e2f791452cb46a2d7b1ea08c6182579d Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 18 Jan 2018 17:47:43 -0800 Subject: [PATCH 26/52] Only small tweaks remaining? --- .../wallet/sendMoney/RecipientDisplay.qml | 7 ++-- .../commerce/wallet/sendMoney/SendMoney.qml | 34 ++++++++++--------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/RecipientDisplay.qml b/interface/resources/qml/hifi/commerce/wallet/sendMoney/RecipientDisplay.qml index 1e7494583f..43636d47ca 100644 --- a/interface/resources/qml/hifi/commerce/wallet/sendMoney/RecipientDisplay.qml +++ b/interface/resources/qml/hifi/commerce/wallet/sendMoney/RecipientDisplay.qml @@ -29,6 +29,7 @@ Item { property string displayName; property string userName; property string profilePic; + property string textColor: hifi.colors.white; Item { visible: root.isDisplayingNearby; @@ -46,7 +47,7 @@ Item { // Text size size: 18; // Style - color: hifi.colors.white; + color: root.textColor; verticalAlignment: Text.AlignBottom; elide: Text.ElideRight; } @@ -63,7 +64,7 @@ Item { // Text size size: 16; // Style - color: hifi.colors.white; + color: root.textColor; verticalAlignment: Text.AlignTop; elide: Text.ElideRight; } @@ -108,7 +109,7 @@ Item { // Text size size: 16; // Style - color: hifi.colors.white; + color: root.textColor; verticalAlignment: Text.AlignVCenter; elide: Text.ElideRight; } diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml b/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml index 2e88b91f5d..ed959333d7 100644 --- a/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml +++ b/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml @@ -262,6 +262,7 @@ Item { anchors.fill: parent; onClicked: { root.nextActiveView = "chooseRecipientConnection"; + filterBar.text = ""; } } } @@ -934,7 +935,7 @@ Item { HifiControlsUit.CheckBox { id: sendPubliclyCheckbox; - visible: true; // FIXME ONCE PARTICLE EFFECTS ARE IN + visible: false; // FIXME ONCE PARTICLE EFFECTS ARE IN text: "Send Publicly" // Anchors anchors.top: messageContainer.bottom; @@ -1018,7 +1019,7 @@ Item { visible: root.isCurrentlySendingMoney; anchors.fill: parent; - color: Qt.rgba(0.0, 0.0, 0.0, 0.5); + color: Qt.rgba(0.0, 0.0, 0.0, 0.8); // This object is always used in a popup or full-screen Wallet section. // This MouseArea is used to prevent a user from being @@ -1030,11 +1031,10 @@ Item { AnimatedImage { id: sendingMoneyImage; - source: "../../../../../icons/profilePicLoading.gif" - width: 160; + source: "./images/loader.gif" + width: 96; height: width; - anchors.top: parent.top; - anchors.topMargin: 185; + anchors.verticalCenter: parent.verticalCenter; anchors.horizontalCenter: parent.horizontalCenter; } @@ -1042,11 +1042,11 @@ Item { text: "Sending"; // Anchors anchors.top: sendingMoneyImage.bottom; - anchors.topMargin: 22; + anchors.topMargin: 4; anchors.horizontalCenter: parent.horizontalCenter; width: paintedWidth; // Text size - size: 24; + size: 26; // Style color: hifi.colors.white; verticalAlignment: Text.AlignVCenter; @@ -1055,17 +1055,17 @@ Item { // Sending Money Overlay END // Payment Success BEGIN - Rectangle { + Item { id: paymentSuccess; visible: root.currentActiveView === "paymentSuccess"; anchors.fill: parent; - color: "#AAAAAA"; Rectangle { anchors.centerIn: parent; width: parent.width - 30; height: parent.height - 30; + color: "#FFFFFF"; RalewaySemiBold { id: paymentSentText; @@ -1138,6 +1138,7 @@ Item { anchors.left: sendToText_paymentSuccess.right; anchors.right: parent.right; height: parent.height; + textColor: hifi.colors.blueAccent; displayName: sendMoneyStep.selectedRecipientDisplayName; userName: sendMoneyStep.selectedRecipientUserName; @@ -1168,7 +1169,7 @@ Item { // Text size size: 18; // Style - color: hifi.colors.white; + color: hifi.colors.baseGray; verticalAlignment: Text.AlignVCenter; } @@ -1183,7 +1184,7 @@ Item { anchors.verticalCenter: parent.verticalCenter; height: 50; // Style - color: hifi.colors.lightGrayText; + color: hifi.colors.blueAccent; } RalewaySemiBold { @@ -1197,7 +1198,7 @@ Item { height: 50; // Style size: 22; - color: hifi.colors.darkGray; + color: hifi.colors.blueAccent; } } @@ -1215,7 +1216,7 @@ Item { // Text size size: 22; // Style - color: hifi.colors.baseGray; + color: hifi.colors.blueAccent; wrapMode: Text.Wrap; verticalAlignment: Text.AlignTop; } @@ -1241,17 +1242,17 @@ Item { // Payment Success END // Payment Failure BEGIN - Rectangle { + Item { id: paymentFailure; visible: root.currentActiveView === "paymentFailure"; anchors.fill: parent; - color: "#AAAAAA"; Rectangle { anchors.centerIn: parent; width: parent.width - 30; height: parent.height - 30; + color: "#FFFFFF"; RalewaySemiBold { id: paymentFailureText; @@ -1342,6 +1343,7 @@ Item { anchors.left: sentToText_paymentFailure.right; anchors.right: parent.right; height: parent.height; + textColor: hifi.colors.baseGray; displayName: sendMoneyStep.selectedRecipientDisplayName; userName: sendMoneyStep.selectedRecipientUserName; From 67e816756cab36d3253e99e50ec9a2ee826f2ef7 Mon Sep 17 00:00:00 2001 From: Alexander Ivash Date: Fri, 19 Jan 2018 01:51:32 +0000 Subject: [PATCH 27/52] make gcc happy --- libraries/ui/src/DesktopPreviewProvider.cpp | 4 +++- libraries/ui/src/DesktopPreviewProvider.h | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/libraries/ui/src/DesktopPreviewProvider.cpp b/libraries/ui/src/DesktopPreviewProvider.cpp index 4ae70a1eb0..31aa7a22e2 100644 --- a/libraries/ui/src/DesktopPreviewProvider.cpp +++ b/libraries/ui/src/DesktopPreviewProvider.cpp @@ -7,6 +7,8 @@ DesktopPreviewProvider::DesktopPreviewProvider() { } +constexpr const char* DesktopPreviewProvider::imagePaths[]; + QSharedPointer DesktopPreviewProvider::getInstance() { static QSharedPointer instance = DependencyManager::get(); return instance; @@ -41,4 +43,4 @@ void DesktopPreviewProvider::setPreviewDisabledReason(const QString& reasonStrin QImage& DesktopPreviewProvider::loadPreviewImage(QImage& image, const QString& path) const { return image = QImage(path).mirrored().convertToFormat(QImage::Format_RGBA8888); -} \ No newline at end of file +} diff --git a/libraries/ui/src/DesktopPreviewProvider.h b/libraries/ui/src/DesktopPreviewProvider.h index 07d39a6cdf..449c3723e1 100644 --- a/libraries/ui/src/DesktopPreviewProvider.h +++ b/libraries/ui/src/DesktopPreviewProvider.h @@ -16,7 +16,7 @@ class DesktopPreviewProvider : public QObject, public Dependency { DesktopPreviewProvider(); DesktopPreviewProvider(const DesktopPreviewProvider& other) = delete; - constexpr static char* imagePaths[] = { + constexpr static const char* imagePaths[] = { "images/preview-disabled.png", // USER "images/preview-privacy.png", // SECURE_SCREEN "images/preview.png", // VSYNC @@ -44,4 +44,4 @@ private: PreviewDisabledReasons m_previewDisabledReason = { USER }; mutable QImage m_previewDisabled[3]; -}; \ No newline at end of file +}; From e0e6fc711bba14648fd54711cb5e408cf5a67816 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 19 Jan 2018 16:05:09 +1300 Subject: [PATCH 28/52] Replace Pointers API function setNonHoverItems() with setDoesHover() --- .../src/raypick/PointerScriptingInterface.cpp | 10 +-- .../src/raypick/PointerScriptingInterface.h | 16 ++-- libraries/pointers/src/Pointer.cpp | 21 +++-- libraries/pointers/src/Pointer.h | 7 +- libraries/pointers/src/PointerManager.cpp | 14 ++-- libraries/pointers/src/PointerManager.h | 2 +- .../controllers/controllerDispatcher.js | 76 +++++++++---------- .../controllerModules/webSurfaceLaserInput.js | 14 ++-- 8 files changed, 77 insertions(+), 83 deletions(-) diff --git a/interface/src/raypick/PointerScriptingInterface.cpp b/interface/src/raypick/PointerScriptingInterface.cpp index 8e50b1d629..a334834979 100644 --- a/interface/src/raypick/PointerScriptingInterface.cpp +++ b/interface/src/raypick/PointerScriptingInterface.cpp @@ -24,10 +24,6 @@ void PointerScriptingInterface::setIncludeItems(unsigned int uid, const QScriptV DependencyManager::get()->setIncludeItems(uid, qVectorQUuidFromScriptValue(includeItems)); } -void PointerScriptingInterface::setNonHoverItems(unsigned int uid, const QScriptValue& nonHoverItems) const { - DependencyManager::get()->setNonHoverItems(uid, qVectorQUuidFromScriptValue(nonHoverItems)); -} - unsigned int PointerScriptingInterface::createPointer(const PickQuery::PickType& type, const QVariant& properties) { // Interaction with managers should always happen on the main thread if (QThread::currentThread() != qApp->thread()) { @@ -179,4 +175,8 @@ QVariantMap PointerScriptingInterface::getPrevPickResult(unsigned int uid) const result = pickResult->toVariantMap(); } return result; -} \ No newline at end of file +} + +void PointerScriptingInterface::setDoesHover(unsigned int uid, bool hover) const { + DependencyManager::get()->setDoesHover(uid, hover); +} diff --git a/interface/src/raypick/PointerScriptingInterface.h b/interface/src/raypick/PointerScriptingInterface.h index 4791fd802e..451c132769 100644 --- a/interface/src/raypick/PointerScriptingInterface.h +++ b/interface/src/raypick/PointerScriptingInterface.h @@ -191,14 +191,6 @@ public: */ Q_INVOKABLE void setIncludeItems(unsigned int uid, const QScriptValue& includeEntities) const; - /**jsdoc - * Sets a list of Entity IDs, Overlay IDs, and/or Avatar IDs that the pointer should not send hover events to. - * @function Pointers.setNonHoverItems - * @param {number} uid - The ID of the Pointer, as returned by {@link Pointers.createPointer}. - * @param {Uuid[]} nonHoverItems - A list of IDs to that hover events should not be sent to. - */ - Q_INVOKABLE void setNonHoverItems(unsigned int uid, const QScriptValue& nonHoverItems) const; - /**jsdoc * Lock a Pointer onto a specific object (overlay, entity, or avatar). Optionally, provide an offset in object-space, otherwise the Pointer will lock on to the center of the object. * Not used by Stylus Pointers. @@ -210,6 +202,14 @@ public: */ Q_INVOKABLE void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) const { DependencyManager::get()->setLockEndUUID(uid, objectID, isOverlay, offsetMat); } + /**jsdoc + * Sets whether or not a pointer should generate hover events. + * @function Pointers.setDoesHover + * @param {boolean} uid - The ID of the Pointer, as returned by {@link Pointers.createPointer}. + * @param {boolean} hover - If true then the pointer generates hover events, otherwise it does not. + */ + Q_INVOKABLE void setDoesHover(unsigned int uid, bool hove) const; + /**jsdoc * Check if a Pointer is associated with the left hand. * @function Pointers.isLeftHand diff --git a/libraries/pointers/src/Pointer.cpp b/libraries/pointers/src/Pointer.cpp index ead3c22687..0e542c47da 100644 --- a/libraries/pointers/src/Pointer.cpp +++ b/libraries/pointers/src/Pointer.cpp @@ -52,12 +52,6 @@ void Pointer::setIncludeItems(const QVector& includeItems) const { DependencyManager::get()->setIncludeItems(_pickUID, includeItems); } -void Pointer::setNonHoverItems(const QVector& nonHoverItems) { - withWriteLock([&] { - _nonHoverItems = nonHoverItems; - }); -} - bool Pointer::isLeftHand() const { return DependencyManager::get()->isLeftHand(_pickUID); } @@ -70,6 +64,12 @@ bool Pointer::isMouse() const { return DependencyManager::get()->isMouse(_pickUID); } +void Pointer::setDoesHover(bool doesHover) { + withWriteLock([&] { + _hover = doesHover; + }); +} + void Pointer::update(unsigned int pointerID) { // This only needs to be a read lock because update won't change any of the properties that can be modified from scripts withReadLock([&] { @@ -101,11 +101,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin } // Hover events - bool doHover = shouldHover(pickResult); - - auto pickResultMap = pickResult->toVariantMap(); - auto uuid = QUuid(pickResultMap.value("objectID", "").toString()); - doHover = doHover && !_nonHoverItems.contains(uuid); + bool doHover = _hover && shouldHover(pickResult); Pointer::PickedObject hoveredObject = getHoveredObject(pickResult); PointerEvent hoveredEvent = buildPointerEvent(hoveredObject, pickResult); @@ -240,7 +236,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin } // if we disable the pointer or disable hovering, send hoverEnd events after triggerEnd - if (_hover && ((!_enabled && _prevEnabled) || (!doHover && _prevDoHover))) { + if ((_hover || _prevHover) && ((!_enabled && _prevEnabled) || (!doHover && _prevDoHover))) { if (_prevHoveredObject.type == ENTITY) { emit pointerManager->hoverEndEntity(_prevHoveredObject.objectID, hoveredEvent); } else if (_prevHoveredObject.type == OVERLAY) { @@ -253,6 +249,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin _prevHoveredObject = hoveredObject; _prevButtons = buttons; _prevEnabled = _enabled; + _prevHover = _hover; _prevDoHover = doHover; } diff --git a/libraries/pointers/src/Pointer.h b/libraries/pointers/src/Pointer.h index 6cb366e92a..ef0048cf1e 100644 --- a/libraries/pointers/src/Pointer.h +++ b/libraries/pointers/src/Pointer.h @@ -54,8 +54,6 @@ public: virtual void setIgnoreItems(const QVector& ignoreItems) const; virtual void setIncludeItems(const QVector& includeItems) const; - void setNonHoverItems(const QVector& nonHoverItems); - bool isLeftHand() const; bool isRightHand() const; bool isMouse() const; @@ -64,6 +62,8 @@ public: virtual void setLength(float length) {} virtual void setLockEndUUID(const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) {} + void setDoesHover(bool hover); + void update(unsigned int pointerID); virtual void updateVisuals(const PickResultPointer& pickResult) = 0; void generatePointerEvents(unsigned int pointerID, const PickResultPointer& pickResult); @@ -99,12 +99,11 @@ private: PickedObject _prevHoveredObject; Buttons _prevButtons; bool _prevEnabled { false }; + bool _prevHover { false }; bool _prevDoHover { false }; std::unordered_map _triggeredObjects; PointerEvent::Button chooseButton(const std::string& button); - - QVector _nonHoverItems; }; #endif // hifi_Pick_h diff --git a/libraries/pointers/src/PointerManager.cpp b/libraries/pointers/src/PointerManager.cpp index f2953ce8c8..13b38457b6 100644 --- a/libraries/pointers/src/PointerManager.cpp +++ b/libraries/pointers/src/PointerManager.cpp @@ -108,13 +108,6 @@ void PointerManager::setIncludeItems(unsigned int uid, const QVector& inc } } -void PointerManager::setNonHoverItems(unsigned int uid, const QVector& nonHoverItems) const { - auto pointer = find(uid); - if (pointer) { - pointer->setNonHoverItems(nonHoverItems); - } -} - void PointerManager::setLength(unsigned int uid, float length) const { auto pointer = find(uid); if (pointer) { @@ -129,6 +122,13 @@ void PointerManager::setLockEndUUID(unsigned int uid, const QUuid& objectID, boo } } +void PointerManager::setDoesHover(unsigned int uid, bool hover) const { + auto pointer = find(uid); + if (pointer) { + pointer->setDoesHover(hover); + } +} + bool PointerManager::isLeftHand(unsigned int uid) { auto pointer = find(uid); if (pointer) { diff --git a/libraries/pointers/src/PointerManager.h b/libraries/pointers/src/PointerManager.h index a2a1e9dcd2..2c9a37e129 100644 --- a/libraries/pointers/src/PointerManager.h +++ b/libraries/pointers/src/PointerManager.h @@ -34,10 +34,10 @@ public: void setPrecisionPicking(unsigned int uid, bool precisionPicking) const; void setIgnoreItems(unsigned int uid, const QVector& ignoreEntities) const; void setIncludeItems(unsigned int uid, const QVector& includeEntities) const; - void setNonHoverItems(unsigned int uid, const QVector& nonHoverItems) const; void setLength(unsigned int uid, float length) const; void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) const; + void setDoesHover(unsigned int uid, bool hover) const; void update(); diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 15b025a7ea..fe18ed25f8 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -46,10 +46,10 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); this.tabletID = null; this.TABLET_UI_UUIDS = []; this.blacklist = []; - this.leftPointerNonHoverItem = null; - this.leftPointerNonHoverItemChanged = false; - this.rightPointerNonHoverItem = null; - this.rightPointerNonHoverItemChanged = false; + this.leftPointerDoesHover = true; + this.leftPointerDoesHoverChanged = false; + this.rightPointerDoesHover = true; + this.rightPointerDoesHoverChanged = false; this.pointerManager = new PointerManager(); // a module can occupy one or more "activity" slots while it's running. If all the required slots for a module are @@ -163,26 +163,14 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); } }; - this.setNonHoverItems = function () { - if (_this.leftPointerNonHoverItemChanged) { - if (_this.leftPointerNonHoverItem === null) { - Pointers.setNonHoverItems(_this.leftPointer, []); - } else if (_this.isTabletID(_this.leftPointerNonHoverItem)) { - Pointers.setNonHoverItems(_this.leftPointer, _this.TABLET_UI_UUIDS); - } else { - Pointers.setNonHoverItems(_this.leftPointer, [_this.leftPointerNonHoverItem]); - } - _this.leftPointerNonHoverItemChanged = false; + this.updateHovering = function () { + if (_this.leftPointerDoesHoverChanged) { + Pointers.setDoesHover(_this.leftPointer, _this.leftPointerDoesHover); + _this.leftPointerDoesHoverChanged = false; } - if (_this.rightPointerNonHoverItemChanged) { - if (_this.rightPointerNonHoverItem === null) { - Pointers.setNonHoverItems(_this.rightPointer, []); - } else if (_this.isTabletID(_this.rightPointerNonHoverItem)) { - Pointers.setNonHoverItems(_this.rightPointer, _this.TABLET_UI_UUIDS); - } else { - Pointers.setNonHoverItems(_this.rightPointer, [_this.rightPointerNonHoverItem]); - } - _this.rightPointerNonHoverItemChanged = false; + if (_this.rightPointerDoesHoverChanged) { + Pointers.setDoesHover(_this.rightPointer, _this.rightPointerDoesHover); + _this.rightPointerDoesHoverChanged = false; } }; @@ -358,15 +346,15 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); _this.markSlots(candidatePlugin, orderedPluginName); _this.pointerManager.makePointerVisible(candidatePlugin.parameters.handLaser); - if (candidatePlugin.parameters.handLaser.nonHoverItem !== undefined) { + if (candidatePlugin.parameters.handLaser.doesHover !== undefined) { if (candidatePlugin.parameters.handLaser.hand === LEFT_HAND - && _this.leftPointerNonHoverItem !== candidatePlugin.parameters.handLaser.nonHoverItem) { - _this.leftPointerNonHoverItem = candidatePlugin.parameters.handLaser.nonHoverItem; - _this.leftPointerNonHoverItemChanged = true; + && _this.leftPointerDoesHover !== candidatePlugin.parameters.handLaser.doesHover) { + _this.leftPointerDoesHover = candidatePlugin.parameters.handLaser.doesHover; + _this.leftPointerDoesHoverChanged = true; } else if (candidatePlugin.parameters.handLaser.hand === RIGHT_HAND - && _this.rightPointerNonHoverItem !== candidatePlugin.parameters.handLaser.nonHoverItem) { - _this.rightPointerNonHoverItem = candidatePlugin.parameters.handLaser.nonHoverItem; - _this.rightPointerNonHoverItemChanged = true; + && _this.rightPointerDoesHover !== candidatePlugin.parameters.handLaser.doesHover) { + _this.rightPointerDoesHover = candidatePlugin.parameters.handLaser.doesHover; + _this.rightPointerDoesHoverChanged = true; } } @@ -402,15 +390,15 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); var runningness = plugin.run(controllerData, deltaTime); if (runningness.active) { - if (plugin.parameters.handLaser.nonHoverItem !== undefined) { + if (plugin.parameters.handLaser.doesHover !== undefined) { if (plugin.parameters.handLaser.hand === LEFT_HAND - && _this.leftPointerNonHoverItem !== plugin.parameters.handLaser.nonHoverItem) { - _this.leftPointerNonHoverItem = plugin.parameters.handLaser.nonHoverItem; - _this.leftPointerNonHoverItemChanged = true; + && _this.leftPointerDoesHover !== plugin.parameters.handLaser.doesHover) { + _this.leftPointerDoesHover = plugin.parameters.handLaser.doesHover; + _this.leftPointerDoesHoverChanged = true; } else if (plugin.parameters.handLaser.hand === RIGHT_HAND - && _this.rightPointerNonHoverItem !== plugin.parameters.handLaser.nonHoverItem) { - _this.rightPointerNonHoverItem = plugin.parameters.handLaser.nonHoverItem; - _this.rightPointerNonHoverItemChanged = true; + && _this.rightPointerDoesHover !== plugin.parameters.handLaser.doesHover) { + _this.rightPointerDoesHover = plugin.parameters.handLaser.doesHover; + _this.rightPointerDoesHoverChanged = true; } } } @@ -421,6 +409,17 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); delete _this.runningPluginNames[runningPluginName]; _this.markSlots(plugin, false); _this.pointerManager.makePointerInvisible(plugin.parameters.handLaser); + + if (plugin.parameters.handLaser.doesHover !== undefined) { + if (plugin.parameters.handLaser.hand === LEFT_HAND && !_this.leftPointerDoesHover) { + _this.leftPointerDoesHover = true; + _this.leftPointerDoesHoverChanged = true; + } else if (plugin.parameters.handLaser.hand === RIGHT_HAND && !_this.rightPointerDoesHover) { + _this.rightPointerDoesHover = true; + _this.rightPointerDoesHoverChanged = true; + } + } + if (DEBUG) { print("controllerDispatcher stopping " + runningPluginName); } @@ -433,8 +432,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); } } _this.pointerManager.updatePointersRenderState(controllerData.triggerClicks, controllerData.triggerValues); - - _this.setNonHoverItems(); + _this.updateHovering(); if (PROFILE) { Script.endProfileRange("dispatch.run"); diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index 5b9afa3a2a..282dd36d17 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -90,7 +90,7 @@ Script.include("/~/system/libraries/controllers.js"); this.hoverItem = null; this.isTabletID = function (uuid) { - return [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID, HMD.homeButtonHightlightID].indexOf(uuid) !== -1; + return [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID, HMD.homeButtonHighlightID].indexOf(uuid) !== -1; }; this.isReady = function(controllerData) { @@ -105,19 +105,19 @@ Script.include("/~/system/libraries/controllers.js"); } if (pointingAt !== this.getOtherModule().hoverItem) { + this.parameters.handLaser.doesHover = true; this.hoverItem = pointingAt; - this.getOtherModule().parameters.handLaser.nonHoverItem = pointingAt; } else { + this.parameters.handLaser.doesHover = false; this.hoverItem = null; - this.getOtherModule().parameters.handLaser.nonHoverItem = null; } return makeRunningValues(true, [], []); } } + this.parameters.handLaser.doesHover = false; this.hoverItem = null; - this.getOtherModule().parameters.handLaser.nonHoverItem = null; return makeRunningValues(false, [], []); }; @@ -135,11 +135,11 @@ Script.include("/~/system/libraries/controllers.js"); } if (pointingAt !== this.getOtherModule().hoverItem || isTriggerPressed) { + this.parameters.handLaser.doesHover = true; this.hoverItem = pointingAt; - this.getOtherModule().parameters.handLaser.nonHoverItem = pointingAt; } else { + this.parameters.handLaser.doesHover = false; this.hoverItem = null; - this.getOtherModule().parameters.handLaser.nonHoverItem = null; } return makeRunningValues(true, [], []); @@ -147,8 +147,8 @@ Script.include("/~/system/libraries/controllers.js"); this.deleteContextOverlay(); this.running = false; + this.parameters.handLaser.doesHover = false; this.hoverItem = null; - this.getOtherModule().parameters.handLaser.nonHoverItem = null; return makeRunningValues(false, [], []); }; From 45bc6b8dabf371d25b6841901be446dae7d6c57f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 19 Jan 2018 16:41:23 +1300 Subject: [PATCH 29/52] Dominant hand gets the highlight if both start pointing simultaneously --- .../controllerModules/webSurfaceLaserInput.js | 40 ++++++++++++++----- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index 282dd36d17..cc8378af84 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -87,6 +87,23 @@ Script.include("/~/system/libraries/controllers.js"); return MyAvatar.getDominantHand() === "right" ? 1 : 0; }; + this.letOtherHandRunFirst = function (controllerData, pointingAt) { + // If both hands are ready to run, let the other hand run first if it is the dominant hand so that it gets the + // highlight. + var isOtherTriggerPressed = controllerData.triggerValues[this.otherHand] > TRIGGER_OFF_VALUE; + var isLetOtherHandRunFirst = !this.getOtherModule().running + && this.getDominantHand() === this.otherHand + && (this.parameters.handLaser.allwaysOn || isOtherTriggerPressed); + if (isLetOtherHandRunFirst) { + var otherHandPointingAt = controllerData.rayPicks[this.otherHand].objectID; + if (this.isTabletID(otherHandPointingAt)) { + otherHandPointingAt = HMD.tabletID; + } + isLetOtherHandRunFirst = pointingAt === otherHandPointingAt; + } + return isLetOtherHandRunFirst; + }; + this.hoverItem = null; this.isTabletID = function (uuid) { @@ -94,25 +111,28 @@ Script.include("/~/system/libraries/controllers.js"); }; this.isReady = function(controllerData) { - var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE - && controllerData.triggerValues[this.otherHand] <= TRIGGER_OFF_VALUE; if (this.isPointingAtOverlay(controllerData) || this.isPointingAtWebEntity(controllerData)) { this.updateAllwaysOn(); + + var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE; if (this.parameters.handLaser.allwaysOn || isTriggerPressed) { var pointingAt = controllerData.rayPicks[this.hand].objectID; if (this.isTabletID(pointingAt)) { pointingAt = HMD.tabletID; } - if (pointingAt !== this.getOtherModule().hoverItem) { - this.parameters.handLaser.doesHover = true; - this.hoverItem = pointingAt; - } else { - this.parameters.handLaser.doesHover = false; - this.hoverItem = null; - } + if (!this.letOtherHandRunFirst(controllerData, pointingAt)) { - return makeRunningValues(true, [], []); + if (pointingAt !== this.getOtherModule().hoverItem) { + this.parameters.handLaser.doesHover = true; + this.hoverItem = pointingAt; + } else { + this.parameters.handLaser.doesHover = false; + this.hoverItem = null; + } + + return makeRunningValues(true, [], []); + } } } From 3004ee08e1c311eaebf56a5bcbf3364079b80d1d Mon Sep 17 00:00:00 2001 From: Bruce Brown <1st-BrainStormer@users.noreply.github.com> Date: Thu, 18 Jan 2018 21:18:36 -0800 Subject: [PATCH 30/52] Fix lockup when no midi devices present. --- libraries/midi/src/Midi.cpp | 818 ++++++++++++++++++------------------ 1 file changed, 409 insertions(+), 409 deletions(-) diff --git a/libraries/midi/src/Midi.cpp b/libraries/midi/src/Midi.cpp index 69c35c4a20..680e53a8c7 100644 --- a/libraries/midi/src/Midi.cpp +++ b/libraries/midi/src/Midi.cpp @@ -1,409 +1,409 @@ -// -// Midi.cpp -// libraries/midi/src -// -// Created by Burt Sloane -// Modified by Bruce Brown -// 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 -// - -#include "Midi.h" - - -#include - -#if defined Q_OS_WIN32 -#include "Windows.h" -#endif - -#if defined Q_OS_WIN32 -const int MIDI_BYTE_MASK = 0x0FF; -const int MIDI_NIBBLE_MASK = 0x00F; -const int MIDI_PITCH_BEND_MASK = 0x3F80; -const int MIDI_SHIFT_STATUS = 4; -const int MIDI_SHIFT_NOTE = 8; -const int MIDI_SHIFT_VELOCITY = 16; -const int MIDI_SHIFT_PITCH_BEND = 9; -// Status Decode -const int MIDI_NOTE_OFF = 0x8; -const int MIDI_NOTE_ON = 0x9; -const int MIDI_POLYPHONIC_KEY_PRESSURE = 0xa; -const int MIDI_PROGRAM_CHANGE = 0xc; -const int MIDI_CHANNEL_PRESSURE = 0xd; -const int MIDI_PITCH_BEND_CHANGE = 0xe; -const int MIDI_SYSTEM_MESSAGE = 0xf; -#endif - -const int MIDI_CONTROL_CHANGE = 0xb; -const int MIDI_CHANNEL_MODE_ALL_NOTES_OFF = 0x07b; - -static Midi* instance = NULL; // communicate this to non-class callbacks -static bool thruModeEnabled = false; -static bool broadcastEnabled = false; -static bool typeNoteOffEnabled = true; -static bool typeNoteOnEnabled = true; -static bool typePolyKeyPressureEnabled = false; -static bool typeControlChangeEnabled = true; -static bool typeProgramChangeEnabled = true; -static bool typeChanPressureEnabled = false; -static bool typePitchBendEnabled = true; -static bool typeSystemMessageEnabled = false; - -std::vector Midi::midiInExclude; -std::vector Midi::midiOutExclude; - -#if defined Q_OS_WIN32 - -#pragma comment(lib, "Winmm.lib") - -// -std::vector midihin; -std::vector midihout; - -void CALLBACK MidiInProc(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { - switch (wMsg) { - case MIM_OPEN: - // message not used - break; - case MIM_CLOSE: - for (int i = 0; i < midihin.size(); i++) { - if (midihin[i] == hMidiIn) { - midihin[i] = NULL; - instance->allNotesOff(); - instance->midiHardwareChange(); - } - } - break; - case MIM_DATA: { - int device = -1; - for (int i = 0; i < midihin.size(); i++) { - if (midihin[i] == hMidiIn) { - device = i; - } - } - int raw = dwParam1; - int channel = (MIDI_NIBBLE_MASK & dwParam1) + 1; - int status = MIDI_BYTE_MASK & dwParam1; - int type = MIDI_NIBBLE_MASK & (dwParam1 >> MIDI_SHIFT_STATUS); - int note = MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_NOTE); - int velocity = MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_VELOCITY); - int bend = 0; - int program = 0; - if (!typeNoteOffEnabled && type == MIDI_NOTE_OFF) { - return; - } - if (!typeNoteOnEnabled && type == MIDI_NOTE_ON) { - return; - } - if (!typePolyKeyPressureEnabled && type == MIDI_POLYPHONIC_KEY_PRESSURE) { - return; - } - if (!typeControlChangeEnabled && type == MIDI_CONTROL_CHANGE) { - return; - } - if (typeProgramChangeEnabled && type == MIDI_PROGRAM_CHANGE) { - program = note; - note = 0; - } - if (typeChanPressureEnabled && type == MIDI_CHANNEL_PRESSURE) { - velocity = note; - note = 0; - } - if (typePitchBendEnabled && type == MIDI_PITCH_BEND_CHANGE) { - bend = ((MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_NOTE)) | - (MIDI_PITCH_BEND_MASK & (dwParam1 >> MIDI_SHIFT_PITCH_BEND))) - 8192; - channel = 0; // Weird values on different instruments - note = 0; - velocity = 0; - } - if (!typeSystemMessageEnabled && type == MIDI_SYSTEM_MESSAGE) { - return; - } - if (thruModeEnabled) { - instance->sendNote(status, note, velocity); // relay the message on to all other midi devices. - } - instance->midiReceived(device, raw, channel, status, type, note, velocity, bend, program); // notify the javascript - break; - } - } -} - -void CALLBACK MidiOutProc(HMIDIOUT hmo, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { - switch (wMsg) { - case MOM_OPEN: - // message not used - break; - case MOM_CLOSE: - for (int i = 0; i < midihout.size(); i++) { - if (midihout[i] == hmo) { - midihout[i] = NULL; - instance->allNotesOff(); - instance->midiHardwareChange(); - } - } - break; - } -} - -void Midi::sendRawMessage(int device, int raw) { - if (broadcastEnabled) { - for (int i = 0; i < midihout.size(); i++) { - if (midihout[i] != NULL) { - midiOutShortMsg(midihout[i], raw); - } - } - } else { - midiOutShortMsg(midihout[device], raw); - } -} - -void Midi::sendMessage(int device, int channel, int type, int note, int velocity) { - int message = (channel - 1) | (type << MIDI_SHIFT_STATUS); - if (broadcastEnabled) { - for (int i = 0; i < midihout.size(); i++) { - if (midihout[i] != NULL) { - midiOutShortMsg(midihout[i], message | (note << MIDI_SHIFT_NOTE) | (velocity << MIDI_SHIFT_VELOCITY)); - } - } - } else { - midiOutShortMsg(midihout[device], message | (note << MIDI_SHIFT_NOTE) | (velocity << MIDI_SHIFT_VELOCITY)); - } -} - -void Midi::sendNote(int status, int note, int velocity) { - for (int i = 0; i < midihout.size(); i++) { - if (midihout[i] != NULL) { - midiOutShortMsg(midihout[i], status + (note << MIDI_SHIFT_NOTE) + (velocity << MIDI_SHIFT_VELOCITY)); - } - } -} - -void Midi::MidiSetup() { - midihin.clear(); - midihout.clear(); - - MIDIINCAPS incaps; - for (unsigned int i = 0; i < midiInGetNumDevs(); i++) { - midiInGetDevCaps(i, &incaps, sizeof(MIDIINCAPS)); - - bool found = false; - for (int j = 0; j < midiInExclude.size(); j++) { - if (midiInExclude[j].toStdString().compare(incaps.szPname) == 0) { - found = true; - break; - } - } - if (!found) { // EXCLUDE AN INPUT BY NAME - HMIDIIN tmphin; - midiInOpen(&tmphin, i, (DWORD_PTR)MidiInProc, NULL, CALLBACK_FUNCTION); - midiInStart(tmphin); - midihin.push_back(tmphin); - } - } - - MIDIOUTCAPS outcaps; - for (unsigned int i = 0; i < midiOutGetNumDevs(); i++) { - midiOutGetDevCaps(i, &outcaps, sizeof(MIDIINCAPS)); - - bool found = false; - for (int j = 0; j < midiOutExclude.size(); j++) { - if (midiOutExclude[j].toStdString().compare(outcaps.szPname) == 0) { - found = true; - break; - } - } - if (!found) { // EXCLUDE AN OUTPUT BY NAME - HMIDIOUT tmphout; - midiOutOpen(&tmphout, i, (DWORD_PTR)MidiOutProc, NULL, CALLBACK_FUNCTION); - midihout.push_back(tmphout); - } - } - - allNotesOff(); -} - -void Midi::MidiCleanup() { - allNotesOff(); - - for (int i = 0; i < midihin.size(); i++) { - if (midihin[i] != NULL) { - midiInStop(midihin[i]); - midiInClose(midihin[i]); - } - } - for (int i = 0; i < midihout.size(); i++) { - if (midihout[i] != NULL) { - midiOutClose(midihout[i]); - } - } - midihin.clear(); - midihout.clear(); -} -#else -void Midi::sendRawMessage(int device, int raw) { -} - -void Midi::sendNote(int status, int note, int velocity) { -} - -void Midi::sendMessage(int device, int channel, int type, int note, int velocity){ -} - -void Midi::MidiSetup() { - allNotesOff(); -} - -void Midi::MidiCleanup() { - allNotesOff(); -} -#endif - -void Midi::midiReceived(int device, int raw, int channel, int status, int type, int note, int velocity, int bend, int program) { - QVariantMap eventData; - eventData["device"] = device; - eventData["raw"] = raw; - eventData["channel"] = channel; - eventData["status"] = status; - eventData["type"] = type; - eventData["note"] = note; - eventData["velocity"] = velocity; - eventData["bend"] = bend; - eventData["program"] = program; - emit midiNote(eventData);// Legacy - emit midiMessage(eventData); -} - -void Midi::midiHardwareChange() { - emit midiReset(); -} -// - -Midi::Midi() { - instance = this; -#if defined Q_OS_WIN32 - midiOutExclude.push_back("Microsoft GS Wavetable Synth"); // we don't want to hear this thing (Lags) -#endif - MidiSetup(); -} - -Midi::~Midi() { -} - -void Midi::sendRawDword(int device, int raw) { - sendRawMessage(device, raw); -} - -void Midi::playMidiNote(int status, int note, int velocity) { - sendNote(status, note, velocity); -} - -void Midi::sendMidiMessage(int device, int channel, int type, int note, int velocity) { - sendMessage(device, channel, type, note, velocity); -} - -void Midi::allNotesOff() { - sendNote(MIDI_CONTROL_CHANGE, MIDI_CHANNEL_MODE_ALL_NOTES_OFF, 0); // all notes off -} - -void Midi::resetDevices() { - MidiCleanup(); - MidiSetup(); -} - -void Midi::USBchanged() { - instance->MidiCleanup(); - instance->MidiSetup(); - instance->midiHardwareChange(); -} - -// - -QStringList Midi::listMidiDevices(bool output) { - QStringList rv; -#if defined Q_OS_WIN32 - if (output) { - MIDIOUTCAPS outcaps; - for (unsigned int i = 0; i < midiOutGetNumDevs(); i++) { - midiOutGetDevCaps(i, &outcaps, sizeof(MIDIINCAPS)); - rv.append(outcaps.szPname); - } - } else { - MIDIINCAPS incaps; - for (unsigned int i = 0; i < midiInGetNumDevs(); i++) { - midiInGetDevCaps(i, &incaps, sizeof(MIDIINCAPS)); - rv.append(incaps.szPname); - } - } -#endif - return rv; -} - -void Midi::unblockMidiDevice(QString name, bool output) { - if (output) { - for (unsigned long i = 0; i < midiOutExclude.size(); i++) { - if (midiOutExclude[i].toStdString().compare(name.toStdString()) == 0) { - midiOutExclude.erase(midiOutExclude.begin() + i); - break; - } - } - } else { - for (unsigned long i = 0; i < midiInExclude.size(); i++) { - if (midiInExclude[i].toStdString().compare(name.toStdString()) == 0) { - midiInExclude.erase(midiInExclude.begin() + i); - break; - } - } - } -} - -void Midi::blockMidiDevice(QString name, bool output) { - unblockMidiDevice(name, output); // make sure it's only in there once - if (output) { - midiOutExclude.push_back(name); - } else { - midiInExclude.push_back(name); - } -} - -void Midi::thruModeEnable(bool enable) { - thruModeEnabled = enable; -} - -void Midi::broadcastEnable(bool enable) { - broadcastEnabled = enable; -} - -void Midi::typeNoteOffEnable(bool enable) { - typeNoteOffEnabled = enable; -} - -void Midi::typeNoteOnEnable(bool enable) { - typeNoteOnEnabled = enable; -} - -void Midi::typePolyKeyPressureEnable(bool enable) { - typePolyKeyPressureEnabled = enable; -} - -void Midi::typeControlChangeEnable(bool enable) { - typeControlChangeEnabled = enable; -} - -void Midi::typeProgramChangeEnable(bool enable) { - typeProgramChangeEnabled = enable; -} - -void Midi::typeChanPressureEnable(bool enable) { - typeChanPressureEnabled = enable; -} - -void Midi::typePitchBendEnable(bool enable) { - typePitchBendEnabled = enable; -} - -void Midi::typeSystemMessageEnable(bool enable) { - typeSystemMessageEnabled = enable; -} +// +// Midi.cpp +// libraries/midi/src +// +// Created by Burt Sloane +// Modified by Bruce Brown +// 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 +// + +#include "Midi.h" + + +#include + +#if defined Q_OS_WIN32 +#include "Windows.h" +#endif + +#if defined Q_OS_WIN32 +const int MIDI_BYTE_MASK = 0x0FF; +const int MIDI_NIBBLE_MASK = 0x00F; +const int MIDI_PITCH_BEND_MASK = 0x3F80; +const int MIDI_SHIFT_STATUS = 4; +const int MIDI_SHIFT_NOTE = 8; +const int MIDI_SHIFT_VELOCITY = 16; +const int MIDI_SHIFT_PITCH_BEND = 9; +// Status Decode +const int MIDI_NOTE_OFF = 0x8; +const int MIDI_NOTE_ON = 0x9; +const int MIDI_POLYPHONIC_KEY_PRESSURE = 0xa; +const int MIDI_PROGRAM_CHANGE = 0xc; +const int MIDI_CHANNEL_PRESSURE = 0xd; +const int MIDI_PITCH_BEND_CHANGE = 0xe; +const int MIDI_SYSTEM_MESSAGE = 0xf; +#endif + +const int MIDI_CONTROL_CHANGE = 0xb; +const int MIDI_CHANNEL_MODE_ALL_NOTES_OFF = 0x07b; + +static Midi* instance = NULL; // communicate this to non-class callbacks +static bool thruModeEnabled = false; +static bool broadcastEnabled = false; +static bool typeNoteOffEnabled = true; +static bool typeNoteOnEnabled = true; +static bool typePolyKeyPressureEnabled = false; +static bool typeControlChangeEnabled = true; +static bool typeProgramChangeEnabled = true; +static bool typeChanPressureEnabled = false; +static bool typePitchBendEnabled = true; +static bool typeSystemMessageEnabled = false; + +std::vector Midi::midiInExclude; +std::vector Midi::midiOutExclude; + +#if defined Q_OS_WIN32 + +#pragma comment(lib, "Winmm.lib") + +// +std::vector midihin; +std::vector midihout; + +void CALLBACK MidiInProc(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { + switch (wMsg) { + case MIM_OPEN: + // message not used + break; + case MIM_CLOSE: + for (int i = 0; i < midihin.size(); i++) { + if (midihin[i] == hMidiIn) { + midihin[i] = NULL; + instance->allNotesOff(); + instance->midiHardwareChange(); + } + } + break; + case MIM_DATA: { + int device = -1; + for (int i = 0; i < midihin.size(); i++) { + if (midihin[i] == hMidiIn) { + device = i; + } + } + int raw = dwParam1; + int channel = (MIDI_NIBBLE_MASK & dwParam1) + 1; + int status = MIDI_BYTE_MASK & dwParam1; + int type = MIDI_NIBBLE_MASK & (dwParam1 >> MIDI_SHIFT_STATUS); + int note = MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_NOTE); + int velocity = MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_VELOCITY); + int bend = 0; + int program = 0; + if (!typeNoteOffEnabled && type == MIDI_NOTE_OFF) { + return; + } + if (!typeNoteOnEnabled && type == MIDI_NOTE_ON) { + return; + } + if (!typePolyKeyPressureEnabled && type == MIDI_POLYPHONIC_KEY_PRESSURE) { + return; + } + if (!typeControlChangeEnabled && type == MIDI_CONTROL_CHANGE) { + return; + } + if (typeProgramChangeEnabled && type == MIDI_PROGRAM_CHANGE) { + program = note; + note = 0; + } + if (typeChanPressureEnabled && type == MIDI_CHANNEL_PRESSURE) { + velocity = note; + note = 0; + } + if (typePitchBendEnabled && type == MIDI_PITCH_BEND_CHANGE) { + bend = ((MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_NOTE)) | + (MIDI_PITCH_BEND_MASK & (dwParam1 >> MIDI_SHIFT_PITCH_BEND))) - 8192; + channel = 0; // Weird values on different instruments + note = 0; + velocity = 0; + } + if (!typeSystemMessageEnabled && type == MIDI_SYSTEM_MESSAGE) { + return; + } + if (thruModeEnabled) { + instance->sendNote(status, note, velocity); // relay the message on to all other midi devices. + } + instance->midiReceived(device, raw, channel, status, type, note, velocity, bend, program); // notify the javascript + break; + } + } +} + +void CALLBACK MidiOutProc(HMIDIOUT hmo, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { + switch (wMsg) { + case MOM_OPEN: + // message not used + break; + case MOM_CLOSE: + for (int i = 0; i < midihout.size(); i++) { + if (midihout[i] == hmo) { + midihout[i] = NULL; + instance->allNotesOff(); + instance->midiHardwareChange(); + } + } + break; + } +} + +void Midi::sendRawMessage(int device, int raw) { + if (broadcastEnabled) { + for (int i = 0; i < midihout.size(); i++) { + if (midihout[i] != NULL) { + midiOutShortMsg(midihout[i], raw); + } + } + } else { + midiOutShortMsg(midihout[device], raw); + } +} + +void Midi::sendMessage(int device, int channel, int type, int note, int velocity) { + int message = (channel - 1) | (type << MIDI_SHIFT_STATUS); + if (broadcastEnabled) { + for (int i = 0; i < midihout.size(); i++) { + if (midihout[i] != NULL) { + midiOutShortMsg(midihout[i], message | (note << MIDI_SHIFT_NOTE) | (velocity << MIDI_SHIFT_VELOCITY)); + } + } + } else { + midiOutShortMsg(midihout[device], message | (note << MIDI_SHIFT_NOTE) | (velocity << MIDI_SHIFT_VELOCITY)); + } +} + +void Midi::sendNote(int status, int note, int velocity) { + for (int i = 0; i < midihout.size(); i++) { + if (midihout[i] != NULL) { + midiOutShortMsg(midihout[i], status + (note << MIDI_SHIFT_NOTE) + (velocity << MIDI_SHIFT_VELOCITY)); + } + } +} + +void Midi::MidiSetup() { + midihin.clear(); + midihout.clear(); + + MIDIINCAPS incaps; + for (unsigned int i = 0; i < midiInGetNumDevs(); i++) { + midiInGetDevCaps(i, &incaps, sizeof(MIDIINCAPS)); + + bool found = false; + for (int j = 0; j < midiInExclude.size(); j++) { + if (midiInExclude[j].toStdString().compare(incaps.szPname) == 0) { + found = true; + break; + } + } + if (!found) { // EXCLUDE AN INPUT BY NAME + HMIDIIN tmphin; + midiInOpen(&tmphin, i, (DWORD_PTR)MidiInProc, NULL, CALLBACK_FUNCTION); + midiInStart(tmphin); + midihin.push_back(tmphin); + } + } + + MIDIOUTCAPS outcaps; + for (unsigned int i = 0; i < midiOutGetNumDevs(); i++) { + midiOutGetDevCaps(i, &outcaps, sizeof(MIDIINCAPS)); + + bool found = false; + for (int j = 0; j < midiOutExclude.size(); j++) { + if (midiOutExclude[j].toStdString().compare(outcaps.szPname) == 0) { + found = true; + break; + } + } + if (!found) { // EXCLUDE AN OUTPUT BY NAME + HMIDIOUT tmphout; + midiOutOpen(&tmphout, i, (DWORD_PTR)MidiOutProc, NULL, CALLBACK_FUNCTION); + midihout.push_back(tmphout); + } + } + + allNotesOff(); +} + +void Midi::MidiCleanup() { + allNotesOff(); + + for (int i = 0; i < midihin.size(); i++) { + if (midihin[i] != NULL) { + midiInStop(midihin[i]); + midiInClose(midihin[i]); + } + } + for (int i = 0; i < midihout.size(); i++) { + if (midihout[i] != NULL) { + midiOutClose(midihout[i]); + } + } + midihin.clear(); + midihout.clear(); +} +#else +void Midi::sendRawMessage(int device, int raw) { +} + +void Midi::sendNote(int status, int note, int velocity) { +} + +void Midi::sendMessage(int device, int channel, int type, int note, int velocity){ +} + +void Midi::MidiSetup() { + allNotesOff(); +} + +void Midi::MidiCleanup() { + allNotesOff(); +} +#endif + +void Midi::midiReceived(int device, int raw, int channel, int status, int type, int note, int velocity, int bend, int program) { + QVariantMap eventData; + eventData["device"] = device; + eventData["raw"] = raw; + eventData["channel"] = channel; + eventData["status"] = status; + eventData["type"] = type; + eventData["note"] = note; + eventData["velocity"] = velocity; + eventData["bend"] = bend; + eventData["program"] = program; + emit midiNote(eventData);// Legacy + emit midiMessage(eventData); +} + +void Midi::midiHardwareChange() { + emit midiReset(); +} +// + +Midi::Midi() { + instance = this; +#if defined Q_OS_WIN32 + midiOutExclude.push_back("Microsoft GS Wavetable Synth"); // we don't want to hear this thing (Lags) +#endif + MidiSetup(); +} + +Midi::~Midi() { +} + +void Midi::sendRawDword(int device, int raw) { + sendRawMessage(device, raw); +} + +void Midi::playMidiNote(int status, int note, int velocity) { + sendNote(status, note, velocity); +} + +void Midi::sendMidiMessage(int device, int channel, int type, int note, int velocity) { + sendMessage(device, channel, type, note, velocity); +} + +void Midi::allNotesOff() { + sendNote(MIDI_CONTROL_CHANGE, MIDI_CHANNEL_MODE_ALL_NOTES_OFF, 0); // all notes off +} + +void Midi::resetDevices() { + MidiCleanup(); + MidiSetup(); +} + +void Midi::USBchanged() { + instance->MidiCleanup(); + instance->MidiSetup(); + instance->midiHardwareChange(); +} + +// + +QStringList Midi::listMidiDevices(bool output) { + QStringList rv; +#if defined Q_OS_WIN32 + if (output) { + MIDIOUTCAPS outcaps; + for (unsigned int i = 0; i < midiOutGetNumDevs(); i++) { + midiOutGetDevCaps(i, &outcaps, sizeof(MIDIINCAPS)); + rv.append(outcaps.szPname); + } + } else { + MIDIINCAPS incaps; + for (unsigned int i = 0; i < midiInGetNumDevs(); i++) { + midiInGetDevCaps(i, &incaps, sizeof(MIDIINCAPS)); + rv.append(incaps.szPname); + } + } +#endif + return rv; +} + +void Midi::unblockMidiDevice(QString name, bool output) { + if (output) { + for (unsigned long i = 0; i < midiOutExclude.size(); i++) { + if (midiOutExclude[i].toStdString().compare(name.toStdString()) == 0) { + midiOutExclude.erase(midiOutExclude.begin() + i); + break; + } + } + } else { + for (unsigned long i = 0; i < midiInExclude.size(); i++) { + if (midiInExclude[i].toStdString().compare(name.toStdString()) == 0) { + midiInExclude.erase(midiInExclude.begin() + i); + break; + } + } + } +} + +void Midi::blockMidiDevice(QString name, bool output) { + unblockMidiDevice(name, output); // make sure it's only in there once + if (output) { + midiOutExclude.push_back(name); + } else { + midiInExclude.push_back(name); + } +} + +void Midi::thruModeEnable(bool enable) { + thruModeEnabled = enable; +} + +void Midi::broadcastEnable(bool enable) { + broadcastEnabled = enable; +} + +void Midi::typeNoteOffEnable(bool enable) { + typeNoteOffEnabled = enable; +} + +void Midi::typeNoteOnEnable(bool enable) { + typeNoteOnEnabled = enable; +} + +void Midi::typePolyKeyPressureEnable(bool enable) { + typePolyKeyPressureEnabled = enable; +} + +void Midi::typeControlChangeEnable(bool enable) { + typeControlChangeEnabled = enable; +} + +void Midi::typeProgramChangeEnable(bool enable) { + typeProgramChangeEnabled = enable; +} + +void Midi::typeChanPressureEnable(bool enable) { + typeChanPressureEnabled = enable; +} + +void Midi::typePitchBendEnable(bool enable) { + typePitchBendEnabled = enable; +} + +void Midi::typeSystemMessageEnable(bool enable) { + typeSystemMessageEnabled = enable; +} From bb09c71bda983d59cf5073b6ef31614a831369bf Mon Sep 17 00:00:00 2001 From: Bruce Brown <1st-BrainStormer@users.noreply.github.com> Date: Thu, 18 Jan 2018 21:35:01 -0800 Subject: [PATCH 31/52] Fix crash caused by no midi device present --- Midi.cpp | 406 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ Midi.h | 108 +++++++++++++++ 2 files changed, 514 insertions(+) create mode 100644 Midi.cpp create mode 100644 Midi.h diff --git a/Midi.cpp b/Midi.cpp new file mode 100644 index 0000000000..ec13508805 --- /dev/null +++ b/Midi.cpp @@ -0,0 +1,406 @@ +// +// Midi.cpp +// libraries/midi/src +// +// Created by Burt Sloane +// Modified by Bruce Brown +// 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 +// + +#include "Midi.h" + + +#include + +#if defined Q_OS_WIN32 +#include "Windows.h" +#endif + +#if defined Q_OS_WIN32 +const int MIDI_BYTE_MASK = 0x0FF; +const int MIDI_NIBBLE_MASK = 0x00F; +const int MIDI_PITCH_BEND_MASK = 0x3F80; +const int MIDI_SHIFT_STATUS = 4; +const int MIDI_SHIFT_NOTE = 8; +const int MIDI_SHIFT_VELOCITY = 16; +const int MIDI_SHIFT_PITCH_BEND = 9; +// Status Decode +const int MIDI_NOTE_OFF = 0x8; +const int MIDI_NOTE_ON = 0x9; +const int MIDI_POLYPHONIC_KEY_PRESSURE = 0xa; +const int MIDI_PROGRAM_CHANGE = 0xc; +const int MIDI_CHANNEL_PRESSURE = 0xd; +const int MIDI_PITCH_BEND_CHANGE = 0xe; +const int MIDI_SYSTEM_MESSAGE = 0xf; +#endif + +const int MIDI_CONTROL_CHANGE = 0xb; +const int MIDI_CHANNEL_MODE_ALL_NOTES_OFF = 0x07b; + +static Midi* instance = NULL; // communicate this to non-class callbacks +static bool thruModeEnabled = false; +static bool broadcastEnabled = false; +static bool typeNoteOffEnabled = true; +static bool typeNoteOnEnabled = true; +static bool typePolyKeyPressureEnabled = false; +static bool typeControlChangeEnabled = true; +static bool typeProgramChangeEnabled = true; +static bool typeChanPressureEnabled = false; +static bool typePitchBendEnabled = true; +static bool typeSystemMessageEnabled = false; + +std::vector Midi::midiInExclude; +std::vector Midi::midiOutExclude; + +#if defined Q_OS_WIN32 + +#pragma comment(lib, "Winmm.lib") + +// +std::vector midihin; +std::vector midihout; + +void CALLBACK MidiInProc(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { + switch (wMsg) { + case MIM_OPEN: + // message not used + break; + case MIM_CLOSE: + for (int i = 0; i < midihin.size(); i++) { + if (midihin[i] == hMidiIn) { + midihin[i] = NULL; + instance->allNotesOff(); + instance->midiHardwareChange(); + } + } + break; + case MIM_DATA: { + int device = -1; + for (int i = 0; i < midihin.size(); i++) { + if (midihin[i] == hMidiIn) { + device = i; + } + } + int raw = dwParam1; + int channel = (MIDI_NIBBLE_MASK & dwParam1) + 1; + int status = MIDI_BYTE_MASK & dwParam1; + int type = MIDI_NIBBLE_MASK & (dwParam1 >> MIDI_SHIFT_STATUS); + int note = MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_NOTE); + int velocity = MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_VELOCITY); + int bend = 0; + int program = 0; + if (!typeNoteOffEnabled && type == MIDI_NOTE_OFF) { + return; + } + if (!typeNoteOnEnabled && type == MIDI_NOTE_ON) { + return; + } + if (!typePolyKeyPressureEnabled && type == MIDI_POLYPHONIC_KEY_PRESSURE) { + return; + } + if (!typeControlChangeEnabled && type == MIDI_CONTROL_CHANGE) { + return; + } + if (typeProgramChangeEnabled && type == MIDI_PROGRAM_CHANGE) { + program = note; + note = 0; + } + if (typeChanPressureEnabled && type == MIDI_CHANNEL_PRESSURE) { + velocity = note; + note = 0; + } + if (typePitchBendEnabled && type == MIDI_PITCH_BEND_CHANGE) { + bend = ((MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_NOTE)) | + (MIDI_PITCH_BEND_MASK & (dwParam1 >> MIDI_SHIFT_PITCH_BEND))) - 8192; + channel = 0; // Weird values on different instruments + note = 0; + velocity = 0; + } + if (!typeSystemMessageEnabled && type == MIDI_SYSTEM_MESSAGE) { + return; + } + if (thruModeEnabled) { + instance->sendNote(status, note, velocity); // relay the message on to all other midi devices. + } + instance->midiReceived(device, raw, channel, status, type, note, velocity, bend, program); // notify the javascript + break; + } + } +} + +void CALLBACK MidiOutProc(HMIDIOUT hmo, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { + switch (wMsg) { + case MOM_OPEN: + // message not used + break; + case MOM_CLOSE: + for (int i = 0; i < midihout.size(); i++) { + if (midihout[i] == hmo) { + midihout[i] = NULL; + instance->allNotesOff(); + instance->midiHardwareChange(); + } + } + break; + } +} + +void Midi::sendRawMessage(int device, int raw) { + if (broadcastEnabled) { + for (int i = 0; i < midihout.size(); i++) { + if (midihout[i] != NULL) { + midiOutShortMsg(midihout[i], raw); + } + } + } else { + midiOutShortMsg(midihout[device], raw); + } +} + +void Midi::sendMessage(int device, int channel, int type, int note, int velocity) { + int message = (channel - 1) | (type << MIDI_SHIFT_STATUS); + if (broadcastEnabled) { + for (int i = 1; i < midihout.size(); i++) { // Skip 0 (Microsoft GS Wavetable Synth) + if (midihout[i] != NULL) { + midiOutShortMsg(midihout[i], message | (note << MIDI_SHIFT_NOTE) | (velocity << MIDI_SHIFT_VELOCITY)); + } + } + } else { + midiOutShortMsg(midihout[device], message | (note << MIDI_SHIFT_NOTE) | (velocity << MIDI_SHIFT_VELOCITY)); + } +} + +void Midi::sendNote(int status, int note, int velocity) { + for (int i = 1; i < midihout.size(); i++) { // Skip 0 (Microsoft GS Wavetable Synth) + if (midihout[i] != NULL) { + midiOutShortMsg(midihout[i], status + (note << MIDI_SHIFT_NOTE) + (velocity << MIDI_SHIFT_VELOCITY)); + } + } +} + +void Midi::MidiSetup() { + midihin.clear(); + midihout.clear(); + + MIDIINCAPS incaps; + for (unsigned int i = 0; i < midiInGetNumDevs(); i++) { + midiInGetDevCaps(i, &incaps, sizeof(MIDIINCAPS)); + + bool found = false; + for (int j = 0; j < midiInExclude.size(); j++) { + if (midiInExclude[j].toStdString().compare(incaps.szPname) == 0) { + found = true; + break; + } + } + if (!found) { // EXCLUDE AN INPUT BY NAME + HMIDIIN tmphin; + midiInOpen(&tmphin, i, (DWORD_PTR)MidiInProc, NULL, CALLBACK_FUNCTION); + midiInStart(tmphin); + midihin.push_back(tmphin); + } + } + + MIDIOUTCAPS outcaps; + for (unsigned int i = 0; i < midiOutGetNumDevs(); i++) { + midiOutGetDevCaps(i, &outcaps, sizeof(MIDIINCAPS)); + + bool found = false; + for (int j = 0; j < midiOutExclude.size(); j++) { + if (midiOutExclude[j].toStdString().compare(outcaps.szPname) == 0) { + found = true; + break; + } + } + if (!found) { // EXCLUDE AN OUTPUT BY NAME + HMIDIOUT tmphout; + midiOutOpen(&tmphout, i, (DWORD_PTR)MidiOutProc, NULL, CALLBACK_FUNCTION); + midihout.push_back(tmphout); + } + } + + allNotesOff(); +} + +void Midi::MidiCleanup() { + allNotesOff(); + + for (int i = 0; i < midihin.size(); i++) { + if (midihin[i] != NULL) { + midiInStop(midihin[i]); + midiInClose(midihin[i]); + } + } + for (int i = 0; i < midihout.size(); i++) { + if (midihout[i] != NULL) { + midiOutClose(midihout[i]); + } + } + midihin.clear(); + midihout.clear(); +} +#else +void Midi::sendRawMessage(int device, int raw) { +} + +void Midi::sendNote(int status, int note, int velocity) { +} + +void Midi::sendMessage(int device, int channel, int type, int note, int velocity){ +} + +void Midi::MidiSetup() { + allNotesOff(); +} + +void Midi::MidiCleanup() { + allNotesOff(); +} +#endif + +void Midi::midiReceived(int device, int raw, int channel, int status, int type, int note, int velocity, int bend, int program) { + QVariantMap eventData; + eventData["device"] = device; + eventData["raw"] = raw; + eventData["channel"] = channel; + eventData["status"] = status; + eventData["type"] = type; + eventData["note"] = note; + eventData["velocity"] = velocity; + eventData["bend"] = bend; + eventData["program"] = program; + emit midiNote(eventData);// Legacy + emit midiMessage(eventData); +} + +void Midi::midiHardwareChange() { + emit midiReset(); +} +// + +Midi::Midi() { + instance = this; + MidiSetup(); +} + +Midi::~Midi() { +} + +void Midi::sendRawDword(int device, int raw) { + sendRawMessage(device, raw); +} + +void Midi::playMidiNote(int status, int note, int velocity) { + sendNote(status, note, velocity); +} + +void Midi::sendMidiMessage(int device, int channel, int type, int note, int velocity) { + sendMessage(device, channel, type, note, velocity); +} + +void Midi::allNotesOff() { + sendNote(MIDI_CONTROL_CHANGE, MIDI_CHANNEL_MODE_ALL_NOTES_OFF, 0); // all notes off +} + +void Midi::resetDevices() { + MidiCleanup(); + MidiSetup(); +} + +void Midi::USBchanged() { + instance->MidiCleanup(); + instance->MidiSetup(); + instance->midiHardwareChange(); +} + +// + +QStringList Midi::listMidiDevices(bool output) { + QStringList rv; +#if defined Q_OS_WIN32 + if (output) { + MIDIOUTCAPS outcaps; + for (unsigned int i = 0; i < midiOutGetNumDevs(); i++) { + midiOutGetDevCaps(i, &outcaps, sizeof(MIDIINCAPS)); + rv.append(outcaps.szPname); + } + } else { + MIDIINCAPS incaps; + for (unsigned int i = 0; i < midiInGetNumDevs(); i++) { + midiInGetDevCaps(i, &incaps, sizeof(MIDIINCAPS)); + rv.append(incaps.szPname); + } + } +#endif + return rv; +} + +void Midi::unblockMidiDevice(QString name, bool output) { + if (output) { + for (unsigned long i = 0; i < midiOutExclude.size(); i++) { + if (midiOutExclude[i].toStdString().compare(name.toStdString()) == 0) { + midiOutExclude.erase(midiOutExclude.begin() + i); + break; + } + } + } else { + for (unsigned long i = 0; i < midiInExclude.size(); i++) { + if (midiInExclude[i].toStdString().compare(name.toStdString()) == 0) { + midiInExclude.erase(midiInExclude.begin() + i); + break; + } + } + } +} + +void Midi::blockMidiDevice(QString name, bool output) { + unblockMidiDevice(name, output); // make sure it's only in there once + if (output) { + midiOutExclude.push_back(name); + } else { + midiInExclude.push_back(name); + } +} + +void Midi::thruModeEnable(bool enable) { + thruModeEnabled = enable; +} + +void Midi::broadcastEnable(bool enable) { + broadcastEnabled = enable; +} + +void Midi::typeNoteOffEnable(bool enable) { + typeNoteOffEnabled = enable; +} + +void Midi::typeNoteOnEnable(bool enable) { + typeNoteOnEnabled = enable; +} + +void Midi::typePolyKeyPressureEnable(bool enable) { + typePolyKeyPressureEnabled = enable; +} + +void Midi::typeControlChangeEnable(bool enable) { + typeControlChangeEnabled = enable; +} + +void Midi::typeProgramChangeEnable(bool enable) { + typeProgramChangeEnabled = enable; +} + +void Midi::typeChanPressureEnable(bool enable) { + typeChanPressureEnabled = enable; +} + +void Midi::typePitchBendEnable(bool enable) { + typePitchBendEnabled = enable; +} + +void Midi::typeSystemMessageEnable(bool enable) { + typeSystemMessageEnabled = enable; +} diff --git a/Midi.h b/Midi.h new file mode 100644 index 0000000000..f7940bbe5d --- /dev/null +++ b/Midi.h @@ -0,0 +1,108 @@ +// +// Midi.h +// libraries/midi/src +// +// Created by Burt Sloane +// Modified by Bruce Brown +// 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 +// + +#ifndef hifi_Midi_h +#define hifi_Midi_h + +#include +#include +#include + +#include +#include + +class Midi : public QObject, public Dependency { + Q_OBJECT + SINGLETON_DEPENDENCY + +public: + void midiReceived(int device, int raw, int channel, int status, int type, int note, int velocity, int bend, int program); // relay a note to Javascript + void midiHardwareChange(); // relay hardware change to Javascript + void sendRawMessage(int device, int raw); // relay midi message to MIDI outputs + void sendNote(int status, int note, int velocity); // relay a note to MIDI outputs + void sendMessage(int device, int channel, int type, int note, int velocity); // relay a message to MIDI outputs + static void USBchanged(); + +private: + static std::vector midiInExclude; + static std::vector midiOutExclude; + +private: + void MidiSetup(); + void MidiCleanup(); + +signals: + void midiNote(QVariantMap eventData); + void midiMessage(QVariantMap eventData); + void midiReset(); + + public slots: + // Send Raw Midi Packet to all connected devices + Q_INVOKABLE void sendRawDword(int device, int raw); + /// Send Raw Midi message to selected device + /// @param {int} device: device number + /// @param {int} raw: raw midi message (DWORD) + + // Send Midi Message to all connected devices + Q_INVOKABLE void sendMidiMessage(int device, int channel, int type, int note, int velocity); + /// Send midi message to selected device/devices + /// @param {int} device: device number + /// @param {int} channel: channel number + /// @param {int} type: 0x8 is noteoff, 0x9 is noteon (if velocity=0, noteoff), etc + /// @param {int} note: midi note number + /// @param {int} velocity: note velocity (0 means noteoff) + + // Send Midi Message to all connected devices + Q_INVOKABLE void playMidiNote(int status, int note, int velocity); + /// play a note on all connected devices + /// @param {int} status: 0x80 is noteoff, 0x90 is noteon (if velocity=0, noteoff), etc + /// @param {int} note: midi note number + /// @param {int} velocity: note velocity (0 means noteoff) + + /// turn off all notes on all connected devices + Q_INVOKABLE void allNotesOff(); + + /// clean up and re-discover attached devices + Q_INVOKABLE void resetDevices(); + + /// ask for a list of inputs/outputs + Q_INVOKABLE QStringList listMidiDevices(bool output); + + /// block an input/output by name + Q_INVOKABLE void blockMidiDevice(QString name, bool output); + + /// unblock an input/output by name + Q_INVOKABLE void unblockMidiDevice(QString name, bool output); + + /// repeat all incoming notes to all outputs (default disabled) + Q_INVOKABLE void thruModeEnable(bool enable); + + /// broadcast on all unblocked devices + Q_INVOKABLE void broadcastEnable(bool enable); + + /// filter by event types + Q_INVOKABLE void typeNoteOffEnable(bool enable); + Q_INVOKABLE void typeNoteOnEnable(bool enable); + Q_INVOKABLE void typePolyKeyPressureEnable(bool enable); + Q_INVOKABLE void typeControlChangeEnable(bool enable); + Q_INVOKABLE void typeProgramChangeEnable(bool enable); + Q_INVOKABLE void typeChanPressureEnable(bool enable); + Q_INVOKABLE void typePitchBendEnable(bool enable); + Q_INVOKABLE void typeSystemMessageEnable(bool enable); + + +public: + Midi(); + virtual ~Midi(); +}; + +#endif // hifi_Midi_h From 12c48a38f75a6be2db0a545bb4ae057438016edb Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Fri, 19 Jan 2018 13:58:35 +0100 Subject: [PATCH 32/52] Normalized diffuse & specular of directional, point and spot lights such as a light intensity of 1 gives a perpendicular diffuse lighting of the same color as the albedo for dielectric materials. --- libraries/render-utils/src/LightingModel.slh | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/libraries/render-utils/src/LightingModel.slh b/libraries/render-utils/src/LightingModel.slh index 7d08fdabaf..be8330f198 100644 --- a/libraries/render-utils/src/LightingModel.slh +++ b/libraries/render-utils/src/LightingModel.slh @@ -186,7 +186,7 @@ float specularDistribution(SurfaceData surface) { // Add geometric factors G1(n,l) and G1(n,v) float smithInvG1NdotL = evalSmithInvG1(surface.roughness4, surface.ndotl); denom *= surface.smithInvG1NdotV * smithInvG1NdotL; - // Don't divide by PI as it will be done later + // Don't divide by PI as this is part of the light normalization factor float power = surface.roughness4 / denom; return power; } @@ -202,12 +202,11 @@ vec4 evalPBRShading(float metallic, vec3 fresnel, SurfaceData surface) { vec3 specular = fresnelColor * power * angleAttenuation; float diffuse = (1.0 - metallic) * angleAttenuation * (1.0 - fresnelColor.x); - diffuse /= 3.1415926; - // Diffuse is divided by PI but specular isn't because an infinitesimal volume light source - // has a multiplier of PI, says Naty Hoffman. + // We don't divided by PI, as the "normalized" equations state we should, because we decide, as Naty Hoffman, that + // we wish to have a similar color as raw albedo on a perfectly diffuse surface perpendicularly lit + // by a white light of intensity 1. But this is an arbitrary normalization of what light intensity "means". // (see http://blog.selfshadow.com/publications/s2013-shading-course/hoffman/s2013_pbs_physics_math_notes.pdf // page 23 paragraph "Punctual light sources") - return vec4(specular, diffuse); } @@ -222,9 +221,9 @@ vec4 evalPBRShadingDielectric(SurfaceData surface, float fresnel) { vec3 specular = vec3(fresnelScalar) * power * angleAttenuation; float diffuse = angleAttenuation * (1.0 - fresnelScalar); - diffuse /= 3.1415926; - // Diffuse is divided by PI but specular isn't because an infinitesimal volume light source - // has a multiplier of PI, says Naty Hoffman. + // We don't divided by PI, as the "normalized" equations state we should, because we decide, as Naty Hoffman, that + // we wish to have a similar color as raw albedo on a perfectly diffuse surface perpendicularly lit + // by a white light of intensity 1. But this is an arbitrary normalization of what light intensity "means". // (see http://blog.selfshadow.com/publications/s2013-shading-course/hoffman/s2013_pbs_physics_math_notes.pdf // page 23 paragraph "Punctual light sources") return vec4(specular, diffuse); @@ -239,8 +238,9 @@ vec4 evalPBRShadingMetallic(SurfaceData surface, vec3 fresnel) { float power = specularDistribution(surface); vec3 specular = fresnelColor * power * angleAttenuation; - // Specular isn't divided by PI because an infinitesimal volume light source - // has a multiplier of PI, says Naty Hoffman. + // We don't divided by PI, as the "normalized" equations state we should, because we decide, as Naty Hoffman, that + // we wish to have a similar color as raw albedo on a perfectly diffuse surface perpendicularly lit + // by a white light of intensity 1. But this is an arbitrary normalization of what light intensity "means". // (see http://blog.selfshadow.com/publications/s2013-shading-course/hoffman/s2013_pbs_physics_math_notes.pdf // page 23 paragraph "Punctual light sources") return vec4(specular, 0.f); From 27ea74f5bf6af58298626fa0ab34b382f2acd127 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 19 Jan 2018 11:22:37 -0700 Subject: [PATCH 33/52] Fix sortOrder param on tablet buttons --- .../ui/src/ui/TabletScriptingInterface.cpp | 31 +++++++++++++++++-- .../ui/src/ui/TabletScriptingInterface.h | 1 + .../developer/tests/dynamics/dynamicsTests.js | 3 +- .../utilities/render/debugHighlight.js | 3 +- scripts/developer/utilities/render/lod.js | 3 +- scripts/developer/utilities/render/luci.js | 3 +- scripts/system/chat.js | 3 +- scripts/system/generalSettings.js | 3 +- scripts/system/goto.js | 3 +- scripts/system/run.js | 3 +- scripts/system/tablet-users.js | 3 +- .../marketplace/clap/clapApp.js | 3 +- .../skyboxChanger/skyboxchanger.js | 3 +- 13 files changed, 40 insertions(+), 25 deletions(-) diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index c69ec1ce84..33ff9b06f7 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -31,6 +31,8 @@ const QString SYSTEM_TOOLBAR = "com.highfidelity.interface.toolbar.system"; const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system"; const QString TabletScriptingInterface::QML = "hifi/tablet/TabletRoot.qml"; +const QString BUTTON_SORT_ORDER_KEY = "sortOrder"; +const int DEFAULT_BUTTON_SORT_ORDER = 100; static QString getUsername() { QString username = "Unknown user"; @@ -74,11 +76,20 @@ QVariant TabletButtonListModel::data(const QModelIndex& index, int role) const { } TabletButtonProxy* TabletButtonListModel::addButton(const QVariant& properties) { - auto tabletButtonProxy = QSharedPointer(new TabletButtonProxy(properties.toMap())); + QVariantMap newTabletButtonProperties = properties.toMap(); + if (newTabletButtonProperties.find(BUTTON_SORT_ORDER_KEY) == newTabletButtonProperties.end()) { + newTabletButtonProperties[BUTTON_SORT_ORDER_KEY] = DEFAULT_BUTTON_SORT_ORDER; + } + int insertButtonUsingIndex = computeNewButtonIndex(newTabletButtonProperties); + auto newTabletButtonProxy = QSharedPointer(new TabletButtonProxy(newTabletButtonProperties)); beginResetModel(); - _buttons.push_back(tabletButtonProxy); + if (insertButtonUsingIndex < _buttons.size()) { + _buttons.insert(_buttons.begin() + insertButtonUsingIndex, newTabletButtonProxy); + } else { + _buttons.push_back(newTabletButtonProxy); + } endResetModel(); - return tabletButtonProxy.data(); + return newTabletButtonProxy.data(); } void TabletButtonListModel::removeButton(TabletButtonProxy* button) { @@ -92,6 +103,20 @@ void TabletButtonListModel::removeButton(TabletButtonProxy* button) { endResetModel(); } +int TabletButtonListModel::computeNewButtonIndex(const QVariantMap& newButtonProperties) { + int buttonCount = (int)_buttons.size(); + int newButtonSortOrder = newButtonProperties[BUTTON_SORT_ORDER_KEY].toInt(); + if (newButtonSortOrder == DEFAULT_BUTTON_SORT_ORDER) return buttonCount; + for (int i = 0; i < buttonCount; i++) { + QVariantMap tabletButtonProperties = _buttons[i]->getProperties(); + int tabletButtonSortOrder = tabletButtonProperties[BUTTON_SORT_ORDER_KEY].toInt(); + if (newButtonSortOrder <= tabletButtonSortOrder) { + return i; + } + } + return buttonCount; +} + TabletButtonsProxyModel::TabletButtonsProxyModel(QObject *parent) : QSortFilterProxyModel(parent) { } diff --git a/libraries/ui/src/ui/TabletScriptingInterface.h b/libraries/ui/src/ui/TabletScriptingInterface.h index 56e3ae257b..34827117f0 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.h +++ b/libraries/ui/src/ui/TabletScriptingInterface.h @@ -115,6 +115,7 @@ protected: friend class TabletProxy; TabletButtonProxy* addButton(const QVariant& properties); void removeButton(TabletButtonProxy* button); + int computeNewButtonIndex(const QVariantMap& newButtonProperties); using List = std::list>; static QHash _roles; static Qt::ItemFlags _flags; diff --git a/scripts/developer/tests/dynamics/dynamicsTests.js b/scripts/developer/tests/dynamics/dynamicsTests.js index c0b001eab3..e9262c9308 100644 --- a/scripts/developer/tests/dynamics/dynamicsTests.js +++ b/scripts/developer/tests/dynamics/dynamicsTests.js @@ -22,8 +22,7 @@ var button = tablet.addButton({ icon: Script.resolvePath("dynamicsTests.svg"), - text: "Dynamics", - sortOrder: 15 + text: "Dynamics" }); diff --git a/scripts/developer/utilities/render/debugHighlight.js b/scripts/developer/utilities/render/debugHighlight.js index e70565cec2..c2173f6e2a 100644 --- a/scripts/developer/utilities/render/debugHighlight.js +++ b/scripts/developer/utilities/render/debugHighlight.js @@ -32,8 +32,7 @@ var button = tablet.addButton({ text: TABLET_BUTTON_NAME, icon: ICON_URL, - activeIcon: ACTIVE_ICON_URL, - sortOrder: 1 + activeIcon: ACTIVE_ICON_URL }); var hasEventBridge = false; diff --git a/scripts/developer/utilities/render/lod.js b/scripts/developer/utilities/render/lod.js index dc0b99edc2..307e509d39 100644 --- a/scripts/developer/utilities/render/lod.js +++ b/scripts/developer/utilities/render/lod.js @@ -30,8 +30,7 @@ var button = tablet.addButton({ text: TABLET_BUTTON_NAME, icon: ICON_URL, - activeIcon: ACTIVE_ICON_URL, - sortOrder: 1 + activeIcon: ACTIVE_ICON_URL }); var hasEventBridge = false; diff --git a/scripts/developer/utilities/render/luci.js b/scripts/developer/utilities/render/luci.js index 1e2ac1261f..6482c884ff 100644 --- a/scripts/developer/utilities/render/luci.js +++ b/scripts/developer/utilities/render/luci.js @@ -31,8 +31,7 @@ var button = tablet.addButton({ text: TABLET_BUTTON_NAME, icon: ICON_URL, - activeIcon: ACTIVE_ICON_URL, - sortOrder: 1 + activeIcon: ACTIVE_ICON_URL }); var hasEventBridge = false; diff --git a/scripts/system/chat.js b/scripts/system/chat.js index 0cb414e23c..b0a2e114a3 100644 --- a/scripts/system/chat.js +++ b/scripts/system/chat.js @@ -945,8 +945,7 @@ tabletButton = tablet.addButton({ icon: tabletButtonIcon, activeIcon: tabletButtonActiveIcon, - text: tabletButtonName, - sortOrder: 0 + text: tabletButtonName }); Messages.subscribe(channelName); diff --git a/scripts/system/generalSettings.js b/scripts/system/generalSettings.js index 082528ffc5..d3848da7d0 100644 --- a/scripts/system/generalSettings.js +++ b/scripts/system/generalSettings.js @@ -37,8 +37,7 @@ tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); button = tablet.addButton({ icon: "icons/tablet-icons/goto-i.svg", - text: buttonName, - sortOrder: 8 + text: buttonName }); } diff --git a/scripts/system/goto.js b/scripts/system/goto.js index d364bf579e..5cc5bad844 100644 --- a/scripts/system/goto.js +++ b/scripts/system/goto.js @@ -41,8 +41,7 @@ if (Settings.getValue("HUDUIEnabled")) { button = tablet.addButton({ icon: "icons/tablet-icons/goto-i.svg", activeIcon: "icons/tablet-icons/goto-a.svg", - text: buttonName, - sortOrder: 8 + text: buttonName }); } diff --git a/scripts/system/run.js b/scripts/system/run.js index 054cca6d9c..c34271b18d 100644 --- a/scripts/system/run.js +++ b/scripts/system/run.js @@ -10,8 +10,7 @@ var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var button = tablet.addButton({ icon: Script.resolvePath("assets/images/run.svg"), - text: "RUN", - sortOrder: 15 + text: "RUN" }); function onClicked() { diff --git a/scripts/system/tablet-users.js b/scripts/system/tablet-users.js index 6f37cd55eb..6181173818 100644 --- a/scripts/system/tablet-users.js +++ b/scripts/system/tablet-users.js @@ -37,8 +37,7 @@ var button = tablet.addButton({ icon: "icons/tablet-icons/users-i.svg", activeIcon: "icons/tablet-icons/users-a.svg", - text: "USERS", - sortOrder: 11 + text: "USERS" }); var onUsersScreen = false; diff --git a/unpublishedScripts/marketplace/clap/clapApp.js b/unpublishedScripts/marketplace/clap/clapApp.js index b2d8ce55db..7118b3623c 100644 --- a/unpublishedScripts/marketplace/clap/clapApp.js +++ b/unpublishedScripts/marketplace/clap/clapApp.js @@ -31,8 +31,7 @@ var activeButton = tablet.addButton({ icon: whiteIcon, activeIcon: blackIcon, text: APP_NAME, - isActive: isActive, - sortOrder: 11 + isActive: isActive }); if (isActive) { diff --git a/unpublishedScripts/marketplace/skyboxChanger/skyboxchanger.js b/unpublishedScripts/marketplace/skyboxChanger/skyboxchanger.js index 7bc65722cd..0bd23552e4 100644 --- a/unpublishedScripts/marketplace/skyboxChanger/skyboxchanger.js +++ b/unpublishedScripts/marketplace/skyboxChanger/skyboxchanger.js @@ -32,8 +32,7 @@ var button = tablet.addButton({ icon: ICONS.icon, activeIcon: ICONS.activeIcon, - text: TABLET_BUTTON_NAME, - sortOrder: 1 + text: TABLET_BUTTON_NAME }); var hasEventBridge = false; From 39e938ccc7add8d1a5debca2c06e9bce37f36686 Mon Sep 17 00:00:00 2001 From: Bruce Brown <1st-BrainStormer@users.noreply.github.com> Date: Fri, 19 Jan 2018 11:52:09 -0800 Subject: [PATCH 34/52] Removed Tabs & CR/LF --- libraries/midi/src/Midi.cpp | 815 ++++++++++++++++++------------------ 1 file changed, 406 insertions(+), 409 deletions(-) diff --git a/libraries/midi/src/Midi.cpp b/libraries/midi/src/Midi.cpp index 680e53a8c7..7f086340ba 100644 --- a/libraries/midi/src/Midi.cpp +++ b/libraries/midi/src/Midi.cpp @@ -1,409 +1,406 @@ -// -// Midi.cpp -// libraries/midi/src -// -// Created by Burt Sloane -// Modified by Bruce Brown -// 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 -// - -#include "Midi.h" - - -#include - -#if defined Q_OS_WIN32 -#include "Windows.h" -#endif - -#if defined Q_OS_WIN32 -const int MIDI_BYTE_MASK = 0x0FF; -const int MIDI_NIBBLE_MASK = 0x00F; -const int MIDI_PITCH_BEND_MASK = 0x3F80; -const int MIDI_SHIFT_STATUS = 4; -const int MIDI_SHIFT_NOTE = 8; -const int MIDI_SHIFT_VELOCITY = 16; -const int MIDI_SHIFT_PITCH_BEND = 9; -// Status Decode -const int MIDI_NOTE_OFF = 0x8; -const int MIDI_NOTE_ON = 0x9; -const int MIDI_POLYPHONIC_KEY_PRESSURE = 0xa; -const int MIDI_PROGRAM_CHANGE = 0xc; -const int MIDI_CHANNEL_PRESSURE = 0xd; -const int MIDI_PITCH_BEND_CHANGE = 0xe; -const int MIDI_SYSTEM_MESSAGE = 0xf; -#endif - -const int MIDI_CONTROL_CHANGE = 0xb; -const int MIDI_CHANNEL_MODE_ALL_NOTES_OFF = 0x07b; - -static Midi* instance = NULL; // communicate this to non-class callbacks -static bool thruModeEnabled = false; -static bool broadcastEnabled = false; -static bool typeNoteOffEnabled = true; -static bool typeNoteOnEnabled = true; -static bool typePolyKeyPressureEnabled = false; -static bool typeControlChangeEnabled = true; -static bool typeProgramChangeEnabled = true; -static bool typeChanPressureEnabled = false; -static bool typePitchBendEnabled = true; -static bool typeSystemMessageEnabled = false; - -std::vector Midi::midiInExclude; -std::vector Midi::midiOutExclude; - -#if defined Q_OS_WIN32 - -#pragma comment(lib, "Winmm.lib") - -// -std::vector midihin; -std::vector midihout; - -void CALLBACK MidiInProc(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { - switch (wMsg) { - case MIM_OPEN: - // message not used - break; - case MIM_CLOSE: - for (int i = 0; i < midihin.size(); i++) { - if (midihin[i] == hMidiIn) { - midihin[i] = NULL; - instance->allNotesOff(); - instance->midiHardwareChange(); - } - } - break; - case MIM_DATA: { - int device = -1; - for (int i = 0; i < midihin.size(); i++) { - if (midihin[i] == hMidiIn) { - device = i; - } - } - int raw = dwParam1; - int channel = (MIDI_NIBBLE_MASK & dwParam1) + 1; - int status = MIDI_BYTE_MASK & dwParam1; - int type = MIDI_NIBBLE_MASK & (dwParam1 >> MIDI_SHIFT_STATUS); - int note = MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_NOTE); - int velocity = MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_VELOCITY); - int bend = 0; - int program = 0; - if (!typeNoteOffEnabled && type == MIDI_NOTE_OFF) { - return; - } - if (!typeNoteOnEnabled && type == MIDI_NOTE_ON) { - return; - } - if (!typePolyKeyPressureEnabled && type == MIDI_POLYPHONIC_KEY_PRESSURE) { - return; - } - if (!typeControlChangeEnabled && type == MIDI_CONTROL_CHANGE) { - return; - } - if (typeProgramChangeEnabled && type == MIDI_PROGRAM_CHANGE) { - program = note; - note = 0; - } - if (typeChanPressureEnabled && type == MIDI_CHANNEL_PRESSURE) { - velocity = note; - note = 0; - } - if (typePitchBendEnabled && type == MIDI_PITCH_BEND_CHANGE) { - bend = ((MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_NOTE)) | - (MIDI_PITCH_BEND_MASK & (dwParam1 >> MIDI_SHIFT_PITCH_BEND))) - 8192; - channel = 0; // Weird values on different instruments - note = 0; - velocity = 0; - } - if (!typeSystemMessageEnabled && type == MIDI_SYSTEM_MESSAGE) { - return; - } - if (thruModeEnabled) { - instance->sendNote(status, note, velocity); // relay the message on to all other midi devices. - } - instance->midiReceived(device, raw, channel, status, type, note, velocity, bend, program); // notify the javascript - break; - } - } -} - -void CALLBACK MidiOutProc(HMIDIOUT hmo, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { - switch (wMsg) { - case MOM_OPEN: - // message not used - break; - case MOM_CLOSE: - for (int i = 0; i < midihout.size(); i++) { - if (midihout[i] == hmo) { - midihout[i] = NULL; - instance->allNotesOff(); - instance->midiHardwareChange(); - } - } - break; - } -} - -void Midi::sendRawMessage(int device, int raw) { - if (broadcastEnabled) { - for (int i = 0; i < midihout.size(); i++) { - if (midihout[i] != NULL) { - midiOutShortMsg(midihout[i], raw); - } - } - } else { - midiOutShortMsg(midihout[device], raw); - } -} - -void Midi::sendMessage(int device, int channel, int type, int note, int velocity) { - int message = (channel - 1) | (type << MIDI_SHIFT_STATUS); - if (broadcastEnabled) { - for (int i = 0; i < midihout.size(); i++) { - if (midihout[i] != NULL) { - midiOutShortMsg(midihout[i], message | (note << MIDI_SHIFT_NOTE) | (velocity << MIDI_SHIFT_VELOCITY)); - } - } - } else { - midiOutShortMsg(midihout[device], message | (note << MIDI_SHIFT_NOTE) | (velocity << MIDI_SHIFT_VELOCITY)); - } -} - -void Midi::sendNote(int status, int note, int velocity) { - for (int i = 0; i < midihout.size(); i++) { - if (midihout[i] != NULL) { - midiOutShortMsg(midihout[i], status + (note << MIDI_SHIFT_NOTE) + (velocity << MIDI_SHIFT_VELOCITY)); - } - } -} - -void Midi::MidiSetup() { - midihin.clear(); - midihout.clear(); - - MIDIINCAPS incaps; - for (unsigned int i = 0; i < midiInGetNumDevs(); i++) { - midiInGetDevCaps(i, &incaps, sizeof(MIDIINCAPS)); - - bool found = false; - for (int j = 0; j < midiInExclude.size(); j++) { - if (midiInExclude[j].toStdString().compare(incaps.szPname) == 0) { - found = true; - break; - } - } - if (!found) { // EXCLUDE AN INPUT BY NAME - HMIDIIN tmphin; - midiInOpen(&tmphin, i, (DWORD_PTR)MidiInProc, NULL, CALLBACK_FUNCTION); - midiInStart(tmphin); - midihin.push_back(tmphin); - } - } - - MIDIOUTCAPS outcaps; - for (unsigned int i = 0; i < midiOutGetNumDevs(); i++) { - midiOutGetDevCaps(i, &outcaps, sizeof(MIDIINCAPS)); - - bool found = false; - for (int j = 0; j < midiOutExclude.size(); j++) { - if (midiOutExclude[j].toStdString().compare(outcaps.szPname) == 0) { - found = true; - break; - } - } - if (!found) { // EXCLUDE AN OUTPUT BY NAME - HMIDIOUT tmphout; - midiOutOpen(&tmphout, i, (DWORD_PTR)MidiOutProc, NULL, CALLBACK_FUNCTION); - midihout.push_back(tmphout); - } - } - - allNotesOff(); -} - -void Midi::MidiCleanup() { - allNotesOff(); - - for (int i = 0; i < midihin.size(); i++) { - if (midihin[i] != NULL) { - midiInStop(midihin[i]); - midiInClose(midihin[i]); - } - } - for (int i = 0; i < midihout.size(); i++) { - if (midihout[i] != NULL) { - midiOutClose(midihout[i]); - } - } - midihin.clear(); - midihout.clear(); -} -#else -void Midi::sendRawMessage(int device, int raw) { -} - -void Midi::sendNote(int status, int note, int velocity) { -} - -void Midi::sendMessage(int device, int channel, int type, int note, int velocity){ -} - -void Midi::MidiSetup() { - allNotesOff(); -} - -void Midi::MidiCleanup() { - allNotesOff(); -} -#endif - -void Midi::midiReceived(int device, int raw, int channel, int status, int type, int note, int velocity, int bend, int program) { - QVariantMap eventData; - eventData["device"] = device; - eventData["raw"] = raw; - eventData["channel"] = channel; - eventData["status"] = status; - eventData["type"] = type; - eventData["note"] = note; - eventData["velocity"] = velocity; - eventData["bend"] = bend; - eventData["program"] = program; - emit midiNote(eventData);// Legacy - emit midiMessage(eventData); -} - -void Midi::midiHardwareChange() { - emit midiReset(); -} -// - -Midi::Midi() { - instance = this; -#if defined Q_OS_WIN32 - midiOutExclude.push_back("Microsoft GS Wavetable Synth"); // we don't want to hear this thing (Lags) -#endif - MidiSetup(); -} - -Midi::~Midi() { -} - -void Midi::sendRawDword(int device, int raw) { - sendRawMessage(device, raw); -} - -void Midi::playMidiNote(int status, int note, int velocity) { - sendNote(status, note, velocity); -} - -void Midi::sendMidiMessage(int device, int channel, int type, int note, int velocity) { - sendMessage(device, channel, type, note, velocity); -} - -void Midi::allNotesOff() { - sendNote(MIDI_CONTROL_CHANGE, MIDI_CHANNEL_MODE_ALL_NOTES_OFF, 0); // all notes off -} - -void Midi::resetDevices() { - MidiCleanup(); - MidiSetup(); -} - -void Midi::USBchanged() { - instance->MidiCleanup(); - instance->MidiSetup(); - instance->midiHardwareChange(); -} - -// - -QStringList Midi::listMidiDevices(bool output) { - QStringList rv; -#if defined Q_OS_WIN32 - if (output) { - MIDIOUTCAPS outcaps; - for (unsigned int i = 0; i < midiOutGetNumDevs(); i++) { - midiOutGetDevCaps(i, &outcaps, sizeof(MIDIINCAPS)); - rv.append(outcaps.szPname); - } - } else { - MIDIINCAPS incaps; - for (unsigned int i = 0; i < midiInGetNumDevs(); i++) { - midiInGetDevCaps(i, &incaps, sizeof(MIDIINCAPS)); - rv.append(incaps.szPname); - } - } -#endif - return rv; -} - -void Midi::unblockMidiDevice(QString name, bool output) { - if (output) { - for (unsigned long i = 0; i < midiOutExclude.size(); i++) { - if (midiOutExclude[i].toStdString().compare(name.toStdString()) == 0) { - midiOutExclude.erase(midiOutExclude.begin() + i); - break; - } - } - } else { - for (unsigned long i = 0; i < midiInExclude.size(); i++) { - if (midiInExclude[i].toStdString().compare(name.toStdString()) == 0) { - midiInExclude.erase(midiInExclude.begin() + i); - break; - } - } - } -} - -void Midi::blockMidiDevice(QString name, bool output) { - unblockMidiDevice(name, output); // make sure it's only in there once - if (output) { - midiOutExclude.push_back(name); - } else { - midiInExclude.push_back(name); - } -} - -void Midi::thruModeEnable(bool enable) { - thruModeEnabled = enable; -} - -void Midi::broadcastEnable(bool enable) { - broadcastEnabled = enable; -} - -void Midi::typeNoteOffEnable(bool enable) { - typeNoteOffEnabled = enable; -} - -void Midi::typeNoteOnEnable(bool enable) { - typeNoteOnEnabled = enable; -} - -void Midi::typePolyKeyPressureEnable(bool enable) { - typePolyKeyPressureEnabled = enable; -} - -void Midi::typeControlChangeEnable(bool enable) { - typeControlChangeEnabled = enable; -} - -void Midi::typeProgramChangeEnable(bool enable) { - typeProgramChangeEnabled = enable; -} - -void Midi::typeChanPressureEnable(bool enable) { - typeChanPressureEnabled = enable; -} - -void Midi::typePitchBendEnable(bool enable) { - typePitchBendEnabled = enable; -} - -void Midi::typeSystemMessageEnable(bool enable) { - typeSystemMessageEnabled = enable; -} +// +// Midi.cpp +// libraries/midi/src +// +// Created by Burt Sloane +// Modified by Bruce Brown +// 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 +// + +#include "Midi.h" + + +#include + +#if defined Q_OS_WIN32 +#include "Windows.h" +#endif + +#if defined Q_OS_WIN32 +const int MIDI_BYTE_MASK = 0x0FF; +const int MIDI_NIBBLE_MASK = 0x00F; +const int MIDI_PITCH_BEND_MASK = 0x3F80; +const int MIDI_SHIFT_STATUS = 4; +const int MIDI_SHIFT_NOTE = 8;- +const int MIDI_SHIFT_VELOCITY = 16; +const int MIDI_SHIFT_PITCH_BEND = 9; +// Status Decode +const int MIDI_NOTE_OFF = 0x8; +const int MIDI_NOTE_ON = 0x9; +const int MIDI_POLYPHONIC_KEY_PRESSURE = 0xa; +const int MIDI_PROGRAM_CHANGE = 0xc; +const int MIDI_CHANNEL_PRESSURE = 0xd; +const int MIDI_PITCH_BEND_CHANGE = 0xe; +const int MIDI_SYSTEM_MESSAGE = 0xf; +#endif + +const int MIDI_CONTROL_CHANGE = 0xb; +const int MIDI_CHANNEL_MODE_ALL_NOTES_OFF = 0x07b; + +static Midi* instance = NULL; // communicate this to non-class callbacks +static bool thruModeEnabled = false; +static bool broadcastEnabled = false; +static bool typeNoteOffEnabled = true; +static bool typeNoteOnEnabled = true; +static bool typePolyKeyPressureEnabled = false; +static bool typeControlChangeEnabled = true; +static bool typeProgramChangeEnabled = true; +static bool typeChanPressureEnabled = false; +static bool typePitchBendEnabled = true; +static bool typeSystemMessageEnabled = false; + +std::vector Midi::midiInExclude; +std::vector Midi::midiOutExclude; + +#if defined Q_OS_WIN32 + +#pragma comment(lib, "Winmm.lib") + +// +std::vector midihin; +std::vector midihout; + +void CALLBACK MidiInProc(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { + switch (wMsg) { + case MIM_OPEN: + // message not used + break; + case MIM_CLOSE: + for (int i = 0; i < midihin.size(); i++) { + if (midihin[i] == hMidiIn) { + midihin[i] = NULL; + instance->allNotesOff(); + instance->midiHardwareChange(); + } + } + break; + case MIM_DATA: { + int device = -1; + for (int i = 0; i < midihin.size(); i++) { + if (midihin[i] == hMidiIn) { + device = i; + } + } + int raw = dwParam1; + int channel = (MIDI_NIBBLE_MASK & dwParam1) + 1; + int status = MIDI_BYTE_MASK & dwParam1; + int type = MIDI_NIBBLE_MASK & (dwParam1 >> MIDI_SHIFT_STATUS); + int note = MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_NOTE); + int velocity = MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_VELOCITY); + int bend = 0; + int program = 0; + if (!typeNoteOffEnabled && type == MIDI_NOTE_OFF) { + return; + } + if (!typeNoteOnEnabled && type == MIDI_NOTE_ON) { + return; + } + if (!typePolyKeyPressureEnabled && type == MIDI_POLYPHONIC_KEY_PRESSURE) { + return; + } + if (!typeControlChangeEnabled && type == MIDI_CONTROL_CHANGE) { + return; + } + if (typeProgramChangeEnabled && type == MIDI_PROGRAM_CHANGE) { + program = note; + note = 0; + } + if (typeChanPressureEnabled && type == MIDI_CHANNEL_PRESSURE) { + velocity = note; + note = 0; + } + if (typePitchBendEnabled && type == MIDI_PITCH_BEND_CHANGE) { + bend = ((MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_NOTE)) | + (MIDI_PITCH_BEND_MASK & (dwParam1 >> MIDI_SHIFT_PITCH_BEND))) - 8192; + channel = 0; // Weird values on different instruments + note = 0; + velocity = 0; + } + if (!typeSystemMessageEnabled && type == MIDI_SYSTEM_MESSAGE) { + return; + } + if (thruModeEnabled) { + instance->sendNote(status, note, velocity); // relay the message on to all other midi devices. + } + instance->midiReceived(device, raw, channel, status, type, note, velocity, bend, program); // notify the javascript + break; + } + } +} + +void CALLBACK MidiOutProc(HMIDIOUT hmo, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { + switch (wMsg) { + case MOM_OPEN: + // message not used + break; + case MOM_CLOSE: + for (int i = 0; i < midihout.size(); i++) { + if (midihout[i] == hmo) { + midihout[i] = NULL; + instance->allNotesOff(); + instance->midiHardwareChange(); + } + } + break; + } +} + +void Midi::sendRawMessage(int device, int raw) { + if (broadcastEnabled) { + for (int i = 0; i < midihout.size(); i++) { + if (midihout[i] != NULL) { + midiOutShortMsg(midihout[i], raw); + } + } + } else { + midiOutShortMsg(midihout[device], raw); + } +} + +void Midi::sendMessage(int device, int channel, int type, int note, int velocity) { + int message = (channel - 1) | (type << MIDI_SHIFT_STATUS); + if (broadcastEnabled) { + for (int i = 1; i < midihout.size(); i++) { // Skip 0 (Microsoft GS Wavetable Synth) + if (midihout[i] != NULL) { + midiOutShortMsg(midihout[i], message | (note << MIDI_SHIFT_NOTE) | (velocity << MIDI_SHIFT_VELOCITY)); + } + } + } else { + midiOutShortMsg(midihout[device], message | (note << MIDI_SHIFT_NOTE) | (velocity << MIDI_SHIFT_VELOCITY)); + } +} + +void Midi::sendNote(int status, int note, int velocity) { + for (int i = 1; i < midihout.size(); i++) { // Skip 0 (Microsoft GS Wavetable Synth) + if (midihout[i] != NULL) { + midiOutShortMsg(midihout[i], status + (note << MIDI_SHIFT_NOTE) + (velocity << MIDI_SHIFT_VELOCITY)); + } + } +} + +void Midi::MidiSetup() { + midihin.clear(); + midihout.clear(); + + MIDIINCAPS incaps; + for (unsigned int i = 0; i < midiInGetNumDevs(); i++) { + midiInGetDevCaps(i, &incaps, sizeof(MIDIINCAPS)); + + bool found = false; + for (int j = 0; j < midiInExclude.size(); j++) { + if (midiInExclude[j].toStdString().compare(incaps.szPname) == 0) { + found = true; + break; + } + } + if (!found) { // EXCLUDE AN INPUT BY NAME + HMIDIIN tmphin; + midiInOpen(&tmphin, i, (DWORD_PTR)MidiInProc, NULL, CALLBACK_FUNCTION); + midiInStart(tmphin); + midihin.push_back(tmphin); + } + } + + MIDIOUTCAPS outcaps; + for (unsigned int i = 0; i < midiOutGetNumDevs(); i++) { + midiOutGetDevCaps(i, &outcaps, sizeof(MIDIINCAPS)); + + bool found = false; + for (int j = 0; j < midiOutExclude.size(); j++) { + if (midiOutExclude[j].toStdString().compare(outcaps.szPname) == 0) { + found = true; + break; + } + } + if (!found) { // EXCLUDE AN OUTPUT BY NAME + HMIDIOUT tmphout; + midiOutOpen(&tmphout, i, (DWORD_PTR)MidiOutProc, NULL, CALLBACK_FUNCTION); + midihout.push_back(tmphout); + } + } + + allNotesOff(); +} + +void Midi::MidiCleanup() { + allNotesOff(); + + for (int i = 0; i < midihin.size(); i++) { + if (midihin[i] != NULL) { + midiInStop(midihin[i]); + midiInClose(midihin[i]); + } + } + for (int i = 0; i < midihout.size(); i++) { + if (midihout[i] != NULL) { + midiOutClose(midihout[i]); + } + } + midihin.clear(); + midihout.clear(); +} +#else +void Midi::sendRawMessage(int device, int raw) { +} + +void Midi::sendNote(int status, int note, int velocity) { +} + +void Midi::sendMessage(int device, int channel, int type, int note, int velocity){ +} + +void Midi::MidiSetup() { + allNotesOff(); +} + +void Midi::MidiCleanup() { + allNotesOff(); +} +#endif + +void Midi::midiReceived(int device, int raw, int channel, int status, int type, int note, int velocity, int bend, int program) { + QVariantMap eventData; + eventData["device"] = device; + eventData["raw"] = raw; + eventData["channel"] = channel; + eventData["status"] = status; + eventData["type"] = type; + eventData["note"] = note; + eventData["velocity"] = velocity; + eventData["bend"] = bend; + eventData["program"] = program; + emit midiNote(eventData);// Legacy + emit midiMessage(eventData); +} + +void Midi::midiHardwareChange() { + emit midiReset(); +} +// + +Midi::Midi() { + instance = this; + MidiSetup(); +} + +Midi::~Midi() { +} + +void Midi::sendRawDword(int device, int raw) { + sendRawMessage(device, raw); +} + +void Midi::playMidiNote(int status, int note, int velocity) { + sendNote(status, note, velocity); +} + +void Midi::sendMidiMessage(int device, int channel, int type, int note, int velocity) { + sendMessage(device, channel, type, note, velocity); +} + +void Midi::allNotesOff() { + sendNote(MIDI_CONTROL_CHANGE, MIDI_CHANNEL_MODE_ALL_NOTES_OFF, 0); // all notes off +} + +void Midi::resetDevices() { + MidiCleanup(); + MidiSetup(); +} + +void Midi::USBchanged() { + instance->MidiCleanup(); + instance->MidiSetup(); + instance->midiHardwareChange(); +} + +// + +QStringList Midi::listMidiDevices(bool output) { + QStringList rv; +#if defined Q_OS_WIN32 + if (output) { + MIDIOUTCAPS outcaps; + for (unsigned int i = 0; i < midiOutGetNumDevs(); i++) { + midiOutGetDevCaps(i, &outcaps, sizeof(MIDIINCAPS)); + rv.append(outcaps.szPname); + } + } else { + MIDIINCAPS incaps; + for (unsigned int i = 0; i < midiInGetNumDevs(); i++) { + midiInGetDevCaps(i, &incaps, sizeof(MIDIINCAPS)); + rv.append(incaps.szPname); + } + } +#endif + return rv; +} + +void Midi::unblockMidiDevice(QString name, bool output) { + if (output) { + for (unsigned long i = 0; i < midiOutExclude.size(); i++) { + if (midiOutExclude[i].toStdString().compare(name.toStdString()) == 0) { + midiOutExclude.erase(midiOutExclude.begin() + i); + break; + } + } + } else { + for (unsigned long i = 0; i < midiInExclude.size(); i++) { + if (midiInExclude[i].toStdString().compare(name.toStdString()) == 0) { + midiInExclude.erase(midiInExclude.begin() + i); + break; + } + } + } +} + +void Midi::blockMidiDevice(QString name, bool output) { + unblockMidiDevice(name, output); // make sure it's only in there once + if (output) { + midiOutExclude.push_back(name); + } else { + midiInExclude.push_back(name); + } +} + +void Midi::thruModeEnable(bool enable) { + thruModeEnabled = enable; +} + +void Midi::broadcastEnable(bool enable) { + broadcastEnabled = enable; +} + +void Midi::typeNoteOffEnable(bool enable) { + typeNoteOffEnabled = enable; +} + +void Midi::typeNoteOnEnable(bool enable) { + typeNoteOnEnabled = enable; +} + +void Midi::typePolyKeyPressureEnable(bool enable) { + typePolyKeyPressureEnabled = enable; +} + +void Midi::typeControlChangeEnable(bool enable) { + typeControlChangeEnabled = enable; +} + +void Midi::typeProgramChangeEnable(bool enable) { + typeProgramChangeEnabled = enable; +} + +void Midi::typeChanPressureEnable(bool enable) { + typeChanPressureEnabled = enable; +} + +void Midi::typePitchBendEnable(bool enable) { + typePitchBendEnabled = enable; +} + +void Midi::typeSystemMessageEnable(bool enable) { + typeSystemMessageEnabled = enable; +} From fbfa5025474f6ee57031098fb13c28983157805c Mon Sep 17 00:00:00 2001 From: Bruce Brown <1st-BrainStormer@users.noreply.github.com> Date: Fri, 19 Jan 2018 12:08:37 -0800 Subject: [PATCH 35/52] Delete Midi.h --- Midi.h | 108 --------------------------------------------------------- 1 file changed, 108 deletions(-) delete mode 100644 Midi.h diff --git a/Midi.h b/Midi.h deleted file mode 100644 index f7940bbe5d..0000000000 --- a/Midi.h +++ /dev/null @@ -1,108 +0,0 @@ -// -// Midi.h -// libraries/midi/src -// -// Created by Burt Sloane -// Modified by Bruce Brown -// 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 -// - -#ifndef hifi_Midi_h -#define hifi_Midi_h - -#include -#include -#include - -#include -#include - -class Midi : public QObject, public Dependency { - Q_OBJECT - SINGLETON_DEPENDENCY - -public: - void midiReceived(int device, int raw, int channel, int status, int type, int note, int velocity, int bend, int program); // relay a note to Javascript - void midiHardwareChange(); // relay hardware change to Javascript - void sendRawMessage(int device, int raw); // relay midi message to MIDI outputs - void sendNote(int status, int note, int velocity); // relay a note to MIDI outputs - void sendMessage(int device, int channel, int type, int note, int velocity); // relay a message to MIDI outputs - static void USBchanged(); - -private: - static std::vector midiInExclude; - static std::vector midiOutExclude; - -private: - void MidiSetup(); - void MidiCleanup(); - -signals: - void midiNote(QVariantMap eventData); - void midiMessage(QVariantMap eventData); - void midiReset(); - - public slots: - // Send Raw Midi Packet to all connected devices - Q_INVOKABLE void sendRawDword(int device, int raw); - /// Send Raw Midi message to selected device - /// @param {int} device: device number - /// @param {int} raw: raw midi message (DWORD) - - // Send Midi Message to all connected devices - Q_INVOKABLE void sendMidiMessage(int device, int channel, int type, int note, int velocity); - /// Send midi message to selected device/devices - /// @param {int} device: device number - /// @param {int} channel: channel number - /// @param {int} type: 0x8 is noteoff, 0x9 is noteon (if velocity=0, noteoff), etc - /// @param {int} note: midi note number - /// @param {int} velocity: note velocity (0 means noteoff) - - // Send Midi Message to all connected devices - Q_INVOKABLE void playMidiNote(int status, int note, int velocity); - /// play a note on all connected devices - /// @param {int} status: 0x80 is noteoff, 0x90 is noteon (if velocity=0, noteoff), etc - /// @param {int} note: midi note number - /// @param {int} velocity: note velocity (0 means noteoff) - - /// turn off all notes on all connected devices - Q_INVOKABLE void allNotesOff(); - - /// clean up and re-discover attached devices - Q_INVOKABLE void resetDevices(); - - /// ask for a list of inputs/outputs - Q_INVOKABLE QStringList listMidiDevices(bool output); - - /// block an input/output by name - Q_INVOKABLE void blockMidiDevice(QString name, bool output); - - /// unblock an input/output by name - Q_INVOKABLE void unblockMidiDevice(QString name, bool output); - - /// repeat all incoming notes to all outputs (default disabled) - Q_INVOKABLE void thruModeEnable(bool enable); - - /// broadcast on all unblocked devices - Q_INVOKABLE void broadcastEnable(bool enable); - - /// filter by event types - Q_INVOKABLE void typeNoteOffEnable(bool enable); - Q_INVOKABLE void typeNoteOnEnable(bool enable); - Q_INVOKABLE void typePolyKeyPressureEnable(bool enable); - Q_INVOKABLE void typeControlChangeEnable(bool enable); - Q_INVOKABLE void typeProgramChangeEnable(bool enable); - Q_INVOKABLE void typeChanPressureEnable(bool enable); - Q_INVOKABLE void typePitchBendEnable(bool enable); - Q_INVOKABLE void typeSystemMessageEnable(bool enable); - - -public: - Midi(); - virtual ~Midi(); -}; - -#endif // hifi_Midi_h From e3fb9cee00c9dc3883bef05e19687499582e70b0 Mon Sep 17 00:00:00 2001 From: Bruce Brown <1st-BrainStormer@users.noreply.github.com> Date: Fri, 19 Jan 2018 12:09:07 -0800 Subject: [PATCH 36/52] Delete Midi.cpp --- Midi.cpp | 406 ------------------------------------------------------- 1 file changed, 406 deletions(-) delete mode 100644 Midi.cpp diff --git a/Midi.cpp b/Midi.cpp deleted file mode 100644 index ec13508805..0000000000 --- a/Midi.cpp +++ /dev/null @@ -1,406 +0,0 @@ -// -// Midi.cpp -// libraries/midi/src -// -// Created by Burt Sloane -// Modified by Bruce Brown -// 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 -// - -#include "Midi.h" - - -#include - -#if defined Q_OS_WIN32 -#include "Windows.h" -#endif - -#if defined Q_OS_WIN32 -const int MIDI_BYTE_MASK = 0x0FF; -const int MIDI_NIBBLE_MASK = 0x00F; -const int MIDI_PITCH_BEND_MASK = 0x3F80; -const int MIDI_SHIFT_STATUS = 4; -const int MIDI_SHIFT_NOTE = 8; -const int MIDI_SHIFT_VELOCITY = 16; -const int MIDI_SHIFT_PITCH_BEND = 9; -// Status Decode -const int MIDI_NOTE_OFF = 0x8; -const int MIDI_NOTE_ON = 0x9; -const int MIDI_POLYPHONIC_KEY_PRESSURE = 0xa; -const int MIDI_PROGRAM_CHANGE = 0xc; -const int MIDI_CHANNEL_PRESSURE = 0xd; -const int MIDI_PITCH_BEND_CHANGE = 0xe; -const int MIDI_SYSTEM_MESSAGE = 0xf; -#endif - -const int MIDI_CONTROL_CHANGE = 0xb; -const int MIDI_CHANNEL_MODE_ALL_NOTES_OFF = 0x07b; - -static Midi* instance = NULL; // communicate this to non-class callbacks -static bool thruModeEnabled = false; -static bool broadcastEnabled = false; -static bool typeNoteOffEnabled = true; -static bool typeNoteOnEnabled = true; -static bool typePolyKeyPressureEnabled = false; -static bool typeControlChangeEnabled = true; -static bool typeProgramChangeEnabled = true; -static bool typeChanPressureEnabled = false; -static bool typePitchBendEnabled = true; -static bool typeSystemMessageEnabled = false; - -std::vector Midi::midiInExclude; -std::vector Midi::midiOutExclude; - -#if defined Q_OS_WIN32 - -#pragma comment(lib, "Winmm.lib") - -// -std::vector midihin; -std::vector midihout; - -void CALLBACK MidiInProc(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { - switch (wMsg) { - case MIM_OPEN: - // message not used - break; - case MIM_CLOSE: - for (int i = 0; i < midihin.size(); i++) { - if (midihin[i] == hMidiIn) { - midihin[i] = NULL; - instance->allNotesOff(); - instance->midiHardwareChange(); - } - } - break; - case MIM_DATA: { - int device = -1; - for (int i = 0; i < midihin.size(); i++) { - if (midihin[i] == hMidiIn) { - device = i; - } - } - int raw = dwParam1; - int channel = (MIDI_NIBBLE_MASK & dwParam1) + 1; - int status = MIDI_BYTE_MASK & dwParam1; - int type = MIDI_NIBBLE_MASK & (dwParam1 >> MIDI_SHIFT_STATUS); - int note = MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_NOTE); - int velocity = MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_VELOCITY); - int bend = 0; - int program = 0; - if (!typeNoteOffEnabled && type == MIDI_NOTE_OFF) { - return; - } - if (!typeNoteOnEnabled && type == MIDI_NOTE_ON) { - return; - } - if (!typePolyKeyPressureEnabled && type == MIDI_POLYPHONIC_KEY_PRESSURE) { - return; - } - if (!typeControlChangeEnabled && type == MIDI_CONTROL_CHANGE) { - return; - } - if (typeProgramChangeEnabled && type == MIDI_PROGRAM_CHANGE) { - program = note; - note = 0; - } - if (typeChanPressureEnabled && type == MIDI_CHANNEL_PRESSURE) { - velocity = note; - note = 0; - } - if (typePitchBendEnabled && type == MIDI_PITCH_BEND_CHANGE) { - bend = ((MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_NOTE)) | - (MIDI_PITCH_BEND_MASK & (dwParam1 >> MIDI_SHIFT_PITCH_BEND))) - 8192; - channel = 0; // Weird values on different instruments - note = 0; - velocity = 0; - } - if (!typeSystemMessageEnabled && type == MIDI_SYSTEM_MESSAGE) { - return; - } - if (thruModeEnabled) { - instance->sendNote(status, note, velocity); // relay the message on to all other midi devices. - } - instance->midiReceived(device, raw, channel, status, type, note, velocity, bend, program); // notify the javascript - break; - } - } -} - -void CALLBACK MidiOutProc(HMIDIOUT hmo, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { - switch (wMsg) { - case MOM_OPEN: - // message not used - break; - case MOM_CLOSE: - for (int i = 0; i < midihout.size(); i++) { - if (midihout[i] == hmo) { - midihout[i] = NULL; - instance->allNotesOff(); - instance->midiHardwareChange(); - } - } - break; - } -} - -void Midi::sendRawMessage(int device, int raw) { - if (broadcastEnabled) { - for (int i = 0; i < midihout.size(); i++) { - if (midihout[i] != NULL) { - midiOutShortMsg(midihout[i], raw); - } - } - } else { - midiOutShortMsg(midihout[device], raw); - } -} - -void Midi::sendMessage(int device, int channel, int type, int note, int velocity) { - int message = (channel - 1) | (type << MIDI_SHIFT_STATUS); - if (broadcastEnabled) { - for (int i = 1; i < midihout.size(); i++) { // Skip 0 (Microsoft GS Wavetable Synth) - if (midihout[i] != NULL) { - midiOutShortMsg(midihout[i], message | (note << MIDI_SHIFT_NOTE) | (velocity << MIDI_SHIFT_VELOCITY)); - } - } - } else { - midiOutShortMsg(midihout[device], message | (note << MIDI_SHIFT_NOTE) | (velocity << MIDI_SHIFT_VELOCITY)); - } -} - -void Midi::sendNote(int status, int note, int velocity) { - for (int i = 1; i < midihout.size(); i++) { // Skip 0 (Microsoft GS Wavetable Synth) - if (midihout[i] != NULL) { - midiOutShortMsg(midihout[i], status + (note << MIDI_SHIFT_NOTE) + (velocity << MIDI_SHIFT_VELOCITY)); - } - } -} - -void Midi::MidiSetup() { - midihin.clear(); - midihout.clear(); - - MIDIINCAPS incaps; - for (unsigned int i = 0; i < midiInGetNumDevs(); i++) { - midiInGetDevCaps(i, &incaps, sizeof(MIDIINCAPS)); - - bool found = false; - for (int j = 0; j < midiInExclude.size(); j++) { - if (midiInExclude[j].toStdString().compare(incaps.szPname) == 0) { - found = true; - break; - } - } - if (!found) { // EXCLUDE AN INPUT BY NAME - HMIDIIN tmphin; - midiInOpen(&tmphin, i, (DWORD_PTR)MidiInProc, NULL, CALLBACK_FUNCTION); - midiInStart(tmphin); - midihin.push_back(tmphin); - } - } - - MIDIOUTCAPS outcaps; - for (unsigned int i = 0; i < midiOutGetNumDevs(); i++) { - midiOutGetDevCaps(i, &outcaps, sizeof(MIDIINCAPS)); - - bool found = false; - for (int j = 0; j < midiOutExclude.size(); j++) { - if (midiOutExclude[j].toStdString().compare(outcaps.szPname) == 0) { - found = true; - break; - } - } - if (!found) { // EXCLUDE AN OUTPUT BY NAME - HMIDIOUT tmphout; - midiOutOpen(&tmphout, i, (DWORD_PTR)MidiOutProc, NULL, CALLBACK_FUNCTION); - midihout.push_back(tmphout); - } - } - - allNotesOff(); -} - -void Midi::MidiCleanup() { - allNotesOff(); - - for (int i = 0; i < midihin.size(); i++) { - if (midihin[i] != NULL) { - midiInStop(midihin[i]); - midiInClose(midihin[i]); - } - } - for (int i = 0; i < midihout.size(); i++) { - if (midihout[i] != NULL) { - midiOutClose(midihout[i]); - } - } - midihin.clear(); - midihout.clear(); -} -#else -void Midi::sendRawMessage(int device, int raw) { -} - -void Midi::sendNote(int status, int note, int velocity) { -} - -void Midi::sendMessage(int device, int channel, int type, int note, int velocity){ -} - -void Midi::MidiSetup() { - allNotesOff(); -} - -void Midi::MidiCleanup() { - allNotesOff(); -} -#endif - -void Midi::midiReceived(int device, int raw, int channel, int status, int type, int note, int velocity, int bend, int program) { - QVariantMap eventData; - eventData["device"] = device; - eventData["raw"] = raw; - eventData["channel"] = channel; - eventData["status"] = status; - eventData["type"] = type; - eventData["note"] = note; - eventData["velocity"] = velocity; - eventData["bend"] = bend; - eventData["program"] = program; - emit midiNote(eventData);// Legacy - emit midiMessage(eventData); -} - -void Midi::midiHardwareChange() { - emit midiReset(); -} -// - -Midi::Midi() { - instance = this; - MidiSetup(); -} - -Midi::~Midi() { -} - -void Midi::sendRawDword(int device, int raw) { - sendRawMessage(device, raw); -} - -void Midi::playMidiNote(int status, int note, int velocity) { - sendNote(status, note, velocity); -} - -void Midi::sendMidiMessage(int device, int channel, int type, int note, int velocity) { - sendMessage(device, channel, type, note, velocity); -} - -void Midi::allNotesOff() { - sendNote(MIDI_CONTROL_CHANGE, MIDI_CHANNEL_MODE_ALL_NOTES_OFF, 0); // all notes off -} - -void Midi::resetDevices() { - MidiCleanup(); - MidiSetup(); -} - -void Midi::USBchanged() { - instance->MidiCleanup(); - instance->MidiSetup(); - instance->midiHardwareChange(); -} - -// - -QStringList Midi::listMidiDevices(bool output) { - QStringList rv; -#if defined Q_OS_WIN32 - if (output) { - MIDIOUTCAPS outcaps; - for (unsigned int i = 0; i < midiOutGetNumDevs(); i++) { - midiOutGetDevCaps(i, &outcaps, sizeof(MIDIINCAPS)); - rv.append(outcaps.szPname); - } - } else { - MIDIINCAPS incaps; - for (unsigned int i = 0; i < midiInGetNumDevs(); i++) { - midiInGetDevCaps(i, &incaps, sizeof(MIDIINCAPS)); - rv.append(incaps.szPname); - } - } -#endif - return rv; -} - -void Midi::unblockMidiDevice(QString name, bool output) { - if (output) { - for (unsigned long i = 0; i < midiOutExclude.size(); i++) { - if (midiOutExclude[i].toStdString().compare(name.toStdString()) == 0) { - midiOutExclude.erase(midiOutExclude.begin() + i); - break; - } - } - } else { - for (unsigned long i = 0; i < midiInExclude.size(); i++) { - if (midiInExclude[i].toStdString().compare(name.toStdString()) == 0) { - midiInExclude.erase(midiInExclude.begin() + i); - break; - } - } - } -} - -void Midi::blockMidiDevice(QString name, bool output) { - unblockMidiDevice(name, output); // make sure it's only in there once - if (output) { - midiOutExclude.push_back(name); - } else { - midiInExclude.push_back(name); - } -} - -void Midi::thruModeEnable(bool enable) { - thruModeEnabled = enable; -} - -void Midi::broadcastEnable(bool enable) { - broadcastEnabled = enable; -} - -void Midi::typeNoteOffEnable(bool enable) { - typeNoteOffEnabled = enable; -} - -void Midi::typeNoteOnEnable(bool enable) { - typeNoteOnEnabled = enable; -} - -void Midi::typePolyKeyPressureEnable(bool enable) { - typePolyKeyPressureEnabled = enable; -} - -void Midi::typeControlChangeEnable(bool enable) { - typeControlChangeEnabled = enable; -} - -void Midi::typeProgramChangeEnable(bool enable) { - typeProgramChangeEnabled = enable; -} - -void Midi::typeChanPressureEnable(bool enable) { - typeChanPressureEnabled = enable; -} - -void Midi::typePitchBendEnable(bool enable) { - typePitchBendEnabled = enable; -} - -void Midi::typeSystemMessageEnable(bool enable) { - typeSystemMessageEnabled = enable; -} From 94e6a68d4197e84b80a0de1b9396d6a7b7bf749e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 20 Jan 2018 09:50:24 +1300 Subject: [PATCH 37/52] Code review --- libraries/pointers/src/Pointer.cpp | 4 +- libraries/pointers/src/Pointer.h | 2 +- .../controllers/controllerDispatcher.js | 55 ++++++------------- 3 files changed, 19 insertions(+), 42 deletions(-) diff --git a/libraries/pointers/src/Pointer.cpp b/libraries/pointers/src/Pointer.cpp index 0e542c47da..8691ab8823 100644 --- a/libraries/pointers/src/Pointer.cpp +++ b/libraries/pointers/src/Pointer.cpp @@ -111,7 +111,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin hoveredEvent.setMoveOnHoverLeave(moveOnHoverLeave); // if shouldHover && !_prevDoHover, only send hoverBegin - if (_enabled && _hover && doHover && !_prevDoHover) { + if (_enabled && doHover && !_prevDoHover) { if (hoveredObject.type == ENTITY) { emit pointerManager->hoverBeginEntity(hoveredObject.objectID, hoveredEvent); } else if (hoveredObject.type == OVERLAY) { @@ -119,7 +119,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin } else if (hoveredObject.type == HUD) { emit pointerManager->hoverBeginHUD(hoveredEvent); } - } else if (_enabled && _hover && doHover) { + } else if (_enabled && doHover) { if (hoveredObject.type == OVERLAY) { if (_prevHoveredObject.type == OVERLAY) { if (hoveredObject.objectID == _prevHoveredObject.objectID) { diff --git a/libraries/pointers/src/Pointer.h b/libraries/pointers/src/Pointer.h index ef0048cf1e..62683cb6e7 100644 --- a/libraries/pointers/src/Pointer.h +++ b/libraries/pointers/src/Pointer.h @@ -62,7 +62,7 @@ public: virtual void setLength(float length) {} virtual void setLockEndUUID(const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) {} - void setDoesHover(bool hover); + virtual void setDoesHover(bool hover); void update(unsigned int pointerID); virtual void updateVisuals(const PickResultPointer& pickResult) = 0; diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index fe18ed25f8..4946a6525e 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -163,6 +163,20 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); } }; + this.updateDoesHover = function(handLaser) { + if (handLaser.doesHover !== undefined) { + if (handLaser.hand === LEFT_HAND + && _this.leftPointerDoesHover !== handLaser.doesHover) { + _this.leftPointerDoesHover = handLaser.doesHover; + _this.leftPointerDoesHoverChanged = true; + } else if (handLaser.hand === RIGHT_HAND + && _this.rightPointerDoesHover !== handLaser.doesHover) { + _this.rightPointerDoesHover = handLaser.doesHover; + _this.rightPointerDoesHoverChanged = true; + } + } + } + this.updateHovering = function () { if (_this.leftPointerDoesHoverChanged) { Pointers.setDoesHover(_this.leftPointer, _this.leftPointerDoesHover); @@ -345,19 +359,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); _this.runningPluginNames[orderedPluginName] = true; _this.markSlots(candidatePlugin, orderedPluginName); _this.pointerManager.makePointerVisible(candidatePlugin.parameters.handLaser); - - if (candidatePlugin.parameters.handLaser.doesHover !== undefined) { - if (candidatePlugin.parameters.handLaser.hand === LEFT_HAND - && _this.leftPointerDoesHover !== candidatePlugin.parameters.handLaser.doesHover) { - _this.leftPointerDoesHover = candidatePlugin.parameters.handLaser.doesHover; - _this.leftPointerDoesHoverChanged = true; - } else if (candidatePlugin.parameters.handLaser.hand === RIGHT_HAND - && _this.rightPointerDoesHover !== candidatePlugin.parameters.handLaser.doesHover) { - _this.rightPointerDoesHover = candidatePlugin.parameters.handLaser.doesHover; - _this.rightPointerDoesHoverChanged = true; - } - } - + _this.updateDoesHover(candidatePlugin.parameters.handLaser); if (DEBUG) { print("controllerDispatcher running " + orderedPluginName); } @@ -387,39 +389,14 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); if (PROFILE) { Script.beginProfileRange("dispatch.run." + runningPluginName); } + _this.updateDoesHover(plugin.parameters.handLaser); var runningness = plugin.run(controllerData, deltaTime); - - if (runningness.active) { - if (plugin.parameters.handLaser.doesHover !== undefined) { - if (plugin.parameters.handLaser.hand === LEFT_HAND - && _this.leftPointerDoesHover !== plugin.parameters.handLaser.doesHover) { - _this.leftPointerDoesHover = plugin.parameters.handLaser.doesHover; - _this.leftPointerDoesHoverChanged = true; - } else if (plugin.parameters.handLaser.hand === RIGHT_HAND - && _this.rightPointerDoesHover !== plugin.parameters.handLaser.doesHover) { - _this.rightPointerDoesHover = plugin.parameters.handLaser.doesHover; - _this.rightPointerDoesHoverChanged = true; - } - } - } - if (!runningness.active) { // plugin is finished running, for now. remove it from the list // of running plugins and mark its activity-slots as "not in use" delete _this.runningPluginNames[runningPluginName]; _this.markSlots(plugin, false); _this.pointerManager.makePointerInvisible(plugin.parameters.handLaser); - - if (plugin.parameters.handLaser.doesHover !== undefined) { - if (plugin.parameters.handLaser.hand === LEFT_HAND && !_this.leftPointerDoesHover) { - _this.leftPointerDoesHover = true; - _this.leftPointerDoesHoverChanged = true; - } else if (plugin.parameters.handLaser.hand === RIGHT_HAND && !_this.rightPointerDoesHover) { - _this.rightPointerDoesHover = true; - _this.rightPointerDoesHoverChanged = true; - } - } - if (DEBUG) { print("controllerDispatcher stopping " + runningPluginName); } From 24dcc8788cd671e99ff8ecdd05f2c4eb087d6ae8 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 20 Jan 2018 10:07:23 +1300 Subject: [PATCH 38/52] Code review --- libraries/pointers/src/Pointer.cpp | 3 +-- libraries/pointers/src/Pointer.h | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/libraries/pointers/src/Pointer.cpp b/libraries/pointers/src/Pointer.cpp index 8691ab8823..287d5a3c97 100644 --- a/libraries/pointers/src/Pointer.cpp +++ b/libraries/pointers/src/Pointer.cpp @@ -236,7 +236,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin } // if we disable the pointer or disable hovering, send hoverEnd events after triggerEnd - if ((_hover || _prevHover) && ((!_enabled && _prevEnabled) || (!doHover && _prevDoHover))) { + if ((!_enabled && _prevEnabled) || (!doHover && _prevDoHover)) { if (_prevHoveredObject.type == ENTITY) { emit pointerManager->hoverEndEntity(_prevHoveredObject.objectID, hoveredEvent); } else if (_prevHoveredObject.type == OVERLAY) { @@ -249,7 +249,6 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin _prevHoveredObject = hoveredObject; _prevButtons = buttons; _prevEnabled = _enabled; - _prevHover = _hover; _prevDoHover = doHover; } diff --git a/libraries/pointers/src/Pointer.h b/libraries/pointers/src/Pointer.h index 62683cb6e7..9fd434fb15 100644 --- a/libraries/pointers/src/Pointer.h +++ b/libraries/pointers/src/Pointer.h @@ -99,7 +99,6 @@ private: PickedObject _prevHoveredObject; Buttons _prevButtons; bool _prevEnabled { false }; - bool _prevHover { false }; bool _prevDoHover { false }; std::unordered_map _triggeredObjects; From fab13a111c8a15213597712aabe110463b8bbca4 Mon Sep 17 00:00:00 2001 From: Bruce Brown <1st-BrainStormer@users.noreply.github.com> Date: Fri, 19 Jan 2018 13:10:34 -0800 Subject: [PATCH 39/52] Remove accidental - --- libraries/midi/src/Midi.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/midi/src/Midi.cpp b/libraries/midi/src/Midi.cpp index 7f086340ba..1f1d581e48 100644 --- a/libraries/midi/src/Midi.cpp +++ b/libraries/midi/src/Midi.cpp @@ -24,7 +24,7 @@ const int MIDI_BYTE_MASK = 0x0FF; const int MIDI_NIBBLE_MASK = 0x00F; const int MIDI_PITCH_BEND_MASK = 0x3F80; const int MIDI_SHIFT_STATUS = 4; -const int MIDI_SHIFT_NOTE = 8;- +const int MIDI_SHIFT_NOTE = 8; const int MIDI_SHIFT_VELOCITY = 16; const int MIDI_SHIFT_PITCH_BEND = 9; // Status Decode From 7898aa2c4b0f6ef879b02ac285dc5afe3d0cecf8 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 20 Jan 2018 10:32:42 +1300 Subject: [PATCH 40/52] Code review --- .../controllers/controllerDispatcher.js | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 4946a6525e..a8658933e7 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -163,15 +163,13 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); } }; - this.updateDoesHover = function(handLaser) { + this.updateDoesHover = function(handLaser, doesHover) { if (handLaser.doesHover !== undefined) { - if (handLaser.hand === LEFT_HAND - && _this.leftPointerDoesHover !== handLaser.doesHover) { - _this.leftPointerDoesHover = handLaser.doesHover; + if (handLaser.hand === LEFT_HAND && _this.leftPointerDoesHover !== doesHover) { + _this.leftPointerDoesHover = doesHover; _this.leftPointerDoesHoverChanged = true; - } else if (handLaser.hand === RIGHT_HAND - && _this.rightPointerDoesHover !== handLaser.doesHover) { - _this.rightPointerDoesHover = handLaser.doesHover; + } else if (handLaser.hand === RIGHT_HAND && _this.rightPointerDoesHover !== doesHover) { + _this.rightPointerDoesHover = doesHover; _this.rightPointerDoesHoverChanged = true; } } @@ -359,7 +357,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); _this.runningPluginNames[orderedPluginName] = true; _this.markSlots(candidatePlugin, orderedPluginName); _this.pointerManager.makePointerVisible(candidatePlugin.parameters.handLaser); - _this.updateDoesHover(candidatePlugin.parameters.handLaser); + _this.updateDoesHover(candidatePlugin.parameters.handLaser, + candidatePlugin.parameters.handLaser.doesHover); if (DEBUG) { print("controllerDispatcher running " + orderedPluginName); } @@ -389,14 +388,16 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); if (PROFILE) { Script.beginProfileRange("dispatch.run." + runningPluginName); } - _this.updateDoesHover(plugin.parameters.handLaser); var runningness = plugin.run(controllerData, deltaTime); - if (!runningness.active) { + if (runningness.active) { + _this.updateDoesHover(plugin.parameters.handLaser, plugin.parameters.handLaser.doesHover); + } else { // plugin is finished running, for now. remove it from the list // of running plugins and mark its activity-slots as "not in use" delete _this.runningPluginNames[runningPluginName]; _this.markSlots(plugin, false); _this.pointerManager.makePointerInvisible(plugin.parameters.handLaser); + _this.updateDoesHover(plugin.parameters.handLaser, true); if (DEBUG) { print("controllerDispatcher stopping " + runningPluginName); } From 41f3b792b62af366b47c35049f8b72f531c4fdb0 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 19 Jan 2018 14:34:52 -0700 Subject: [PATCH 41/52] Fix warnings --- libraries/ui/src/ui/TabletScriptingInterface.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index 33ff9b06f7..2b89045246 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -83,7 +83,8 @@ TabletButtonProxy* TabletButtonListModel::addButton(const QVariant& properties) int insertButtonUsingIndex = computeNewButtonIndex(newTabletButtonProperties); auto newTabletButtonProxy = QSharedPointer(new TabletButtonProxy(newTabletButtonProperties)); beginResetModel(); - if (insertButtonUsingIndex < _buttons.size()) { + int buttonCount = (int)_buttons.size(); + if (insertButtonUsingIndex < buttonCount) { _buttons.insert(_buttons.begin() + insertButtonUsingIndex, newTabletButtonProxy); } else { _buttons.push_back(newTabletButtonProxy); From 4d408a8df5c6a18b683f25e6234be505e7f53f53 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 19 Jan 2018 14:49:28 -0800 Subject: [PATCH 42/52] Nearby UI --- .../commerce/wallet/sendMoney/SendMoney.qml | 219 ++++++++---------- .../sendMoney/images/p2p-nearby-selected.svg | 27 +++ .../images/p2p-nearby-unselected.svg | 30 +++ 3 files changed, 156 insertions(+), 120 deletions(-) create mode 100644 interface/resources/qml/hifi/commerce/wallet/sendMoney/images/p2p-nearby-selected.svg create mode 100644 interface/resources/qml/hifi/commerce/wallet/sendMoney/images/p2p-nearby-unselected.svg diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml b/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml index ed959333d7..55fca14c24 100644 --- a/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml +++ b/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml @@ -553,163 +553,142 @@ Item { } } - Item { - id: selectionInstructionsContainer; - visible: chooseRecipientNearby.selectedRecipient === ""; - anchors.fill: parent; - - RalewaySemiBold { - id: selectionInstructions_deselected; - text: "Click/trigger on an avatar nearby to select them..."; - // Anchors - anchors.bottom: parent.bottom; - anchors.bottomMargin: 200; - anchors.left: parent.left; - anchors.leftMargin: 58; - anchors.right: parent.right; - anchors.rightMargin: anchors.leftMargin; - height: paintedHeight; - // Text size - size: 20; - // Style - color: hifi.colors.baseGray; - horizontalAlignment: Text.AlignHCenter; - wrapMode: Text.Wrap; - } + RalewaySemiBold { + id: selectionInstructions; + text: chooseRecipientNearby.selectedRecipient === "" ? "Trigger or click on\nsomeone nearby to select them" : + "Trigger or click on\nsomeone else to select again"; + // Anchors + anchors.top: parent.top; + anchors.topMargin: 100; + anchors.left: parent.left; + anchors.leftMargin: 20; + anchors.right: parent.right; + anchors.rightMargin: anchors.leftMargin; + height: paintedHeight; + // Text size + size: 20; + // Style + color: hifi.colors.baseGray; + horizontalAlignment: Text.AlignHCenter; + wrapMode: Text.Wrap; } - Item { + Image { + anchors.top: selectionInstructions.bottom; + anchors.topMargin: 20; + anchors.bottom: selectionMadeContainer.top; + anchors.bottomMargin: 30; + source: "./images/p2p-nearby-unselected.svg"; + width: parent.width; + fillMode: Image.PreserveAspectFit; + horizontalAlignment: Image.AlignHCenter; + verticalAlignment: Image.AlignVCenter; + mipmap: true; + } + + Rectangle { id: selectionMadeContainer; - visible: !selectionInstructionsContainer.visible; - anchors.fill: parent; + visible: chooseRecipientNearby.selectedRecipient !== ""; + anchors.bottom: parent.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + height: 190; + color: "#F3F3F3"; + radius: 8; + + // Used to square off the top left and top right edges of this container + Rectangle { + color: "#F3F3F3"; + height: selectionMadeContainer.radius; + anchors.top: selectionMadeContainer.top; + anchors.left: selectionMadeContainer.left; + anchors.right: selectionMadeContainer.right; + } RalewaySemiBold { id: sendToText; - text: "Send To:"; + text: "Send to:"; // Anchors anchors.top: parent.top; - anchors.topMargin: 120; + anchors.topMargin: 36; anchors.left: parent.left; - anchors.leftMargin: 12; + anchors.leftMargin: 36; width: paintedWidth; height: paintedHeight; // Text size - size: 20; + size: 18; // Style color: hifi.colors.baseGray; } + Image { + id: selectedImage; + anchors.top: parent.top; + anchors.topMargin: 24; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 32; + anchors.left: sendToText.right; + anchors.leftMargin: 4; + source: "./images/p2p-nearby-selected.svg"; + width: 50; + fillMode: Image.PreserveAspectFit; + horizontalAlignment: Image.AlignHCenter; + verticalAlignment: Image.AlignVCenter; + mipmap: true; + } + RalewaySemiBold { id: avatarDisplayName; text: '"' + AvatarList.getAvatar(chooseRecipientNearby.selectedRecipient).sessionDisplayName + '"'; // Anchors - anchors.top: sendToText.bottom; - anchors.topMargin: 60; - anchors.left: parent.left; - anchors.leftMargin: 30; + anchors.top: parent.top; + anchors.topMargin: 34; + anchors.left: selectedImage.right; + anchors.leftMargin: 10; anchors.right: parent.right; - anchors.rightMargin: 30; + anchors.rightMargin: 10; height: paintedHeight; // Text size - size: 22; + size: 20; // Style - horizontalAlignment: Text.AlignHCenter; - color: hifi.colors.baseGray; - } - - RalewaySemiBold { - id: avatarNodeID; - text: chooseRecipientNearby.selectedRecipient; - // Anchors - anchors.top: avatarDisplayName.bottom; - anchors.topMargin: 6; - anchors.left: parent.left; - anchors.leftMargin: 30; - anchors.right: parent.right; - anchors.rightMargin: 30; - height: paintedHeight; - // Text size - size: 14; - // Style - horizontalAlignment: Text.AlignHCenter; - color: hifi.colors.lightGrayText; + color: hifi.colors.blueAccent; } RalewaySemiBold { id: avatarUserName; text: sendMoneyStep.selectedRecipientUserName; // Anchors - anchors.top: avatarNodeID.bottom; - anchors.topMargin: 12; - anchors.left: parent.left; - anchors.leftMargin: 30; + anchors.top: avatarDisplayName.bottom; + anchors.topMargin: 16; + anchors.left: selectedImage.right; + anchors.leftMargin: 10; anchors.right: parent.right; - anchors.rightMargin: 30; + anchors.rightMargin: 10; height: paintedHeight; // Text size - size: 22; + size: 18; // Style - horizontalAlignment: Text.AlignHCenter; color: hifi.colors.baseGray; } - RalewaySemiBold { - id: selectionInstructions_selected; - text: "Click/trigger on another avatar nearby to select them...\n\nor press 'Next' to continue."; - // Anchors + // "CHOOSE" button + HifiControlsUit.Button { + id: chooseButton; + color: hifi.buttons.blue; + colorScheme: hifi.colorSchemes.dark; + anchors.horizontalCenter: parent.horizontalCenter; anchors.bottom: parent.bottom; - anchors.bottomMargin: 200; - anchors.left: parent.left; - anchors.leftMargin: 58; - anchors.right: parent.right; - anchors.rightMargin: anchors.leftMargin; - height: paintedHeight; - // Text size - size: 20; - // Style - color: hifi.colors.baseGray; - horizontalAlignment: Text.AlignHCenter; - wrapMode: Text.Wrap; - } - } + anchors.bottomMargin: 20; + height: 40; + width: 110; + text: "CHOOSE"; + onClicked: { + sendMoneyStep.referrer = "nearby"; + sendMoneyStep.selectedRecipientNodeID = chooseRecipientNearby.selectedRecipient; + chooseRecipientNearby.selectedRecipient = ""; - // "Cancel" button - HifiControlsUit.Button { - id: cancelButton; - color: hifi.buttons.noneBorderless; - colorScheme: hifi.colorSchemes.dark; - anchors.left: parent.left; - anchors.leftMargin: 60; - anchors.bottom: parent.bottom; - anchors.bottomMargin: 80; - height: 50; - width: 120; - text: "Cancel"; - onClicked: { - root.nextActiveView = "sendMoneyHome"; - resetSendMoneyData(); - } - } - - // "Next" button - HifiControlsUit.Button { - id: nextButton; - enabled: chooseRecipientNearby.selectedRecipient !== ""; - color: hifi.buttons.blue; - colorScheme: hifi.colorSchemes.dark; - anchors.right: parent.right; - anchors.rightMargin: 60; - anchors.bottom: parent.bottom; - anchors.bottomMargin: 80; - height: 50; - width: 120; - text: "Next"; - onClicked: { - sendMoneyStep.referrer = "nearby"; - sendMoneyStep.selectedRecipientNodeID = chooseRecipientNearby.selectedRecipient; - chooseRecipientNearby.selectedRecipient = ""; - - root.nextActiveView = "sendMoneyStep"; + root.nextActiveView = "sendMoneyStep"; + } } } } @@ -790,7 +769,7 @@ Item { HifiControlsUit.Button { id: changeButton; color: hifi.buttons.none; - colorScheme: hifi.colorSchemes.white; + colorScheme: hifi.colorSchemes.dark; anchors.right: parent.right; anchors.verticalCenter: parent.verticalCenter; height: 35; @@ -1086,7 +1065,7 @@ Item { HiFiGlyphs { id: closeGlyphButton_paymentSuccess; text: hifi.glyphs.close; - color: lightGrayText; + color: hifi.colors.lightGrayText; size: 26; anchors.top: parent.top; anchors.topMargin: 10; diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/p2p-nearby-selected.svg b/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/p2p-nearby-selected.svg new file mode 100644 index 0000000000..59635a99b1 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/p2p-nearby-selected.svg @@ -0,0 +1,27 @@ + + + + + + + + diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/p2p-nearby-unselected.svg b/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/p2p-nearby-unselected.svg new file mode 100644 index 0000000000..bba07b9567 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/p2p-nearby-unselected.svg @@ -0,0 +1,30 @@ + + + + + + + + + From 5db5f240434249c632a7da2a3093d1d1c7fcd971 Mon Sep 17 00:00:00 2001 From: RebeccaStankus Date: Fri, 19 Jan 2018 14:52:44 -0800 Subject: [PATCH 43/52] Updated UI to fit Toolkit Standards --- interface/resources/qml/CurrentAPI.qml | 441 ++++++++++-------- .../developer/utilities/tools/currentAPI.js | 4 +- 2 files changed, 247 insertions(+), 198 deletions(-) diff --git a/interface/resources/qml/CurrentAPI.qml b/interface/resources/qml/CurrentAPI.qml index 45cf09925a..37984965d9 100644 --- a/interface/resources/qml/CurrentAPI.qml +++ b/interface/resources/qml/CurrentAPI.qml @@ -16,10 +16,9 @@ import "controls-uit" as HifiControls Item { id: root width: parent.width - height: parent.height + height: parent.height property var hideQtMethods: true - property var maxUpdateValues: 20 property var maxReloadValues: 200 property var apiMembers: [] @@ -30,7 +29,7 @@ Item { property Component keyboard Rectangle { - color: "white" + color: hifi.colors.baseGray width: parent.width height: parent.height } @@ -51,32 +50,22 @@ Item { Row { id: topBar anchors.left: parent.left - anchors.leftMargin: 8 + anchors.leftMargin: 30 + anchors.top: parent.top + anchors.topMargin: 30 width: parent.width - height: 50 - HifiControls.GlyphButton { - id: search - enabled: true - glyph: hifi.glyphs.search - color: hifi.colors.text - size: 48 - width: 50 - height: 50 - onClicked: { - addListElements(searchBar.text); - focus = true; - } - } + height: 40 HifiControls.GlyphButton { id: back; enabled: true; + color: hifi.buttons.black glyph: hifi.glyphs.backward - color: hifi.colors.text - size: 48 - width: 30 - height: 50 - anchors.margins: 2 + size: 40 + width: 40 + height: 40 + anchors.left: search.right + anchors.leftMargin: 12 onClicked: { var text = searchBar.text; var chain = text.split("."); @@ -99,17 +88,20 @@ Item { } } - TextField { - id: searchBar + HifiControls.TextField { + id: searchBar focus: true - font.pixelSize: 16 - width: 2*(parent.width-back.width-search.width-reload.width-update.width-evaluate.width-addMember.width-16)/3 - height: parent.height - font.family: ralewayRegular.name + isSearchField: true + width: parent.width - 112 + height: 40 + colorScheme: hifi.colorSchemes.dark + anchors.left: back.right + anchors.leftMargin: 10 + font.family: firaSansSemiBold.name placeholderText: "Search" onAccepted: { console.log("Enter Pressed"); - search.clicked(); + addListElements(searchBar.text); } onActiveFocusChanged: { if (activeFocus && HMD.mounted) { @@ -119,15 +111,27 @@ Item { } } - } + } + } - HifiControls.Button { + Row { + id: topBar2 + anchors.left: parent.left + anchors.leftMargin: 30 + anchors.top: topBar.bottom + anchors.topMargin: 30 + width: parent.width -60 + height: 40 + + HifiControls.GlyphButton { id: addMember; enabled: true; - text: "+" - width: 50 - height: 50 - anchors.margins: 2 + color: hifi.buttons.black + glyph: hifi.glyphs.maximize + width: 40 + height: 40 + anchors.top: parent.top + anchors.left: parent.left onClicked: { addNewMember(); updateList.start(); @@ -138,36 +142,48 @@ Item { HifiControls.Button { id: evaluate; enabled: true; + color: hifi.buttons.black text: "Eval" - width: 50 - height: 50 - anchors.margins: 2 + width: 40 + height: 40 + anchors.left: addMember.right + anchors.leftMargin: 12 onClicked: { evaluateMember(); focus = true; } } - TextField { - id: valueBar - focus: true + + HifiControls.TextField { + id: valueBar + isSearchField: false font.pixelSize: 16 - width: (parent.width-back.width-search.width-reload.width-update.width-evaluate.width-addMember.width-16)/3 - height: parent.height - font.family: ralewayRegular.name + width: parent.width - 208 + height: 40 + colorScheme: hifi.colorSchemes.dark + font.family: firaSansSemiBold.name placeholderText: "Value" - textColor: "#4466DD" - anchors.margins: 2 + anchors.left: evaluate.right + anchors.leftMargin: 12 + onActiveFocusChanged: { + if (activeFocus && HMD.mounted) { + keyboard.raised = true; + } else { + keyboard.raised = false; + } + } } HifiControls.GlyphButton { id: reload; enabled: false; + color: hifi.buttons.black glyph: hifi.glyphs.reload - color: hifi.colors.text - size: 48 - width: 50 - height: 50 - anchors.margins: 2 + size: 40 + width: 40 + height: 40 + anchors.right: update.left + anchors.rightMargin: 12 onClicked: { reloadListValues(); focus = true; @@ -177,11 +193,12 @@ Item { HifiControls.GlyphButton { id: update; enabled: false; + color: hifi.buttons.black glyph: hifi.glyphs.playback_play - size: 48 - width: 50 - height: 50 - anchors.margins: 2 + size: 40 + width: 40 + height: 40 + anchors.right: parent.right onClicked: { if (isReloading) { update.glyph = hifi.glyphs.playback_play @@ -196,71 +213,104 @@ Item { } } } - - ListModel { - id: memberModel - } - Component { - id: memberDelegate - - Row { - id: memberRow - property var isMainKey: apiType === "class"; - spacing: 10 - Rectangle { - width: isMainKey ? 20 : 40; - height: parent.height - } - - RalewayRegular { - text: apiMember - size: !isMainKey ? 16 : 22 - MouseArea { - width: list.width - height: parent.height - onClicked: { - searchBar.text = apiType=="function()" ? apiMember + "()" : apiMember; - valueBar.text = !apiValue ? "" : apiValue; - list.currentIndex = index; - evaluatingIdx = index; - } - onDoubleClicked: { - if (apiType === "class") { - addListElements(apiMember+"."); - } else { - isolateElement(evaluatingIdx); - } - - } - } - } - - - RalewayRegular { - text: apiType - size: 14 - color: hifi.colors.baseGrayHighlight - } - - RalewayRegular { - text: !apiValue ? "" : apiValue; - size: 16 - color: "#4466DD" - } - } - } - - Rectangle { id: membersBackground anchors { - left: parent.left; right: parent.right; top: topBar.bottom; bottom: parent.bottom; - margins: hifi.dimensions.contentMargin.x - bottomMargin: hifi.dimensions.contentSpacing.y + 40 + left: parent.left; right: parent.right; top: topBar2.bottom; bottom: bottomBar.top; + margins: 30 + } + color: hifi.colors.tableBackgroundDark + border.color: hifi.colors.lightGray + border.width: 2 + radius: 5 + + ListModel { + id: memberModel + } + + Component { + id: memberDelegate + Item { + id: item + width: parent.width + anchors.left: parent.left + height: 26 + clip: true + + Rectangle { + width: parent.width + height: parent.height + color: index % 2 == 0 ? hifi.colors.tableRowDarkEven : hifi.colors.tableRowDarkOdd + anchors.verticalCenter: parent.verticalCenter + + Row { + id: memberRow + anchors.bottom: parent.bottom + anchors.verticalCenter: parent.verticalCenter + spacing: 10 + + FiraSansSemiBold { + property var isMainKey: apiType === "class"; + text: apiMember + size: isMainKey ? 17 : 15 + font.bold: true + anchors.verticalCenter: parent.verticalCenter + color: isMainKey ? hifi.colors.faintGray : hifi.colors.lightGrayText + MouseArea { + width: list.width + height: parent.height + onClicked: { + searchBar.text = apiType=="function()" ? apiMember + "()" : apiMember; + valueBar.text = !apiValue ? "" : apiValue; + list.currentIndex = index; + evaluatingIdx = index; + } + onDoubleClicked: { + if (apiType === "class") { + addListElements(apiMember+"."); + } else { + isolateElement(evaluatingIdx); + } + } + } + } + + FiraSansRegular { + text: apiType + anchors.left: apiMember.right + anchors.verticalCenter: parent.verticalCenter + size: 13 + color: hifi.colors.lightGrayText + } + + FiraSansRegular { + text: !apiValue ? "" : apiValue; + anchors.left: apiType.right + anchors.verticalCenter: parent.verticalCenter + size: 14 + color: hifi.colors.primaryHighlight + } + } + } + } + } + + Component { + id: highlight + Rectangle { + anchors { + left: list.left + right: scrollBar.left + leftMargin: 2 + rightMargin: 2 + } + color: hifi.colors.primaryHighlight + radius: 4 + z: 10 + opacity: 0.5 + } } - color: "white" - radius: 4 ListView { id: list @@ -269,23 +319,16 @@ Item { left: parent.left right: scrollBar.left bottom: parent.bottom - margins: 4 + topMargin: 2 + leftMargin: 2 + bottomMargin: 2 } clip: true cacheBuffer: 4000 model: memberModel delegate: memberDelegate highlightMoveDuration: 0 - - highlight: Rectangle { - anchors { - left: parent ? parent.left : undefined - right: parent ? parent.right : undefined - leftMargin: hifi.dimensions.borderWidth - rightMargin: hifi.dimensions.borderWidth - } - color: "#BBDDFF" - } + highlight: highlight onMovementStarted: { scrollSlider.manual = true; } @@ -310,12 +353,11 @@ Item { top: parent.top right: parent.right bottom: parent.bottom - topMargin: 4 - bottomMargin: 4 + margins: 2 } - width: scrolling ? 18 : 0 - radius: 4 - color: hifi.colors.baseGrayShadow + width: 22 + height: parent.height - 4 + color: hifi.colors.tableScrollBackgroundDark MouseArea { anchors.fill: parent @@ -344,14 +386,12 @@ Item { y = index*(scrollBar.height - scrollSlider.height)/(list.count - 1); } - anchors { - right: parent.right - rightMargin: 3 - } - width: 12 - height: (list.height / list.contentHeight) * list.height - radius: width / 4 - color: "white" + anchors.right: parent.right + anchors.margins: 2 + width: 18 + height: ((list.height / list.contentHeight) * list.height) < 15 ? 15 : (list.height / list.contentHeight) * list.height + radius: 5 + color: hifi.colors.tableScrollHandleDark visible: scrollBar.scrolling; @@ -373,66 +413,75 @@ Item { } } } - - HifiControls.GlyphButton { - id: clipboard; - enabled: true; - glyph: hifi.glyphs.scriptNew - size: 38 - width: 50 - height: 50 + + Row { + id: bottomBar anchors.left: parent.left + anchors.leftMargin: 30 anchors.bottom: parent.bottom - anchors.margins: 2 - anchors.leftMargin: 8 - onClicked: { - var buffer = ""; - for (var i = 0; i < memberModel.count; i++) { - var datarow = memberModel.get(i); - buffer += "\n" + datarow.apiMember + " " + datarow.apiType + " " + datarow.apiValue; - } - Window.copyToClipboard(buffer); - focus = true; - } - } - - HifiControls.Button { - id: debug; - enabled: true; - text: "Debug Script" - width: 120 - height: 50 - anchors.right: parent.right - anchors.bottom: parent.bottom - anchors.margins: 2 - anchors.rightMargin: 8 - onClicked: { - sendToScript({type: "selectScript"}); - } - } + anchors.bottomMargin: 30 + width: parent.width + height: 40 - HifiControls.CheckBox { - id: hideQt - boxSize: 25 - boxRadius: 3 - checked: true - anchors.left: clipboard.right - anchors.leftMargin: 8 - anchors.verticalCenter: clipboard.verticalCenter - anchors.margins: 2 - onClicked: { - hideQtMethods = checked; - addListElements(); + HifiControls.GlyphButton { + id: clipboard; + enabled: true; + color: hifi.buttons.black + glyph: hifi.glyphs.scriptNew + size: 25 + width: 40 + height: 40 + anchors.left: parent.left + onClicked: { + var buffer = ""; + for (var i = 0; i < memberModel.count; i++) { + var datarow = memberModel.get(i); + buffer += "\n" + datarow.apiMember + " " + datarow.apiType + " " + datarow.apiValue; + } + Window.copyToClipboard(buffer); + focus = true; + } } - } - HifiControls.Label { - id: hideLabel - anchors.left: hideQt.right - anchors.verticalCenter: clipboard.verticalCenter - anchors.margins: 2 - font.pixelSize: 15 - text: "Hide Qt Methods" + HifiControls.CheckBox { + id: hideQt + colorScheme: hifi.checkbox.dark + boxSize: 25 + boxRadius: 3 + checked: true + anchors.left: clipboard.right + anchors.leftMargin: 10 + anchors.verticalCenter: clipboard.verticalCenter + onClicked: { + hideQtMethods = checked; + addListElements(); + } + } + + HifiControls.Label { + id: hideLabel + anchors.left: hideQt.right + anchors.verticalCenter: clipboard.verticalCenter + anchors.margins: 2 + font.pixelSize: 15 + text: "Hide Qt Methods" + } + + HifiControls.Button { + id: debug; + enabled: true; + color: hifi.buttons.black + text: "Debug Script" + width: 120 + height: 40 + anchors.right: parent.right + anchors.rightMargin: 60 + anchors.bottom: parent.bottom + + onClicked: { + sendToScript({type: "selectScript"}); + } + } } HifiControls.Keyboard { @@ -639,4 +688,4 @@ Item { } signal sendToScript(var message); -} \ No newline at end of file +} diff --git a/scripts/developer/utilities/tools/currentAPI.js b/scripts/developer/utilities/tools/currentAPI.js index 175b84b8d9..6cab6a5710 100644 --- a/scripts/developer/utilities/tools/currentAPI.js +++ b/scripts/developer/utilities/tools/currentAPI.js @@ -28,8 +28,8 @@ var window = new OverlayWindow({ title: 'API Debugger', source: qml, - width: 1200, - height: 500 + width: 500, + height: 700 }); window.closed.connect(function () { From 455090d2b94c6b6dd26b863bd8ec1b15936200fa Mon Sep 17 00:00:00 2001 From: John Conklin II Date: Fri, 19 Jan 2018 15:15:52 -0800 Subject: [PATCH 44/52] Revert "Display both lasers on tablet and Web surfaces" --- .../src/raypick/PointerScriptingInterface.cpp | 6 +- .../src/raypick/PointerScriptingInterface.h | 8 -- libraries/pointers/src/Pointer.cpp | 15 +--- libraries/pointers/src/Pointer.h | 3 +- libraries/pointers/src/PointerManager.cpp | 7 -- libraries/pointers/src/PointerManager.h | 1 - .../controllers/controllerDispatcher.js | 42 +--------- .../controllerModules/webSurfaceLaserInput.js | 84 ++++--------------- 8 files changed, 25 insertions(+), 141 deletions(-) diff --git a/interface/src/raypick/PointerScriptingInterface.cpp b/interface/src/raypick/PointerScriptingInterface.cpp index a334834979..ac5a467e76 100644 --- a/interface/src/raypick/PointerScriptingInterface.cpp +++ b/interface/src/raypick/PointerScriptingInterface.cpp @@ -175,8 +175,4 @@ QVariantMap PointerScriptingInterface::getPrevPickResult(unsigned int uid) const result = pickResult->toVariantMap(); } return result; -} - -void PointerScriptingInterface::setDoesHover(unsigned int uid, bool hover) const { - DependencyManager::get()->setDoesHover(uid, hover); -} +} \ No newline at end of file diff --git a/interface/src/raypick/PointerScriptingInterface.h b/interface/src/raypick/PointerScriptingInterface.h index 451c132769..1cc7b56503 100644 --- a/interface/src/raypick/PointerScriptingInterface.h +++ b/interface/src/raypick/PointerScriptingInterface.h @@ -202,14 +202,6 @@ public: */ Q_INVOKABLE void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) const { DependencyManager::get()->setLockEndUUID(uid, objectID, isOverlay, offsetMat); } - /**jsdoc - * Sets whether or not a pointer should generate hover events. - * @function Pointers.setDoesHover - * @param {boolean} uid - The ID of the Pointer, as returned by {@link Pointers.createPointer}. - * @param {boolean} hover - If true then the pointer generates hover events, otherwise it does not. - */ - Q_INVOKABLE void setDoesHover(unsigned int uid, bool hove) const; - /**jsdoc * Check if a Pointer is associated with the left hand. * @function Pointers.isLeftHand diff --git a/libraries/pointers/src/Pointer.cpp b/libraries/pointers/src/Pointer.cpp index 287d5a3c97..5307e17355 100644 --- a/libraries/pointers/src/Pointer.cpp +++ b/libraries/pointers/src/Pointer.cpp @@ -64,12 +64,6 @@ bool Pointer::isMouse() const { return DependencyManager::get()->isMouse(_pickUID); } -void Pointer::setDoesHover(bool doesHover) { - withWriteLock([&] { - _hover = doesHover; - }); -} - void Pointer::update(unsigned int pointerID) { // This only needs to be a read lock because update won't change any of the properties that can be modified from scripts withReadLock([&] { @@ -101,8 +95,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin } // Hover events - bool doHover = _hover && shouldHover(pickResult); - + bool doHover = shouldHover(pickResult); Pointer::PickedObject hoveredObject = getHoveredObject(pickResult); PointerEvent hoveredEvent = buildPointerEvent(hoveredObject, pickResult); hoveredEvent.setType(PointerEvent::Move); @@ -111,7 +104,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin hoveredEvent.setMoveOnHoverLeave(moveOnHoverLeave); // if shouldHover && !_prevDoHover, only send hoverBegin - if (_enabled && doHover && !_prevDoHover) { + if (_enabled && _hover && doHover && !_prevDoHover) { if (hoveredObject.type == ENTITY) { emit pointerManager->hoverBeginEntity(hoveredObject.objectID, hoveredEvent); } else if (hoveredObject.type == OVERLAY) { @@ -119,7 +112,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin } else if (hoveredObject.type == HUD) { emit pointerManager->hoverBeginHUD(hoveredEvent); } - } else if (_enabled && doHover) { + } else if (_enabled && _hover && doHover) { if (hoveredObject.type == OVERLAY) { if (_prevHoveredObject.type == OVERLAY) { if (hoveredObject.objectID == _prevHoveredObject.objectID) { @@ -236,7 +229,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin } // if we disable the pointer or disable hovering, send hoverEnd events after triggerEnd - if ((!_enabled && _prevEnabled) || (!doHover && _prevDoHover)) { + if (_hover && ((!_enabled && _prevEnabled) || (!doHover && _prevDoHover))) { if (_prevHoveredObject.type == ENTITY) { emit pointerManager->hoverEndEntity(_prevHoveredObject.objectID, hoveredEvent); } else if (_prevHoveredObject.type == OVERLAY) { diff --git a/libraries/pointers/src/Pointer.h b/libraries/pointers/src/Pointer.h index 9fd434fb15..3197c80cad 100644 --- a/libraries/pointers/src/Pointer.h +++ b/libraries/pointers/src/Pointer.h @@ -62,8 +62,6 @@ public: virtual void setLength(float length) {} virtual void setLockEndUUID(const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) {} - virtual void setDoesHover(bool hover); - void update(unsigned int pointerID); virtual void updateVisuals(const PickResultPointer& pickResult) = 0; void generatePointerEvents(unsigned int pointerID, const PickResultPointer& pickResult); @@ -103,6 +101,7 @@ private: std::unordered_map _triggeredObjects; PointerEvent::Button chooseButton(const std::string& button); + }; #endif // hifi_Pick_h diff --git a/libraries/pointers/src/PointerManager.cpp b/libraries/pointers/src/PointerManager.cpp index 13b38457b6..be890da392 100644 --- a/libraries/pointers/src/PointerManager.cpp +++ b/libraries/pointers/src/PointerManager.cpp @@ -122,13 +122,6 @@ void PointerManager::setLockEndUUID(unsigned int uid, const QUuid& objectID, boo } } -void PointerManager::setDoesHover(unsigned int uid, bool hover) const { - auto pointer = find(uid); - if (pointer) { - pointer->setDoesHover(hover); - } -} - bool PointerManager::isLeftHand(unsigned int uid) { auto pointer = find(uid); if (pointer) { diff --git a/libraries/pointers/src/PointerManager.h b/libraries/pointers/src/PointerManager.h index 2c9a37e129..b98558622f 100644 --- a/libraries/pointers/src/PointerManager.h +++ b/libraries/pointers/src/PointerManager.h @@ -37,7 +37,6 @@ public: void setLength(unsigned int uid, float length) const; void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) const; - void setDoesHover(unsigned int uid, bool hover) const; void update(); diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index a8658933e7..16f1d086b7 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -44,12 +44,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); this.highVarianceCount = 0; this.veryhighVarianceCount = 0; this.tabletID = null; - this.TABLET_UI_UUIDS = []; this.blacklist = []; - this.leftPointerDoesHover = true; - this.leftPointerDoesHoverChanged = false; - this.rightPointerDoesHover = true; - this.rightPointerDoesHoverChanged = false; this.pointerManager = new PointerManager(); // a module can occupy one or more "activity" slots while it's running. If all the required slots for a module are @@ -127,10 +122,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); return getControllerWorldLocation(Controller.Standard.RightHand, true); }; - this.isTabletID = function (uuid) { - return _this.TABLET_UI_UUIDS.indexOf(uuid) !== -1; - }; - this.updateTimings = function () { _this.intervalCount++; var thisInterval = Date.now(); @@ -157,35 +148,11 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); this.setIgnorePointerItems = function() { if (HMD.tabletID !== this.tabletID) { this.tabletID = HMD.tabletID; - this.TABLET_UI_UUIDS = [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID, HMD.homeButtonHighlightID]; Pointers.setIgnoreItems(_this.leftPointer, _this.blacklist); Pointers.setIgnoreItems(_this.rightPointer, _this.blacklist); } }; - this.updateDoesHover = function(handLaser, doesHover) { - if (handLaser.doesHover !== undefined) { - if (handLaser.hand === LEFT_HAND && _this.leftPointerDoesHover !== doesHover) { - _this.leftPointerDoesHover = doesHover; - _this.leftPointerDoesHoverChanged = true; - } else if (handLaser.hand === RIGHT_HAND && _this.rightPointerDoesHover !== doesHover) { - _this.rightPointerDoesHover = doesHover; - _this.rightPointerDoesHoverChanged = true; - } - } - } - - this.updateHovering = function () { - if (_this.leftPointerDoesHoverChanged) { - Pointers.setDoesHover(_this.leftPointer, _this.leftPointerDoesHover); - _this.leftPointerDoesHoverChanged = false; - } - if (_this.rightPointerDoesHoverChanged) { - Pointers.setDoesHover(_this.rightPointer, _this.rightPointerDoesHover); - _this.rightPointerDoesHoverChanged = false; - } - }; - this.update = function () { try { _this.updateInternal(); @@ -357,8 +324,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); _this.runningPluginNames[orderedPluginName] = true; _this.markSlots(candidatePlugin, orderedPluginName); _this.pointerManager.makePointerVisible(candidatePlugin.parameters.handLaser); - _this.updateDoesHover(candidatePlugin.parameters.handLaser, - candidatePlugin.parameters.handLaser.doesHover); if (DEBUG) { print("controllerDispatcher running " + orderedPluginName); } @@ -389,15 +354,12 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Script.beginProfileRange("dispatch.run." + runningPluginName); } var runningness = plugin.run(controllerData, deltaTime); - if (runningness.active) { - _this.updateDoesHover(plugin.parameters.handLaser, plugin.parameters.handLaser.doesHover); - } else { + if (!runningness.active) { // plugin is finished running, for now. remove it from the list // of running plugins and mark its activity-slots as "not in use" delete _this.runningPluginNames[runningPluginName]; _this.markSlots(plugin, false); _this.pointerManager.makePointerInvisible(plugin.parameters.handLaser); - _this.updateDoesHover(plugin.parameters.handLaser, true); if (DEBUG) { print("controllerDispatcher stopping " + runningPluginName); } @@ -410,8 +372,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); } } _this.pointerManager.updatePointersRenderState(controllerData.triggerClicks, controllerData.triggerValues); - _this.updateHovering(); - if (PROFILE) { Script.endProfileRange("dispatch.run"); } diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index cc8378af84..3d9d7979d5 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -87,89 +87,41 @@ Script.include("/~/system/libraries/controllers.js"); return MyAvatar.getDominantHand() === "right" ? 1 : 0; }; - this.letOtherHandRunFirst = function (controllerData, pointingAt) { - // If both hands are ready to run, let the other hand run first if it is the dominant hand so that it gets the - // highlight. - var isOtherTriggerPressed = controllerData.triggerValues[this.otherHand] > TRIGGER_OFF_VALUE; - var isLetOtherHandRunFirst = !this.getOtherModule().running - && this.getDominantHand() === this.otherHand - && (this.parameters.handLaser.allwaysOn || isOtherTriggerPressed); - if (isLetOtherHandRunFirst) { - var otherHandPointingAt = controllerData.rayPicks[this.otherHand].objectID; - if (this.isTabletID(otherHandPointingAt)) { - otherHandPointingAt = HMD.tabletID; - } - isLetOtherHandRunFirst = pointingAt === otherHandPointingAt; - } - return isLetOtherHandRunFirst; - }; - - this.hoverItem = null; - - this.isTabletID = function (uuid) { - return [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID, HMD.homeButtonHighlightID].indexOf(uuid) !== -1; - }; + this.dominantHandOverride = false; this.isReady = function(controllerData) { - if (this.isPointingAtOverlay(controllerData) || this.isPointingAtWebEntity(controllerData)) { + var otherModuleRunning = this.getOtherModule().running; + otherModuleRunning = otherModuleRunning && this.getDominantHand() !== this.hand; // Auto-swap to dominant hand. + var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE + && controllerData.triggerValues[this.otherHand] <= TRIGGER_OFF_VALUE; + if ((!otherModuleRunning || isTriggerPressed) + && (this.isPointingAtOverlay(controllerData) || this.isPointingAtWebEntity(controllerData))) { this.updateAllwaysOn(); - - var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE; + if (isTriggerPressed) { + this.dominantHandOverride = true; // Override dominant hand. + this.getOtherModule().dominantHandOverride = false; + } if (this.parameters.handLaser.allwaysOn || isTriggerPressed) { - var pointingAt = controllerData.rayPicks[this.hand].objectID; - if (this.isTabletID(pointingAt)) { - pointingAt = HMD.tabletID; - } - - if (!this.letOtherHandRunFirst(controllerData, pointingAt)) { - - if (pointingAt !== this.getOtherModule().hoverItem) { - this.parameters.handLaser.doesHover = true; - this.hoverItem = pointingAt; - } else { - this.parameters.handLaser.doesHover = false; - this.hoverItem = null; - } - - return makeRunningValues(true, [], []); - } + return makeRunningValues(true, [], []); } } - - this.parameters.handLaser.doesHover = false; - this.hoverItem = null; - return makeRunningValues(false, [], []); }; this.run = function(controllerData, deltaTime) { + var otherModuleRunning = this.getOtherModule().running; + otherModuleRunning = otherModuleRunning && this.getDominantHand() !== this.hand; // Auto-swap to dominant hand. + otherModuleRunning = otherModuleRunning || this.getOtherModule().dominantHandOverride; // Override dominant hand. var grabModuleNeedsToRun = this.grabModuleWantsNearbyOverlay(controllerData); - var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE; - if (!grabModuleNeedsToRun && (isTriggerPressed || this.parameters.handLaser.allwaysOn + if (!otherModuleRunning && !grabModuleNeedsToRun && (controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE + || this.parameters.handLaser.allwaysOn && (this.isPointingAtOverlay(controllerData) || this.isPointingAtWebEntity(controllerData)))) { this.running = true; - - var pointingAt = controllerData.rayPicks[this.hand].objectID; - if (this.isTabletID(pointingAt)) { - pointingAt = HMD.tabletID; - } - - if (pointingAt !== this.getOtherModule().hoverItem || isTriggerPressed) { - this.parameters.handLaser.doesHover = true; - this.hoverItem = pointingAt; - } else { - this.parameters.handLaser.doesHover = false; - this.hoverItem = null; - } - return makeRunningValues(true, [], []); } this.deleteContextOverlay(); this.running = false; - - this.parameters.handLaser.doesHover = false; - this.hoverItem = null; - + this.dominantHandOverride = false; return makeRunningValues(false, [], []); }; } From cd4d9255bd8899be1124fb5522e6e3d45ad3a5ed Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 18 Jan 2018 13:43:55 -0800 Subject: [PATCH 45/52] Added defaultPoseFlags to avatar protocol Change rotationSet to rotationIsDefaultPose for JointData Also for translation. Fixed all code to flip boolean value. Created EntityJointData so that the ModelEntity stuff doesn't need to change. --- .../src/avatars/ScriptableAvatar.cpp | 4 +- interface/src/ui/overlays/ModelOverlay.cpp | 4 +- libraries/animation/src/Rig.cpp | 20 +-- libraries/avatars/src/AvatarData.cpp | 154 +++++++++++++----- libraries/avatars/src/AvatarData.h | 37 +++-- .../src/RenderableModelEntityItem.cpp | 2 +- libraries/entities/src/ModelEntityItem.cpp | 2 +- libraries/entities/src/ModelEntityItem.h | 4 +- .../networking/src/udt/PacketHeaders.cpp | 2 +- libraries/networking/src/udt/PacketHeaders.h | 3 +- libraries/shared/src/BitVectorHelpers.h | 58 +++++++ libraries/shared/src/JointData.h | 21 ++- 12 files changed, 234 insertions(+), 77 deletions(-) create mode 100644 libraries/shared/src/BitVectorHelpers.h diff --git a/assignment-client/src/avatars/ScriptableAvatar.cpp b/assignment-client/src/avatars/ScriptableAvatar.cpp index c9ded2d6fb..1f3f770867 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.cpp +++ b/assignment-client/src/avatars/ScriptableAvatar.cpp @@ -123,12 +123,12 @@ void ScriptableAvatar::update(float deltatime) { AnimPose& absPose = absPoses[i]; if (data.rotation != absPose.rot()) { data.rotation = absPose.rot(); - data.rotationSet = true; + data.rotationIsDefaultPose = false; } AnimPose& relPose = poses[i]; if (data.translation != relPose.trans()) { data.translation = relPose.trans(); - data.translationSet = true; + data.translationIsDefaultPose = false; } } diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index ec586771af..0081a3b273 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -566,9 +566,9 @@ void ModelOverlay::animate() { rotationMat * fbxJoints[index].postTransform); auto& jointData = jointsData[j]; jointData.translation = extractTranslation(finalMat); - jointData.translationSet = true; + jointData.translationIsDefaultPose = false; jointData.rotation = glmExtractRotation(finalMat); - jointData.rotationSet = true; + jointData.rotationIsDefaultPose = false; } } // Set the data in the model diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 1c9d9a3447..b1b41775a8 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1705,16 +1705,16 @@ void Rig::copyJointsIntoJointData(QVector& jointDataVec) const { // rotations are in absolute rig frame. glm::quat defaultAbsRot = geometryToRigPose.rot() * _animSkeleton->getAbsoluteDefaultPose(i).rot(); data.rotation = _internalPoseSet._absolutePoses[i].rot(); - data.rotationSet = !isEqual(data.rotation, defaultAbsRot); + data.rotationIsDefaultPose = isEqual(data.rotation, defaultAbsRot); // translations are in relative frame but scaled so that they are in meters, // instead of geometry units. glm::vec3 defaultRelTrans = _geometryOffset.scale() * _animSkeleton->getRelativeDefaultPose(i).trans(); data.translation = _geometryOffset.scale() * _internalPoseSet._relativePoses[i].trans(); - data.translationSet = !isEqual(data.translation, defaultRelTrans); + data.translationIsDefaultPose = isEqual(data.translation, defaultRelTrans); } else { - data.translationSet = false; - data.rotationSet = false; + data.translationIsDefaultPose = true; + data.rotationIsDefaultPose = true; } } } @@ -1739,11 +1739,11 @@ void Rig::copyJointsFromJointData(const QVector& jointDataVec) { const glm::quat rigToGeometryRot(glmExtractRotation(_rigToGeometryTransform)); for (int i = 0; i < numJoints; i++) { const JointData& data = jointDataVec.at(i); - if (data.rotationSet) { + if (data.rotationIsDefaultPose) { + rotations.push_back(absoluteDefaultPoses[i].rot()); + } else { // JointData rotations are in absolute rig-frame so we rotate them to absolute geometry-frame rotations.push_back(rigToGeometryRot * data.rotation); - } else { - rotations.push_back(absoluteDefaultPoses[i].rot()); } } @@ -1759,11 +1759,11 @@ void Rig::copyJointsFromJointData(const QVector& jointDataVec) { const JointData& data = jointDataVec.at(i); _internalPoseSet._relativePoses[i].scale() = Vectors::ONE; _internalPoseSet._relativePoses[i].rot() = rotations[i]; - if (data.translationSet) { + if (data.translationIsDefaultPose) { + _internalPoseSet._relativePoses[i].trans() = relativeDefaultPoses[i].trans(); + } else { // JointData translations are in scaled relative-frame so we scale back to regular relative-frame _internalPoseSet._relativePoses[i].trans() = _invGeometryOffset.scale() * data.translation; - } else { - _internalPoseSet._relativePoses[i].trans() = relativeDefaultPoses[i].trans(); } } } diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 94df5bf7f4..4ae1a94366 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include "AvatarLogging.h" @@ -77,6 +78,12 @@ size_t AvatarDataPacket::maxJointDataSize(size_t numJoints) { return totalSize; } +size_t AvatarDataPacket::maxJointDefaultPoseFlagsSize(size_t numJoints) { + const size_t bitVectorSize = calcBitVectorSize((int)numJoints); + size_t totalSize = sizeof(uint8_t); // numJoints + totalSize += 2 * bitVectorSize; + return totalSize; +} AvatarData::AvatarData() : SpatiallyNestable(NestableType::Avatar, QUuid()), @@ -272,6 +279,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent bool hasFaceTrackerInfo = false; bool hasJointData = false; + bool hasJointDefaultPoseFlags = false; if (sendPALMinimum) { hasAudioLoudness = true; @@ -290,6 +298,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent hasFaceTrackerInfo = !dropFaceTracking && hasFaceTracker() && (sendAll || faceTrackerInfoChangedSince(lastSentTime)); hasJointData = sendAll || !sendMinimum; + hasJointDefaultPoseFlags = hasJointData; } @@ -314,7 +323,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent | (hasParentInfo ? AvatarDataPacket::PACKET_HAS_PARENT_INFO : 0) | (hasAvatarLocalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_LOCAL_POSITION : 0) | (hasFaceTrackerInfo ? AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO : 0) - | (hasJointData ? AvatarDataPacket::PACKET_HAS_JOINT_DATA : 0); + | (hasJointData ? AvatarDataPacket::PACKET_HAS_JOINT_DATA : 0) + | (hasJointDefaultPoseFlags ? AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS : 0); memcpy(destinationBuffer, &packetStateFlags, sizeof(packetStateFlags)); destinationBuffer += sizeof(packetStateFlags); @@ -541,14 +551,19 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent for (int i = 0; i < _jointData.size(); i++) { const JointData& data = _jointData[i]; + const JointData& last = lastSentJointData[i]; - // The dot product for smaller rotations is a smaller number. - // So if the dot() is less than the value, then the rotation is a larger angle of rotation - bool largeEnoughRotation = fabsf(glm::dot(data.rotation, lastSentJointData[i].rotation)) < minRotationDOT; + if (!data.rotationIsDefaultPose) { + if (sendAll || last.rotationIsDefaultPose || last.rotation != data.rotation) { - if (sendAll || lastSentJointData[i].rotation != data.rotation) { - if (sendAll || !cullSmallChanges || largeEnoughRotation) { - if (data.rotationSet) { + bool largeEnoughRotation = true; + if (cullSmallChanges) { + // The dot product for smaller rotations is a smaller number. + // So if the dot() is less than the value, then the rotation is a larger angle of rotation + largeEnoughRotation = fabsf(glm::dot(last.rotation, data.rotation)) < minRotationDOT; + } + + if (sendAll || !cullSmallChanges || largeEnoughRotation) { validity |= (1 << validityBit); #ifdef WANT_DEBUG rotationSentCount++; @@ -557,8 +572,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (sentJointDataOut) { localSentJointDataOut[i].rotation = data.rotation; + localSentJointDataOut[i].rotationIsDefaultPose = false; } - } } } @@ -588,11 +603,10 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent float maxTranslationDimension = 0.0; for (int i = 0; i < _jointData.size(); i++) { const JointData& data = _jointData[i]; - if (sendAll || lastSentJointData[i].translation != data.translation) { - if (sendAll || - !cullSmallChanges || - glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation) { - if (data.translationSet) { + + if (!data.translationIsDefaultPose) { + if (sendAll || lastSentJointData[i].translation != data.translation) { + if (sendAll || !cullSmallChanges || glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation) { validity |= (1 << validityBit); #ifdef WANT_DEBUG translationSentCount++; @@ -606,8 +620,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (sentJointDataOut) { localSentJointDataOut[i].translation = data.translation; + localSentJointDataOut[i].translationIsDefaultPose = false; } - } } } @@ -655,6 +669,30 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } + if (hasJointDefaultPoseFlags) { + auto startSection = destinationBuffer; + QReadLocker readLock(&_jointDataLock); + + // write numJoints + int numJoints = _jointData.size(); + *destinationBuffer++ = (uint8_t)numJoints; + + // write rotationIsDefaultPose bits + destinationBuffer += writeBitVector(destinationBuffer, numJoints, [&](int i) { + return _jointData[i].rotationIsDefaultPose; + }); + + // write translationIsDefaultPose bits + destinationBuffer += writeBitVector(destinationBuffer, numJoints, [&](int i) { + return _jointData[i].translationIsDefaultPose; + }); + + if (outboundDataRateOut) { + size_t numBytes = destinationBuffer - startSection; + outboundDataRateOut->jointDefaultPoseFlagsRate.increment(numBytes); + } + } + int avatarDataSize = destinationBuffer - startPosition; if (avatarDataSize > (int)byteArraySize) { @@ -664,6 +702,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent return avatarDataByteArray.left(avatarDataSize); } + // NOTE: This is never used in a "distanceAdjust" mode, so it's ok that it doesn't use a variable minimum rotation/translation void AvatarData::doneEncoding(bool cullSmallChanges) { // The server has finished sending this version of the joint-data to other nodes. Update _lastSentJointData. @@ -674,7 +713,7 @@ void AvatarData::doneEncoding(bool cullSmallChanges) { if (_lastSentJointData[i].rotation != data.rotation) { if (!cullSmallChanges || fabsf(glm::dot(data.rotation, _lastSentJointData[i].rotation)) <= AVATAR_MIN_ROTATION_DOT) { - if (data.rotationSet) { + if (!data.rotationIsDefaultPose) { _lastSentJointData[i].rotation = data.rotation; } } @@ -682,7 +721,7 @@ void AvatarData::doneEncoding(bool cullSmallChanges) { if (_lastSentJointData[i].translation != data.translation) { if (!cullSmallChanges || glm::distance(data.translation, _lastSentJointData[i].translation) > AVATAR_MIN_TRANSLATION) { - if (data.translationSet) { + if (!data.translationIsDefaultPose) { _lastSentJointData[i].translation = data.translation; } } @@ -730,6 +769,7 @@ const unsigned char* unpackFauxJoint(const unsigned char* sourceBuffer, ThreadSa // read data in packet starting at byte offset and return number of bytes parsed int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { + // lazily allocate memory for HeadData in case we're not an Avatar instance lazyInitHeadData(); @@ -745,18 +785,19 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { #define HAS_FLAG(B,F) ((B & F) == F) - bool hasAvatarGlobalPosition = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_GLOBAL_POSITION); - bool hasAvatarBoundingBox = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_BOUNDING_BOX); - bool hasAvatarOrientation = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_ORIENTATION); - bool hasAvatarScale = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_SCALE); - bool hasLookAtPosition = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_LOOK_AT_POSITION); - bool hasAudioLoudness = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AUDIO_LOUDNESS); - bool hasSensorToWorldMatrix = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_SENSOR_TO_WORLD_MATRIX); - bool hasAdditionalFlags = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_ADDITIONAL_FLAGS); - bool hasParentInfo = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_PARENT_INFO); - bool hasAvatarLocalPosition = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_LOCAL_POSITION); - bool hasFaceTrackerInfo = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO); - bool hasJointData = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_JOINT_DATA); + bool hasAvatarGlobalPosition = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_GLOBAL_POSITION); + bool hasAvatarBoundingBox = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_BOUNDING_BOX); + bool hasAvatarOrientation = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_ORIENTATION); + bool hasAvatarScale = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_SCALE); + bool hasLookAtPosition = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_LOOK_AT_POSITION); + bool hasAudioLoudness = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AUDIO_LOUDNESS); + bool hasSensorToWorldMatrix = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_SENSOR_TO_WORLD_MATRIX); + bool hasAdditionalFlags = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_ADDITIONAL_FLAGS); + bool hasParentInfo = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_PARENT_INFO); + bool hasAvatarLocalPosition = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_LOCAL_POSITION); + bool hasFaceTrackerInfo = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO); + bool hasJointData = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_JOINT_DATA); + bool hasJointDefaultPoseFlags = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS); quint64 now = usecTimestampNow(); @@ -1055,7 +1096,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { if (validRotations[i]) { sourceBuffer += unpackOrientationQuatFromSixBytes(sourceBuffer, data.rotation); _hasNewJointData = true; - data.rotationSet = true; + data.rotationIsDefaultPose = false; } } @@ -1090,7 +1131,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { if (validTranslations[i]) { sourceBuffer += unpackFloatVec3FromSignedTwoByteFixed(sourceBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); _hasNewJointData = true; - data.translationSet = true; + data.translationIsDefaultPose = false; } } @@ -1110,6 +1151,32 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { _jointDataUpdateRate.increment(); } + if (hasJointDefaultPoseFlags) { + auto startSection = sourceBuffer; + + QWriteLocker writeLock(&_jointDataLock); + + PACKET_READ_CHECK(JointDefaultPoseFlagsNumJoints, sizeof(uint8_t)); + int numJoints = (int)*sourceBuffer++; + + _jointData.resize(numJoints); + + size_t bitVectorSize = calcBitVectorSize(numJoints); + PACKET_READ_CHECK(JointDefaultPoseFlagsRotationFlags, bitVectorSize); + sourceBuffer += readBitVector(sourceBuffer, numJoints, [&](int i, bool value) { + _jointData[i].rotationIsDefaultPose = value; + }); + + PACKET_READ_CHECK(JointDefaultPoseFlagsTranslationFlags, bitVectorSize); + sourceBuffer += readBitVector(sourceBuffer, numJoints, [&](int i, bool value) { + _jointData[i].translationIsDefaultPose = value; + }); + + int numBytesRead = sourceBuffer - startSection; + _jointDefaultPoseFlagsRate.increment(numBytesRead); + _jointDefaultPoseFlagsUpdateRate.increment(); + } + int numBytesRead = sourceBuffer - startPosition; _averageBytesReceived.updateAverage(numBytesRead); @@ -1146,6 +1213,8 @@ float AvatarData::getDataRate(const QString& rateName) const { return _faceTrackerRate.rate() / BYTES_PER_KILOBIT; } else if (rateName == "jointData") { return _jointDataRate.rate() / BYTES_PER_KILOBIT; + } else if (rateName == "jointDefaultPoseFlagsRate") { + return _jointDefaultPoseFlagsRate.rate() / BYTES_PER_KILOBIT; } else if (rateName == "globalPositionOutbound") { return _outboundDataRate.globalPositionRate.rate() / BYTES_PER_KILOBIT; } else if (rateName == "localPositionOutbound") { @@ -1170,6 +1239,8 @@ float AvatarData::getDataRate(const QString& rateName) const { return _outboundDataRate.faceTrackerRate.rate() / BYTES_PER_KILOBIT; } else if (rateName == "jointDataOutbound") { return _outboundDataRate.jointDataRate.rate() / BYTES_PER_KILOBIT; + } else if (rateName == "jointDefaultPoseFlagsOutbound") { + return _outboundDataRate.jointDefaultPoseFlagsRate.rate() / BYTES_PER_KILOBIT; } return 0.0f; } @@ -1236,9 +1307,9 @@ void AvatarData::setJointData(int index, const glm::quat& rotation, const glm::v } JointData& data = _jointData[index]; data.rotation = rotation; - data.rotationSet = true; + data.rotationIsDefaultPose = false; data.translation = translation; - data.translationSet = true; + data.translationIsDefaultPose = false; } void AvatarData::clearJointData(int index) { @@ -1294,7 +1365,8 @@ void AvatarData::setJointData(const QString& name, const glm::quat& rotation, co auto& jointData = _jointData[index]; jointData.rotation = rotation; jointData.translation = translation; - jointData.rotationSet = jointData.translationSet = true; + jointData.rotationIsDefaultPose = false; + jointData.translationIsDefaultPose = false; }); } @@ -1304,7 +1376,7 @@ void AvatarData::setJointRotation(const QString& name, const glm::quat& rotation writeLockWithNamedJointIndex(name, [&](int index) { auto& data = _jointData[index]; data.rotation = rotation; - data.rotationSet = true; + data.rotationIsDefaultPose = false; }); } @@ -1314,7 +1386,7 @@ void AvatarData::setJointTranslation(const QString& name, const glm::vec3& trans writeLockWithNamedJointIndex(name, [&](int index) { auto& data = _jointData[index]; data.translation = translation; - data.translationSet = true; + data.translationIsDefaultPose = false; }); } @@ -1328,7 +1400,7 @@ void AvatarData::setJointRotation(int index, const glm::quat& rotation) { } JointData& data = _jointData[index]; data.rotation = rotation; - data.rotationSet = true; + data.rotationIsDefaultPose = false; } void AvatarData::setJointTranslation(int index, const glm::vec3& translation) { @@ -1341,7 +1413,7 @@ void AvatarData::setJointTranslation(int index, const glm::vec3& translation) { } JointData& data = _jointData[index]; data.translation = translation; - data.translationSet = true; + data.translationIsDefaultPose = false; } void AvatarData::clearJointData(const QString& name) { @@ -1397,7 +1469,7 @@ void AvatarData::setJointRotations(const QVector& jointRotations) { for (int i = 0; i < size; ++i) { auto& data = _jointData[i]; data.rotation = jointRotations[i]; - data.rotationSet = true; + data.rotationIsDefaultPose = false; } } @@ -1419,7 +1491,7 @@ void AvatarData::setJointTranslations(const QVector& jointTranslation for (int i = 0; i < size; ++i) { auto& data = _jointData[i]; data.translation = jointTranslations[i]; - data.translationSet = true; + data.translationIsDefaultPose = false; } } @@ -1996,9 +2068,9 @@ JointData jointDataFromJsonValue(const QJsonValue& json) { if (json.isArray()) { QJsonArray array = json.toArray(); result.rotation = quatFromJsonValue(array[0]); - result.rotationSet = true; + result.rotationIsDefaultPose = false; result.translation = vec3FromJsonValue(array[1]); - result.translationSet = true; + result.translationIsDefaultPose = false; } return result; } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 00cc760658..92c505f5c3 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -113,18 +113,19 @@ namespace AvatarDataPacket { // Packet State Flags - we store the details about the existence of other records in this bitset: // AvatarGlobalPosition, Avatar face tracker, eye tracking, and existence of using HasFlags = uint16_t; - const HasFlags PACKET_HAS_AVATAR_GLOBAL_POSITION = 1U << 0; - const HasFlags PACKET_HAS_AVATAR_BOUNDING_BOX = 1U << 1; - const HasFlags PACKET_HAS_AVATAR_ORIENTATION = 1U << 2; - const HasFlags PACKET_HAS_AVATAR_SCALE = 1U << 3; - const HasFlags PACKET_HAS_LOOK_AT_POSITION = 1U << 4; - const HasFlags PACKET_HAS_AUDIO_LOUDNESS = 1U << 5; - const HasFlags PACKET_HAS_SENSOR_TO_WORLD_MATRIX = 1U << 6; - const HasFlags PACKET_HAS_ADDITIONAL_FLAGS = 1U << 7; - const HasFlags PACKET_HAS_PARENT_INFO = 1U << 8; - const HasFlags PACKET_HAS_AVATAR_LOCAL_POSITION = 1U << 9; - const HasFlags PACKET_HAS_FACE_TRACKER_INFO = 1U << 10; - const HasFlags PACKET_HAS_JOINT_DATA = 1U << 11; + const HasFlags PACKET_HAS_AVATAR_GLOBAL_POSITION = 1U << 0; + const HasFlags PACKET_HAS_AVATAR_BOUNDING_BOX = 1U << 1; + const HasFlags PACKET_HAS_AVATAR_ORIENTATION = 1U << 2; + const HasFlags PACKET_HAS_AVATAR_SCALE = 1U << 3; + const HasFlags PACKET_HAS_LOOK_AT_POSITION = 1U << 4; + const HasFlags PACKET_HAS_AUDIO_LOUDNESS = 1U << 5; + const HasFlags PACKET_HAS_SENSOR_TO_WORLD_MATRIX = 1U << 6; + const HasFlags PACKET_HAS_ADDITIONAL_FLAGS = 1U << 7; + const HasFlags PACKET_HAS_PARENT_INFO = 1U << 8; + const HasFlags PACKET_HAS_AVATAR_LOCAL_POSITION = 1U << 9; + const HasFlags PACKET_HAS_FACE_TRACKER_INFO = 1U << 10; + const HasFlags PACKET_HAS_JOINT_DATA = 1U << 11; + const HasFlags PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS = 1U << 12; const size_t AVATAR_HAS_FLAGS_SIZE = 2; using SixByteQuat = uint8_t[6]; @@ -256,6 +257,15 @@ namespace AvatarDataPacket { }; */ size_t maxJointDataSize(size_t numJoints); + + /* + struct JointDefaultPoseFlags { + uint8_t numJoints; + uint8_t rotationIsDefaultPoseBits[ceil(numJoints / 8)]; + uint8_t translationIsDefaultPoseBits[ceil(numJoints / 8)]; + }; + */ + size_t maxJointDefaultPoseFlagsSize(size_t numJoints); } const float MAX_AUDIO_LOUDNESS = 1000.0f; // close enough for mouth animation @@ -321,6 +331,7 @@ public: RateCounter<> parentInfoRate; RateCounter<> faceTrackerRate; RateCounter<> jointDataRate; + RateCounter<> jointDefaultPoseFlagsRate; }; class AvatarPriority { @@ -810,6 +821,7 @@ protected: RateCounter<> _parentInfoRate; RateCounter<> _faceTrackerRate; RateCounter<> _jointDataRate; + RateCounter<> _jointDefaultPoseFlagsRate; // Some rate data for incoming data updates RateCounter<> _parseBufferUpdateRate; @@ -825,6 +837,7 @@ protected: RateCounter<> _parentInfoUpdateRate; RateCounter<> _faceTrackerUpdateRate; RateCounter<> _jointDataUpdateRate; + RateCounter<> _jointDefaultPoseFlagsUpdateRate; // Some rate data for outgoing data AvatarDataRate _outboundDataRate; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 827507c3aa..c85817a228 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1050,7 +1050,7 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) { return; } - QVector jointsData; + QVector jointsData; const QVector& frames = _animation->getFramesReference(); // NOTE: getFrames() is too heavy int frameCount = frames.size(); diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 8996665262..a615beefa9 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -452,7 +452,7 @@ void ModelEntityItem::resizeJointArrays(int newSize) { }); } -void ModelEntityItem::setAnimationJointsData(const QVector& jointsData) { +void ModelEntityItem::setAnimationJointsData(const QVector& jointsData) { resizeJointArrays(jointsData.size()); _jointDataLock.withWriteLock([&] { for (auto index = 0; index < jointsData.size(); ++index) { diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 6169039f52..c2109ba51f 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -124,7 +124,7 @@ public: virtual void setJointTranslations(const QVector& translations); virtual void setJointTranslationsSet(const QVector& translationsSet); - virtual void setAnimationJointsData(const QVector& jointsData); + virtual void setAnimationJointsData(const QVector& jointsData); QVector getJointRotations() const; QVector getJointRotationsSet() const; @@ -150,7 +150,7 @@ protected: bool _jointTranslationsExplicitlySet{ false }; // were the joints set as a property or just side effect of animations struct ModelJointData { - JointData joint; + EntityJointData joint; bool rotationDirty { false }; bool translationDirty { false }; }; diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 711aeb2cc2..c48c6bfc0b 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -38,7 +38,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::AvatarData: case PacketType::BulkAvatarData: case PacketType::KillAvatar: - return static_cast(AvatarMixerPacketVersion::UpdatedMannequinDefaultAvatar); + return static_cast(AvatarMixerPacketVersion::AvatarJointDefaultPoseFlags); case PacketType::MessagesData: return static_cast(MessageDataVersion::TextOrBinaryData); case PacketType::ICEServerHeartbeat: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 458666571c..0f69691bd5 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -247,7 +247,8 @@ enum class AvatarMixerPacketVersion : PacketVersion { AvatarIdentitySequenceFront, IsReplicatedInAvatarIdentity, AvatarIdentityLookAtSnapping, - UpdatedMannequinDefaultAvatar + UpdatedMannequinDefaultAvatar, + AvatarJointDefaultPoseFlags }; enum class DomainConnectRequestVersion : PacketVersion { diff --git a/libraries/shared/src/BitVectorHelpers.h b/libraries/shared/src/BitVectorHelpers.h new file mode 100644 index 0000000000..71826036eb --- /dev/null +++ b/libraries/shared/src/BitVectorHelpers.h @@ -0,0 +1,58 @@ +// +// BitVectorHelpers.h +// libraries/shared/src +// +// Created by Anthony Thibault on 1/19/18. +// Copyright 2018 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_BitVectorHelpers_h +#define hifi_BitVectorHelpers_h + +size_t calcBitVectorSize(int numBits) { + return ((numBits - 1) >> 3) + 1; +} + +// func should be of type bool func(int index) +template +size_t writeBitVector(uint8_t* destinationBuffer, int numBits, const F& func) { + size_t totalBytes = ((numBits - 1) >> 3) + 1; + uint8_t* cursor = destinationBuffer; + uint8_t byte = 0; + uint8_t bit = 0; + + for (int i = 0; i < numBits; i++) { + if (func(i)) { + byte |= (1 << bit); + } + if (++bit == BITS_IN_BYTE) { + *cursor++ = byte; + byte = 0; + bit = 0; + } + } + return totalBytes; +} + +// func should be of type 'void func(int index, bool value)' +template +size_t readBitVector(const uint8_t* sourceBuffer, int numBits, const F& func) { + size_t totalBytes = ((numBits - 1) >> 3) + 1; + const uint8_t* cursor = sourceBuffer; + uint8_t bit = 0; + + for (int i = 0; i < numBits; i++) { + bool value = (bool)(*cursor & (1 << bit)); + func(i, value); + if (++bit == BITS_IN_BYTE) { + cursor++; + bit = 0; + } + } + return totalBytes; +} + +#endif diff --git a/libraries/shared/src/JointData.h b/libraries/shared/src/JointData.h index a05b5c649a..f4c8b89e7a 100644 --- a/libraries/shared/src/JointData.h +++ b/libraries/shared/src/JointData.h @@ -5,14 +5,27 @@ #include #include -// Used by the avatar mixer to describe a single joint -// These are relative to their parent and translations are in meters -class JointData { +class EntityJointData { public: glm::quat rotation; + glm::vec3 translation; bool rotationSet = false; - glm::vec3 translation; // meters bool translationSet = false; }; +// Used by the avatar mixer to describe a single joint +// Translations relative to their parent and are in meters. +// Rotations are absolute (i.e. not relative to parent) and are in rig space. +class JointData { +public: + glm::quat rotation; + glm::vec3 translation; + + // This indicates that the rotation or translation is the same as the defaultPose for the avatar. + // if true, it also means that the rotation or translation value in this structure is not valid and + // should be replaced by the avatar's actual default pose value. + bool rotationIsDefaultPose = true; + bool translationIsDefaultPose = true; +}; + #endif From 89e403d438bc76741aae4f153073939a7824db27 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 19 Jan 2018 15:39:57 -0800 Subject: [PATCH 46/52] Balance text in send money --- .../commerce/wallet/sendMoney/SendMoney.qml | 58 ++++++++++++++++++- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml b/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml index 55fca14c24..c87ef13a63 100644 --- a/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml +++ b/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml @@ -832,6 +832,58 @@ Item { } } + FiraSansSemiBold { + visible: amountTextFieldError.text === ""; + text: "Balance: "; + // Anchors + anchors.top: amountTextField.bottom; + anchors.topMargin: 2; + anchors.left: amountTextField.left; + anchors.right: sendMoneyBalanceText_HFC.left; + width: paintedWidth; + height: 40; + // Text size + size: 16; + // Style + color: hifi.colors.lightGrayText; + verticalAlignment: Text.AlignTop; + horizontalAlignment: Text.AlignRight; + } + HiFiGlyphs { + id: sendMoneyBalanceText_HFC; + visible: amountTextFieldError.text === ""; + text: hifi.glyphs.hfc; + // Size + size: 16; + // Anchors + anchors.top: amountTextField.bottom; + anchors.topMargin: 2; + anchors.right: sendMoneyBalanceText.left; + width: paintedWidth; + height: 40; + // Style + color: hifi.colors.lightGrayText; + verticalAlignment: Text.AlignTop; + horizontalAlignment: Text.AlignRight; + } + FiraSansSemiBold { + id: sendMoneyBalanceText; + visible: amountTextFieldError.text === ""; + text: balanceText.text; + // Anchors + anchors.top: amountTextField.bottom; + anchors.topMargin: 2; + anchors.right: amountTextField.right; + width: paintedWidth; + height: 40; + // Text size + size: 16; + // Style + color: hifi.colors.lightGrayText; + verticalAlignment: Text.AlignTop; + horizontalAlignment: Text.AlignRight; + } + RalewaySemiBold { id: amountTextFieldError; // Anchors @@ -895,7 +947,7 @@ Item { previousText = text; } } - RalewaySemiBold { + FiraSansSemiBold { id: optionalMessageCharacterCount; text: optionalMessage.text.length + "/" + optionalMessage.maximumLength; // Anchors @@ -1166,7 +1218,7 @@ Item { color: hifi.colors.blueAccent; } - RalewaySemiBold { + FiraSansSemiBold { id: amountSentText; text: amountTextField.text; // Anchors @@ -1371,7 +1423,7 @@ Item { color: hifi.colors.baseGray; } - RalewaySemiBold { + FiraSansSemiBold { id: amountSentText_paymentFailure; text: amountTextField.text; // Anchors From f6062ebfccb5a3653844fd69d0b8afdf820524cb Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 19 Jan 2018 16:00:08 -0800 Subject: [PATCH 47/52] new icon and button layout --- .../icons/tablet-icons/EmoteAppIcon.svg | 21 ++++++++++++++++++ scripts/system/emote.js | 2 +- scripts/system/html/EmoteApp.html | 22 +++++++++---------- 3 files changed, 33 insertions(+), 12 deletions(-) create mode 100644 interface/resources/icons/tablet-icons/EmoteAppIcon.svg diff --git a/interface/resources/icons/tablet-icons/EmoteAppIcon.svg b/interface/resources/icons/tablet-icons/EmoteAppIcon.svg new file mode 100644 index 0000000000..340f0fcd2f --- /dev/null +++ b/interface/resources/icons/tablet-icons/EmoteAppIcon.svg @@ -0,0 +1,21 @@ + + + + + + + + + diff --git a/scripts/system/emote.js b/scripts/system/emote.js index f1f739c126..139870fd63 100644 --- a/scripts/system/emote.js +++ b/scripts/system/emote.js @@ -43,7 +43,7 @@ var activeTimer = false; // used to cancel active timer if a user plays an amima var activeEmote = false; // to keep track of the currently playing emote button = tablet.addButton({ - //icon: "icons/tablet-icons/emote.svg", // TODO - we need graphics for this + icon: "icons/tablet-icons/EmoteAppIcon.svg", text: EMOTE_LABEL, sortOrder: EMOTE_APP_SORT_ORDER }); diff --git a/scripts/system/html/EmoteApp.html b/scripts/system/html/EmoteApp.html index 30ef3e17a1..0a423b9b6c 100644 --- a/scripts/system/html/EmoteApp.html +++ b/scripts/system/html/EmoteApp.html @@ -44,11 +44,11 @@ input[type=button] { font-family: 'Raleway'; font-weight: bold; - font-size: 13px; + font-size: 20px; text-transform: uppercase; vertical-align: top; - height: 28px; - min-width: 120px; + height: 105px; + min-width: 190px; padding: 0px 18px; margin-right: 6px; border-radius: 5px; @@ -98,14 +98,14 @@

Click an emotion to Emote:

-

-

-

-

-

-

-

-

+

+

+

+

+

+

+

+

From 1dde640a40bd2c2536df04c5a9e8d30436ae2fdc Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 19 Jan 2018 16:06:01 -0800 Subject: [PATCH 48/52] -_- --- interface/resources/qml/controls-uit/TextField.qml | 2 +- .../resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/controls-uit/TextField.qml b/interface/resources/qml/controls-uit/TextField.qml index e2552c24d0..a21d1f8efd 100644 --- a/interface/resources/qml/controls-uit/TextField.qml +++ b/interface/resources/qml/controls-uit/TextField.qml @@ -24,7 +24,7 @@ TextField { property bool isSearchField: false property string label: "" property real controlHeight: height + (textFieldLabel.visible ? textFieldLabel.height + 1 : 0) - property bool hasDefocusedBorder: false; + property bool hasDefocusedBorder: true; property bool hasRoundedBorder: false property int roundedBorderRadius: 4 property bool error: false; diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml b/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml index c87ef13a63..f66781c919 100644 --- a/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml +++ b/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml @@ -402,6 +402,7 @@ Item { colorScheme: hifi.colorSchemes.faintGray; hasClearButton: true; hasRoundedBorder: true; + hasDefocusedBorder: false; roundedBorderRadius: filterBar.height/2; anchors.fill: parent; centerPlaceholderGlyph: hifi.glyphs.search; From cbc8698e9a3273f12c7ccdbc79b089eb2bbecd15 Mon Sep 17 00:00:00 2001 From: Bruce Brown <1st-BrainStormer@users.noreply.github.com> Date: Fri, 19 Jan 2018 16:59:47 -0800 Subject: [PATCH 49/52] Fix Bitwise operators. --- libraries/midi/src/Midi.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/midi/src/Midi.cpp b/libraries/midi/src/Midi.cpp index 1f1d581e48..1f190111f2 100644 --- a/libraries/midi/src/Midi.cpp +++ b/libraries/midi/src/Midi.cpp @@ -176,7 +176,7 @@ void Midi::sendMessage(int device, int channel, int type, int note, int velocity void Midi::sendNote(int status, int note, int velocity) { for (int i = 1; i < midihout.size(); i++) { // Skip 0 (Microsoft GS Wavetable Synth) if (midihout[i] != NULL) { - midiOutShortMsg(midihout[i], status + (note << MIDI_SHIFT_NOTE) + (velocity << MIDI_SHIFT_VELOCITY)); + midiOutShortMsg(midihout[i], status | (note << MIDI_SHIFT_NOTE) | (velocity << MIDI_SHIFT_VELOCITY)); } } } From b0f21c6931b2213032a1d1cf087e33095599bd71 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Sat, 20 Jan 2018 07:09:16 -0700 Subject: [PATCH 50/52] sorter variable names --- .../ui/src/ui/TabletScriptingInterface.cpp | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index 2b89045246..0bc9676a2b 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -76,21 +76,21 @@ QVariant TabletButtonListModel::data(const QModelIndex& index, int role) const { } TabletButtonProxy* TabletButtonListModel::addButton(const QVariant& properties) { - QVariantMap newTabletButtonProperties = properties.toMap(); - if (newTabletButtonProperties.find(BUTTON_SORT_ORDER_KEY) == newTabletButtonProperties.end()) { - newTabletButtonProperties[BUTTON_SORT_ORDER_KEY] = DEFAULT_BUTTON_SORT_ORDER; + QVariantMap newProperties = properties.toMap(); + if (newProperties.find(BUTTON_SORT_ORDER_KEY) == newProperties.end()) { + newProperties[BUTTON_SORT_ORDER_KEY] = DEFAULT_BUTTON_SORT_ORDER; } - int insertButtonUsingIndex = computeNewButtonIndex(newTabletButtonProperties); - auto newTabletButtonProxy = QSharedPointer(new TabletButtonProxy(newTabletButtonProperties)); + int index = computeNewButtonIndex(newProperties); + auto button = QSharedPointer(new TabletButtonProxy(newProperties)); beginResetModel(); - int buttonCount = (int)_buttons.size(); - if (insertButtonUsingIndex < buttonCount) { - _buttons.insert(_buttons.begin() + insertButtonUsingIndex, newTabletButtonProxy); + int numButtons = (int)_buttons.size(); + if (index < numButtons) { + _buttons.insert(_buttons.begin() + index, button); } else { - _buttons.push_back(newTabletButtonProxy); + _buttons.push_back(button); } endResetModel(); - return newTabletButtonProxy.data(); + return button.data(); } void TabletButtonListModel::removeButton(TabletButtonProxy* button) { @@ -105,17 +105,17 @@ void TabletButtonListModel::removeButton(TabletButtonProxy* button) { } int TabletButtonListModel::computeNewButtonIndex(const QVariantMap& newButtonProperties) { - int buttonCount = (int)_buttons.size(); + int numButtons = (int)_buttons.size(); int newButtonSortOrder = newButtonProperties[BUTTON_SORT_ORDER_KEY].toInt(); - if (newButtonSortOrder == DEFAULT_BUTTON_SORT_ORDER) return buttonCount; - for (int i = 0; i < buttonCount; i++) { + if (newButtonSortOrder == DEFAULT_BUTTON_SORT_ORDER) return numButtons; + for (int i = 0; i < numButtons; i++) { QVariantMap tabletButtonProperties = _buttons[i]->getProperties(); int tabletButtonSortOrder = tabletButtonProperties[BUTTON_SORT_ORDER_KEY].toInt(); if (newButtonSortOrder <= tabletButtonSortOrder) { return i; } } - return buttonCount; + return numButtons; } TabletButtonsProxyModel::TabletButtonsProxyModel(QObject *parent) From f7caba5295680e713c45849a7b97654ac3123281 Mon Sep 17 00:00:00 2001 From: Bruce Brown <1st-BrainStormer@users.noreply.github.com> Date: Sat, 20 Jan 2018 11:17:47 -0800 Subject: [PATCH 51/52] de-bounce Midi::USBchanged signal. Debounced USBchanged signal to prevent pauses. --- 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 4b96bc2f2e..3cfa5338ba 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -503,7 +503,13 @@ public: } if (message->message == WM_DEVICECHANGE) { - Midi::USBchanged(); // re-scan the MIDI bus + const float MIN_DELTA_SECONDS = 2.0f; // de-bounce signal + static float lastTriggerTime = 0.0f; + const float deltaSeconds = secTimestampNow() - lastTriggerTime; + lastTriggerTime = secTimestampNow(); + if (deltaSeconds > MIN_DELTA_SECONDS) { + Midi::USBchanged(); // re-scan the MIDI bus + } } } return false; From 52f1803ea82a4a2682db6270654e9ebef6b10438 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 22 Jan 2018 09:55:39 -0800 Subject: [PATCH 52/52] code review feedback --- libraries/avatars/src/AvatarData.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 4ae1a94366..9924cc137e 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -81,7 +81,11 @@ size_t AvatarDataPacket::maxJointDataSize(size_t numJoints) { size_t AvatarDataPacket::maxJointDefaultPoseFlagsSize(size_t numJoints) { const size_t bitVectorSize = calcBitVectorSize((int)numJoints); size_t totalSize = sizeof(uint8_t); // numJoints - totalSize += 2 * bitVectorSize; + + // one set of bits for rotation and one for translation + const size_t NUM_BIT_VECTORS_IN_DEFAULT_POSE_FLAGS_SECTION = 2; + totalSize += NUM_BIT_VECTORS_IN_DEFAULT_POSE_FLAGS_SECTION * bitVectorSize; + return totalSize; }