From 08b07e57d0c2a1b27415dfa99c26fc498dd2736f Mon Sep 17 00:00:00 2001
From: Alexander Ivash <elderorb@gmail.com>
Date: Thu, 20 Sep 2018 00:42:39 +0300
Subject: [PATCH 01/31] FB12459 - Keyboard shouldn't lower when move between
 property values in Create app in HMD mode

---
 .../resources/html/raiseAndLowerKeyboard.js   |  9 +++++-
 .../qml/controls/FlickableWebViewCore.qml     | 30 +++++++++++++++++--
 .../qml/controls/TabletWebScreen.qml          |  2 ++
 .../resources/qml/controls/TabletWebView.qml  |  2 ++
 interface/resources/qml/controls/WebView.qml  |  2 ++
 5 files changed, 41 insertions(+), 4 deletions(-)

diff --git a/interface/resources/html/raiseAndLowerKeyboard.js b/interface/resources/html/raiseAndLowerKeyboard.js
index f40c0d7376..8cdb3c2327 100644
--- a/interface/resources/html/raiseAndLowerKeyboard.js
+++ b/interface/resources/html/raiseAndLowerKeyboard.js
@@ -18,6 +18,11 @@
     window.isKeyboardRaised = false;
     window.isNumericKeyboard = false;
     window.isPasswordField = false;
+    window.lastActiveElement = null;
+
+    function getActiveElement() {
+        return document.activeElement;
+    }
 
     function shouldSetPasswordField() {
         var nodeType = document.activeElement.type;
@@ -65,10 +70,11 @@
         var keyboardRaised = shouldRaiseKeyboard();
         var numericKeyboard = shouldSetNumeric();
         var passwordField = shouldSetPasswordField();
+        var activeElement = getActiveElement();
 
         if (isWindowFocused &&
             (keyboardRaised !== window.isKeyboardRaised || numericKeyboard !== window.isNumericKeyboard
-                || passwordField !== window.isPasswordField)) {
+                || passwordField !== window.isPasswordField || activeElement !== window.lastActiveElement)) {
 
             if (typeof EventBridge !== "undefined" && EventBridge !== null) {
                 EventBridge.emitWebEvent(
@@ -90,6 +96,7 @@
             window.isKeyboardRaised = keyboardRaised;
             window.isNumericKeyboard = numericKeyboard;
             window.isPasswordField = passwordField;
+            window.lastActiveElement = activeElement;
         }
     }, POLL_FREQUENCY);
 
diff --git a/interface/resources/qml/controls/FlickableWebViewCore.qml b/interface/resources/qml/controls/FlickableWebViewCore.qml
index 943f15e1de..bc5883425f 100644
--- a/interface/resources/qml/controls/FlickableWebViewCore.qml
+++ b/interface/resources/qml/controls/FlickableWebViewCore.qml
@@ -34,10 +34,34 @@ Item {
         webViewCore.stop();
     }
 
+    Timer {
+        id: delayedUnfocuser
+        repeat: false
+        interval: 200
+        onTriggered: {
+
+            // The idea behind this is to delay unfocusing, so that fast lower/raise will not result actual unfocusing.
+            // Fast lower/raise happens every time keyboard is being re-raised (see the code below in OffscreenQmlSurface::setKeyboardRaised)
+            //
+            // if (raised) {
+            //    item->setProperty("keyboardRaised", QVariant(!raised));
+            // }
+            //
+            // item->setProperty("keyboardRaised", QVariant(raised));
+            //
+
+            webViewCore.runJavaScript("if (document.activeElement) document.activeElement.blur();", function(result) {
+                console.log('unfocus completed: ', result);
+            });
+        }
+    }
+
     function unfocus() {
-        webViewCore.runJavaScript("if (document.activeElement) document.activeElement.blur();", function(result) {
-            console.log('unfocus completed: ', result);
-        });
+        delayedUnfocuser.start();
+    }
+
+    function stopUnfocus() {
+        delayedUnfocuser.stop();
     }
 
     function onLoadingChanged(loadRequest) {
diff --git a/interface/resources/qml/controls/TabletWebScreen.qml b/interface/resources/qml/controls/TabletWebScreen.qml
index bb037ad478..a33dc8a1d7 100644
--- a/interface/resources/qml/controls/TabletWebScreen.qml
+++ b/interface/resources/qml/controls/TabletWebScreen.qml
@@ -13,6 +13,8 @@ Item {
     onKeyboardRaisedChanged: {
         if(!keyboardRaised) {
             webroot.unfocus();
+        } else {
+            webroot.stopUnfocus();
         }
     }
     property bool punctuationMode: false
diff --git a/interface/resources/qml/controls/TabletWebView.qml b/interface/resources/qml/controls/TabletWebView.qml
index db695dbfb2..41127c2190 100644
--- a/interface/resources/qml/controls/TabletWebView.qml
+++ b/interface/resources/qml/controls/TabletWebView.qml
@@ -17,6 +17,8 @@ Item {
     onKeyboardRaisedChanged: {
         if(!keyboardRaised) {
             webroot.unfocus();
+        } else {
+            webroot.stopUnfocus();
         }
     }
     property bool punctuationMode: false
diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml
index 71bf69fdc8..8e7e5493ae 100644
--- a/interface/resources/qml/controls/WebView.qml
+++ b/interface/resources/qml/controls/WebView.qml
@@ -15,6 +15,8 @@ Item {
     onKeyboardRaisedChanged: {
         if(!keyboardRaised) {
             webroot.unfocus();
+        } else {
+            webroot.stopUnfocus();
         }
     }
     property bool punctuationMode: false

From ca4fe0682e1d555e4f492a6994115dd4ec4d3b91 Mon Sep 17 00:00:00 2001
From: David Rowe <david@ctrlaltstudio.com>
Date: Sat, 13 Oct 2018 10:22:05 +1300
Subject: [PATCH 02/31] Fix collisionless entities not being able to be far
 grabbed

---
 scripts/system/controllers/controllerDispatcher.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js
index db622a6724..417a2a49bb 100644
--- a/scripts/system/controllers/controllerDispatcher.js
+++ b/scripts/system/controllers/controllerDispatcher.js
@@ -425,7 +425,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
 
         this.leftPointer = this.pointerManager.createPointer(false, PickType.Ray, {
             joint: "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND",
-            filter: Picks.PICK_OVERLAYS | Picks.PICK_ENTITIES,
+            filter: Picks.PICK_OVERLAYS | Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_NONCOLLIDABLE,
             triggers: [{action: Controller.Standard.LTClick, button: "Focus"}, {action: Controller.Standard.LTClick, button: "Primary"}],
             posOffset: getGrabPointSphereOffset(Controller.Standard.LeftHand, true),
             hover: true,
@@ -435,7 +435,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
         });
         this.rightPointer = this.pointerManager.createPointer(false, PickType.Ray, {
             joint: "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND",
-            filter: Picks.PICK_OVERLAYS | Picks.PICK_ENTITIES,
+            filter: Picks.PICK_OVERLAYS | Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_NONCOLLIDABLE,
             triggers: [{action: Controller.Standard.RTClick, button: "Focus"}, {action: Controller.Standard.RTClick, button: "Primary"}],
             posOffset: getGrabPointSphereOffset(Controller.Standard.RightHand, true),
             hover: true,

From 3bc4581f82fa3b9e7bf94007223f151c54190bc0 Mon Sep 17 00:00:00 2001
From: David Rowe <david@ctrlaltstudio.com>
Date: Sat, 13 Oct 2018 10:22:27 +1300
Subject: [PATCH 03/31] Fix far grabbing collisionless entity removing
 collisionless property

---
 .../controllers/controllerModules/farActionGrabEntity.js   | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js
index 2e73526728..d9b63ad534 100644
--- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js
+++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js
@@ -102,7 +102,6 @@ Script.include("/~/system/libraries/Xform.js");
         this.potentialEntityWithContextOverlay = false;
         this.entityWithContextOverlay = false;
         this.contextOverlayTimer = false;
-        this.previousCollisionStatus = false;
         this.locked = false;
         this.highlightedEntity = null;
         this.reticleMinX = MARGIN;
@@ -490,7 +489,8 @@ Script.include("/~/system/libraries/Xform.js");
                         var targetProps = Entities.getEntityProperties(entityID, [
                             "dynamic", "shapeType", "position",
                             "rotation", "dimensions", "density",
-                            "userData", "locked", "type", "href"
+                            "userData", "locked", "type", "href",
+                            "collisionless"
                         ]);
                         if (targetProps.href !== "") {
                             AddressManager.handleLookupString(targetProps.href);
@@ -543,7 +543,8 @@ Script.include("/~/system/libraries/Xform.js");
                             var selectionTargetProps = Entities.getEntityProperties(targetEntityID, [
                                 "dynamic", "shapeType", "position",
                                 "rotation", "dimensions", "density",
-                                "userData", "locked", "type", "href"
+                                "userData", "locked", "type", "href",
+                                "collisionless"
                             ]);
 
                             var selectionTargetObject = new TargetObject(targetEntityID, selectionTargetProps);

From 7d3eca57bb4a721351162fbdc51ce67a5b875c9a Mon Sep 17 00:00:00 2001
From: David Rowe <david@ctrlaltstudio.com>
Date: Sat, 13 Oct 2018 10:22:41 +1300
Subject: [PATCH 04/31] Fix mouse pointer not recognizing or highlighting
 collisionless entities

---
 scripts/system/controllers/controllerDispatcher.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js
index 417a2a49bb..f5bf0f8e4c 100644
--- a/scripts/system/controllers/controllerDispatcher.js
+++ b/scripts/system/controllers/controllerDispatcher.js
@@ -467,7 +467,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
         });
         this.mouseRayPick = Pointers.createPointer(PickType.Ray, {
             joint: "Mouse",
-            filter: Picks.PICK_ENTITIES | Picks.PICK_OVERLAYS,
+            filter: Picks.PICK_OVERLAYS | Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_NONCOLLIDABLE,
             enabled: true
         });
         this.handleHandMessage = function(channel, data, sender) {

From 83ec346cc55c59444b5ddc4ea33af052121299d9 Mon Sep 17 00:00:00 2001
From: amantley <amantley@googlemail.com>
Date: Wed, 17 Oct 2018 16:42:29 -0700
Subject: [PATCH 05/31] changed the hand azimuth computation to be in spine2
 space.  this stops you from having a reversed direction when you sit down
 with hmd lean off and the hands are behind the origin in the z direction.

---
 interface/src/avatar/MyAvatar.cpp | 91 +++++++++++++++++++++++--------
 interface/src/avatar/MyAvatar.h   |  2 +-
 2 files changed, 70 insertions(+), 23 deletions(-)

diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index b347963cf1..e7d09b79a2 100755
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -475,16 +475,33 @@ void MyAvatar::update(float deltaTime) {
     // put the average hand azimuth into sensor space.
     // then mix it with head facing direction to determine rotation recenter
     if (getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() && getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND).isValid()) {
-        glm::vec3 handHipAzimuthWorldSpace = transformVectorFast(getTransform().getMatrix(), glm::vec3(_hipToHandController.x, 0.0f, _hipToHandController.y));
-        glm::mat4 sensorToWorldMat = getSensorToWorldMatrix();
-        glm::mat4 worldToSensorMat = glm::inverse(sensorToWorldMat);
-        glm::vec3 handHipAzimuthSensorSpace = transformVectorFast(worldToSensorMat, handHipAzimuthWorldSpace);
-        glm::vec2 normedHandHipAzimuthSensorSpace(0.0f, 1.0f);
-        if (glm::length(glm::vec2(handHipAzimuthSensorSpace.x, handHipAzimuthSensorSpace.z)) > 0.0f) {
-            normedHandHipAzimuthSensorSpace = glm::normalize(glm::vec2(handHipAzimuthSensorSpace.x, handHipAzimuthSensorSpace.z));
+        int spine2Index = _skeletonModel->getRig().indexOfJoint("Spine2");
+        if (!(spine2Index < 0)) {
+            // use the spine for the azimuth origin.
+            glm::quat spine2Rot = getAbsoluteJointRotationInObjectFrame(spine2Index);
+            glm::vec3 handHipAzimuthAvatarSpace = spine2Rot * glm::vec3(_hipToHandController.x, 0.0f, _hipToHandController.y);
+            glm::vec3 handHipAzimuthWorldSpace = transformVectorFast(getTransform().getMatrix(), handHipAzimuthAvatarSpace);
+            glm::mat4 sensorToWorldMat = getSensorToWorldMatrix();
+            glm::mat4 worldToSensorMat = glm::inverse(sensorToWorldMat);
+            glm::vec3 handHipAzimuthSensorSpace = transformVectorFast(worldToSensorMat, handHipAzimuthWorldSpace);
+            glm::vec2 normedHandHipAzimuthSensorSpace(0.0f, 1.0f);
+            if (glm::length(glm::vec2(handHipAzimuthSensorSpace.x, handHipAzimuthSensorSpace.z)) > 0.0f) {
+                normedHandHipAzimuthSensorSpace = glm::normalize(glm::vec2(handHipAzimuthSensorSpace.x, handHipAzimuthSensorSpace.z));
+            }
+            glm::vec2 headFacingPlusHandHipAzimuthMix = lerp(normedHandHipAzimuthSensorSpace, _headControllerFacing, PERCENTAGE_WEIGHT_HEAD_VS_SHOULDERS_AZIMUTH);
+            _headControllerFacingMovingAverage = lerp(_headControllerFacingMovingAverage, headFacingPlusHandHipAzimuthMix, tau);
+        } else {
+            glm::vec3 handHipAzimuthWorldSpace = transformVectorFast(getTransform().getMatrix(), glm::vec3(_hipToHandController.x, 0.0f, _hipToHandController.y));
+            glm::mat4 sensorToWorldMat = getSensorToWorldMatrix();
+            glm::mat4 worldToSensorMat = glm::inverse(sensorToWorldMat);
+            glm::vec3 handHipAzimuthSensorSpace = transformVectorFast(worldToSensorMat, handHipAzimuthWorldSpace);
+            glm::vec2 normedHandHipAzimuthSensorSpace(0.0f, 1.0f);
+            if (glm::length(glm::vec2(handHipAzimuthSensorSpace.x, handHipAzimuthSensorSpace.z)) > 0.0f) {
+                normedHandHipAzimuthSensorSpace = glm::normalize(glm::vec2(handHipAzimuthSensorSpace.x, handHipAzimuthSensorSpace.z));
+            }
+            glm::vec2 headFacingPlusHandHipAzimuthMix = lerp(normedHandHipAzimuthSensorSpace, _headControllerFacing, PERCENTAGE_WEIGHT_HEAD_VS_SHOULDERS_AZIMUTH);
+            _headControllerFacingMovingAverage = lerp(_headControllerFacingMovingAverage, headFacingPlusHandHipAzimuthMix, tau);
         }
-        glm::vec2 headFacingPlusHandHipAzimuthMix = lerp(normedHandHipAzimuthSensorSpace, _headControllerFacing, PERCENTAGE_WEIGHT_HEAD_VS_SHOULDERS_AZIMUTH);
-        _headControllerFacingMovingAverage = lerp(_headControllerFacingMovingAverage, headFacingPlusHandHipAzimuthMix, tau);
     } else {
         _headControllerFacingMovingAverage = lerp(_headControllerFacingMovingAverage, _headControllerFacing, tau);
     }
@@ -854,6 +871,20 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) {
 glm::vec2 MyAvatar::computeHandAzimuth() const {
     controller::Pose leftHandPoseAvatarSpace = getLeftHandPose();
     controller::Pose rightHandPoseAvatarSpace = getRightHandPose();
+
+    int spine2Index = _skeletonModel->getRig().indexOfJoint("Spine2");
+    glm::vec3 spine2Position(0.0f,0.0f,0.0f);
+    glm::quat spine2Rotation(0.0f, 0.0f, 0.0f, 1.0f);
+    if (!(spine2Index < 0)) {
+        // use the spine for the azimuth origin.
+       spine2Position = getAbsoluteJointTranslationInObjectFrame(spine2Index);
+       glm::quat spine2Rotation = getAbsoluteJointRotationInObjectFrame(spine2Index);
+    }
+    glm::vec3 rightHandOffset = rightHandPoseAvatarSpace.translation - spine2Position;
+    glm::vec3 leftHandOffset = leftHandPoseAvatarSpace.translation - spine2Position;
+    glm::vec3 rightHandSpine2Space = glm::inverse(spine2Rotation) * rightHandOffset;
+    glm::vec3 leftHandSpine2Space = glm::inverse(spine2Rotation) * leftHandOffset;
+
     controller::Pose headPoseAvatarSpace = getControllerPoseInAvatarFrame(controller::Action::HEAD);
     const float HALFWAY = 0.50f;
     glm::vec2 latestHipToHandController = _hipToHandController;
@@ -862,23 +893,25 @@ glm::vec2 MyAvatar::computeHandAzimuth() const {
         // we need the old azimuth reading to prevent flipping the facing direction 180
         // in the case where the hands go from being slightly less than 180 apart to slightly more than 180 apart.
         glm::vec2 oldAzimuthReading = _hipToHandController;
-        if ((glm::length(glm::vec2(rightHandPoseAvatarSpace.translation.x, rightHandPoseAvatarSpace.translation.z)) > 0.0f) && (glm::length(glm::vec2(leftHandPoseAvatarSpace.translation.x, leftHandPoseAvatarSpace.translation.z)) > 0.0f)) {
-            latestHipToHandController = lerp(glm::normalize(glm::vec2(rightHandPoseAvatarSpace.translation.x, rightHandPoseAvatarSpace.translation.z)), glm::normalize(glm::vec2(leftHandPoseAvatarSpace.translation.x, leftHandPoseAvatarSpace.translation.z)), HALFWAY);
+        if ((glm::length(glm::vec2(rightHandSpine2Space.x, rightHandSpine2Space.z)) > 0.0f) && (glm::length(glm::vec2(leftHandSpine2Space.x, leftHandSpine2Space.z)) > 0.0f)) {
+            latestHipToHandController = lerp(glm::normalize(glm::vec2(rightHandSpine2Space.x, rightHandSpine2Space.z)), glm::normalize(glm::vec2(leftHandSpine2Space.x, leftHandSpine2Space.z)), HALFWAY);
         } else {
-            latestHipToHandController = glm::vec2(0.0f, -1.0f);
+            latestHipToHandController = glm::vec2(0.0f, 1.0f);
         }
 
         glm::vec3 headLookAtAvatarSpace = transformVectorFast(headPoseAvatarSpace.getMatrix(), glm::vec3(0.0f, 0.0f, 1.0f));
-        glm::vec2 headAzimuthAvatarSpace = glm::vec2(headLookAtAvatarSpace.x, headLookAtAvatarSpace.z);
-        if (glm::length(headAzimuthAvatarSpace) > 0.0f) {
-            headAzimuthAvatarSpace = glm::normalize(headAzimuthAvatarSpace);
+        glm::vec3 headLookAtSpine2Space = glm::inverse(spine2Rotation) * headLookAtAvatarSpace;
+
+        glm::vec2 headAzimuthSpine2Space = glm::vec2(headLookAtSpine2Space.x, headLookAtSpine2Space.z);
+        if (glm::length(headAzimuthSpine2Space) > 0.0f) {
+            headAzimuthSpine2Space = glm::normalize(headAzimuthSpine2Space);
         } else {
-            headAzimuthAvatarSpace = -latestHipToHandController;
+            headAzimuthSpine2Space = -latestHipToHandController;
         }
 
         // check the angular distance from forward and back
         float cosForwardAngle = glm::dot(latestHipToHandController, oldAzimuthReading);
-        float cosHeadShoulder = glm::dot(-latestHipToHandController, headAzimuthAvatarSpace);
+        float cosHeadShoulder = glm::dot(-latestHipToHandController, headAzimuthSpine2Space);
         // if we are now closer to the 180 flip of the previous chest forward
         // then we negate our computed latestHipToHandController to keep the chest from flipping.
         // also check the head to shoulder azimuth difference if we negate.
@@ -3392,19 +3425,33 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const {
 }
 
 glm::mat4 MyAvatar::getSpine2RotationRigSpace() const {
+    int spine2Index = _skeletonModel->getRig().indexOfJoint("Spine2");
+    glm::quat spine2Rot = Quaternions::IDENTITY;
+    if (!(spine2Index < 0)) {
+        // use the spine for the azimuth origin.
+        spine2Rot = getAbsoluteJointRotationInObjectFrame(spine2Index);
+    }
+    glm::vec3 spine2UpAvatarSpace = spine2Rot * glm::vec3(0.0f, 1.0f, 0.0f);
+    glm::vec3 spine2FwdAvatarSpace = spine2Rot * glm::vec3(_hipToHandController.x, 0.0f, _hipToHandController.y);
 
     // static const glm::quat RIG_CHANGE_OF_BASIS = Quaternions::Y_180;
     // RIG_CHANGE_OF_BASIS * AVATAR_TO_RIG_ROTATION * inverse(RIG_CHANGE_OF_BASIS) = Quaternions::Y_180; //avatar Space;
     const glm::quat AVATAR_TO_RIG_ROTATION = Quaternions::Y_180;
-    glm::vec3 hipToHandRigSpace = AVATAR_TO_RIG_ROTATION * glm::vec3(_hipToHandController.x, 0.0f, _hipToHandController.y);
+    glm::vec3 spine2UpRigSpace = AVATAR_TO_RIG_ROTATION * spine2UpAvatarSpace;
+    glm::vec3 spine2FwdRigSpace = AVATAR_TO_RIG_ROTATION * spine2FwdAvatarSpace;
 
     glm::vec3 u, v, w;
-    if (glm::length(hipToHandRigSpace) > 0.0f) {
-        hipToHandRigSpace = glm::normalize(hipToHandRigSpace);
+    if (glm::length(spine2FwdRigSpace) > 0.0f) {
+        spine2FwdRigSpace = glm::normalize(spine2FwdRigSpace);
     } else {
-        hipToHandRigSpace = glm::vec3(0.0f, 0.0f, 1.0f);
+        spine2FwdRigSpace = glm::vec3(0.0f, 0.0f, 1.0f);
     }
-    generateBasisVectors(glm::vec3(0.0f,1.0f,0.0f), hipToHandRigSpace, u, v, w);
+    if (glm::length(spine2UpRigSpace) > 0.0f) {
+        spine2UpRigSpace = glm::normalize(spine2UpRigSpace);
+    } else {
+        spine2UpRigSpace = glm::vec3(0.0f, 1.0f, 0.0f);
+    }
+    generateBasisVectors(spine2UpRigSpace, spine2FwdRigSpace, u, v, w);
     glm::mat4 spine2RigSpace(glm::vec4(w, 0.0f), glm::vec4(u, 0.0f), glm::vec4(v, 0.0f), glm::vec4(glm::vec3(0.0f, 0.0f, 0.0f), 1.0f));
     return spine2RigSpace;
 }
diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h
index 16b765711a..85d6bea018 100644
--- a/interface/src/avatar/MyAvatar.h
+++ b/interface/src/avatar/MyAvatar.h
@@ -1688,7 +1688,7 @@ private:
     glm::vec2 _headControllerFacingMovingAverage { 0.0f, 0.0f };   // facing vector in xz plane (sensor space)
     glm::quat _averageHeadRotation { 0.0f, 0.0f, 0.0f, 1.0f };
 
-    glm::vec2 _hipToHandController { 0.0f, -1.0f };  // spine2 facing vector in xz plane (avatar space)
+    glm::vec2 _hipToHandController { 0.0f, 1.0f };  // spine2 facing vector in xz plane (spine2 space)
 
     float _currentStandingHeight { 0.0f };
     bool _resetMode { true };

From 8f4fafe4d0480a88d0e19ad4044df32ad69b7433 Mon Sep 17 00:00:00 2001
From: amantley <amantley@googlemail.com>
Date: Thu, 18 Oct 2018 08:44:01 -0700
Subject: [PATCH 06/31] put some smoothing on the spine2 rotation in
 myskeletonmodel.cpp

---
 interface/src/avatar/MyAvatar.cpp        | 2 +-
 interface/src/avatar/MySkeletonModel.cpp | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index e7d09b79a2..a5bb08658d 100755
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -878,7 +878,7 @@ glm::vec2 MyAvatar::computeHandAzimuth() const {
     if (!(spine2Index < 0)) {
         // use the spine for the azimuth origin.
        spine2Position = getAbsoluteJointTranslationInObjectFrame(spine2Index);
-       glm::quat spine2Rotation = getAbsoluteJointRotationInObjectFrame(spine2Index);
+       spine2Rotation = getAbsoluteJointRotationInObjectFrame(spine2Index);
     }
     glm::vec3 rightHandOffset = rightHandPoseAvatarSpace.translation - spine2Position;
     glm::vec3 leftHandOffset = leftHandPoseAvatarSpace.translation - spine2Position;
diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp
index 3084542472..900d532c88 100644
--- a/interface/src/avatar/MySkeletonModel.cpp
+++ b/interface/src/avatar/MySkeletonModel.cpp
@@ -261,7 +261,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
                 }
                 generateBasisVectors(up, fwd, u, v, w);
                 AnimPose newSpinePose(glm::mat4(glm::vec4(w, 0.0f), glm::vec4(u, 0.0f), glm::vec4(v, 0.0f), glm::vec4(glm::vec3(0.0f, 0.0f, 0.0f), 1.0f)));
-                currentSpine2Pose.rot() = newSpinePose.rot();
+                currentSpine2Pose.rot() = safeLerp(currentSpine2Pose.rot(), newSpinePose.rot(), 0.5f);
                 params.primaryControllerPoses[Rig::PrimaryControllerType_Spine2] = currentSpine2Pose;
                 params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] = (uint8_t)Rig::ControllerFlags::Enabled | (uint8_t)Rig::ControllerFlags::Estimated;
             }

From 12b03b0e07e3d69d727e44b401cfea953ddcde3f Mon Sep 17 00:00:00 2001
From: amantley <amantley@googlemail.com>
Date: Thu, 18 Oct 2018 10:52:13 -0700
Subject: [PATCH 07/31] tinkering with lerping the spine2 position in avatar
 space

---
 interface/src/avatar/MyAvatar.cpp | 3 ++-
 interface/src/avatar/MyAvatar.h   | 1 +
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index a5bb08658d..e8d87d30af 100755
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -877,7 +877,8 @@ glm::vec2 MyAvatar::computeHandAzimuth() const {
     glm::quat spine2Rotation(0.0f, 0.0f, 0.0f, 1.0f);
     if (!(spine2Index < 0)) {
         // use the spine for the azimuth origin.
-       spine2Position = getAbsoluteJointTranslationInObjectFrame(spine2Index);
+        spine2Position = getAbsoluteJointTranslationInObjectFrame(spine2Index);
+        _spine2PositionAvatarSpace = _spine2PositionAvatarSpace + ((0.05f) * (spine2Position - _spine2PositionAvatarSpace));
        spine2Rotation = getAbsoluteJointRotationInObjectFrame(spine2Index);
     }
     glm::vec3 rightHandOffset = rightHandPoseAvatarSpace.translation - spine2Position;
diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h
index 85d6bea018..f40f7e3023 100644
--- a/interface/src/avatar/MyAvatar.h
+++ b/interface/src/avatar/MyAvatar.h
@@ -1689,6 +1689,7 @@ private:
     glm::quat _averageHeadRotation { 0.0f, 0.0f, 0.0f, 1.0f };
 
     glm::vec2 _hipToHandController { 0.0f, 1.0f };  // spine2 facing vector in xz plane (spine2 space)
+    glm::vec3 _spine2PositionAvatarSpace { 0.0f, 0.0f, 0.0f };
 
     float _currentStandingHeight { 0.0f };
     bool _resetMode { true };

From 5a8a5bbc55612098b07bb182b84aeb5513785ad4 Mon Sep 17 00:00:00 2001
From: Gabriel Calero <gcalero1984@gmail.com>
Date: Mon, 22 Oct 2018 18:18:16 -0300
Subject: [PATCH 08/31] Set AEC enabled by default

---
 .../fragment/SettingsFragment.java            | 20 +++++++++++--------
 android/app/src/main/res/xml/settings.xml     |  3 ++-
 libraries/audio-client/src/AudioClient.cpp    |  4 ++--
 libraries/audio-client/src/AudioClient.h      |  1 +
 4 files changed, 17 insertions(+), 11 deletions(-)

diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java
index cc23665e72..58b6638531 100644
--- a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java
+++ b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java
@@ -3,8 +3,8 @@ package io.highfidelity.hifiinterface.fragment;
 import android.content.SharedPreferences;
 import android.media.audiofx.AcousticEchoCanceler;
 import android.os.Bundle;
-import android.preference.Preference;
 import android.preference.PreferenceFragment;
+import android.preference.PreferenceManager;
 import android.support.annotation.Nullable;
 
 import io.highfidelity.hifiinterface.R;
@@ -18,17 +18,23 @@ public class SettingsFragment extends PreferenceFragment implements SharedPrefer
     private final String HIFI_SETTINGS_AEC_KEY = "aec";
     private final String PREFERENCE_KEY_AEC = "aec";
 
+    private final boolean DEFAULT_AEC_ENABLED = true;
+
     @Override
     public void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         addPreferencesFromResource(R.xml.settings);
+        boolean aecAvailable = AcousticEchoCanceler.isAvailable();
+        PreferenceManager.setDefaultValues(getContext(), R.xml.settings, false);
 
-        if (!AcousticEchoCanceler.isAvailable()) {
-            getPreferenceScreen().getPreferenceManager().findPreference("aec").setEnabled(false);
+        if (!aecAvailable) {
+            findPreference(PREFERENCE_KEY_AEC).setEnabled(false);
+            updateHifiSetting(HIFI_SETTINGS_ANDROID_GROUP, HIFI_SETTINGS_AEC_KEY, false);
         }
 
         getPreferenceScreen().getSharedPreferences().edit().putBoolean(PREFERENCE_KEY_AEC,
-                getHifiSettingBoolean(HIFI_SETTINGS_ANDROID_GROUP, HIFI_SETTINGS_AEC_KEY, false));
+                aecAvailable && getHifiSettingBoolean(HIFI_SETTINGS_ANDROID_GROUP, HIFI_SETTINGS_AEC_KEY, DEFAULT_AEC_ENABLED)).commit();
+
     }
 
     public static SettingsFragment newInstance() {
@@ -46,15 +52,13 @@ public class SettingsFragment extends PreferenceFragment implements SharedPrefer
     public void onPause() {
         super.onPause();
         getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
-
     }
 
     @Override
     public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
-        Preference pref = findPreference(key);
         switch (key) {
-            case "aec":
-                updateHifiSetting(HIFI_SETTINGS_ANDROID_GROUP, HIFI_SETTINGS_AEC_KEY, sharedPreferences.getBoolean(key, false));
+            case PREFERENCE_KEY_AEC:
+                updateHifiSetting(HIFI_SETTINGS_ANDROID_GROUP, HIFI_SETTINGS_AEC_KEY, sharedPreferences.getBoolean(key, DEFAULT_AEC_ENABLED));
                 break;
             default:
                 break;
diff --git a/android/app/src/main/res/xml/settings.xml b/android/app/src/main/res/xml/settings.xml
index 5ec47b1aff..934d34ba73 100644
--- a/android/app/src/main/res/xml/settings.xml
+++ b/android/app/src/main/res/xml/settings.xml
@@ -6,6 +6,7 @@
         <SwitchPreference
             android:key="aec"
             android:title="@string/AEC"
-            android:summary="@string/acoustic_echo_cancellation" />
+            android:summary="@string/acoustic_echo_cancellation"
+            android:defaultValue="true" />
     </PreferenceCategory>
 </PreferenceScreen>
\ No newline at end of file
diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp
index d00bc29054..abe8f628f7 100644
--- a/libraries/audio-client/src/AudioClient.cpp
+++ b/libraries/audio-client/src/AudioClient.cpp
@@ -465,7 +465,7 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) {
 
 #if defined (Q_OS_ANDROID)
     if (mode == QAudio::AudioInput) {
-        Setting::Handle<bool> enableAEC(SETTING_AEC_KEY, false);
+        Setting::Handle<bool> enableAEC(SETTING_AEC_KEY, DEFAULT_AEC_ENABLED);
         bool aecEnabled = enableAEC.get();
         auto audioClient = DependencyManager::get<AudioClient>();
         bool headsetOn = audioClient? audioClient->isHeadsetPluggedIn() : false;
@@ -1673,7 +1673,7 @@ void AudioClient::setHeadsetPluggedIn(bool pluggedIn) {
             QThread::msleep(200);
         }
 
-        Setting::Handle<bool> enableAEC(SETTING_AEC_KEY, false);
+        Setting::Handle<bool> enableAEC(SETTING_AEC_KEY, DEFAULT_AEC_ENABLED);
         bool aecEnabled = enableAEC.get();
 
         if ((pluggedIn || !aecEnabled) && _inputDeviceInfo.deviceName() != VOICE_RECOGNITION) {
diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h
index 5e7f1fb8a0..057f477777 100644
--- a/libraries/audio-client/src/AudioClient.h
+++ b/libraries/audio-client/src/AudioClient.h
@@ -69,6 +69,7 @@
 #define VOICE_COMMUNICATION "voicecommunication"
 
 #define SETTING_AEC_KEY "Android/aec"
+#define DEFAULT_AEC_ENABLED true
 #endif
 
 class QAudioInput;

From 5063d0b527fc6f7b4d0a0721860ec5be9a78cbb2 Mon Sep 17 00:00:00 2001
From: amantley <amantley@googlemail.com>
Date: Tue, 23 Oct 2018 16:51:56 -0700
Subject: [PATCH 09/31] changed handAzimuth to non const function

---
 interface/src/avatar/MyAvatar.cpp | 2 +-
 interface/src/avatar/MyAvatar.h   | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index f75f3fbdfc..ac919d4420 100755
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -997,7 +997,7 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) {
 
 // Find the vector halfway between the hip to hand azimuth vectors
 // This midpoint hand azimuth is in Avatar space
-glm::vec2 MyAvatar::computeHandAzimuth() const {
+glm::vec2 MyAvatar::computeHandAzimuth() {
     controller::Pose leftHandPoseAvatarSpace = getLeftHandPose();
     controller::Pose rightHandPoseAvatarSpace = getRightHandPose();
 
diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h
index 6783cb8995..1be07a244e 100644
--- a/interface/src/avatar/MyAvatar.h
+++ b/interface/src/avatar/MyAvatar.h
@@ -337,7 +337,7 @@ public:
     void updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix);
 
     // compute the hip to hand average azimuth.
-    glm::vec2 computeHandAzimuth() const;
+    glm::vec2 computeHandAzimuth();
 
     // read the location of a hand controller and save the transform
     void updateJointFromController(controller::Action poseKey, ThreadSafeValueCache<glm::mat4>& matrixCache);

From 370801bd85a2f753453082c7cffb78b21b5756d0 Mon Sep 17 00:00:00 2001
From: amantley <amantley@googlemail.com>
Date: Wed, 24 Oct 2018 16:23:05 -0700
Subject: [PATCH 10/31] removed _spine2position variable and added handling for
 when there is no spine2 joint.  In that case the head will do the recentering
 rotation and the shoulders will not move with the hand azimuth

---
 interface/src/avatar/MyAvatar.cpp        | 72 ++++++++++--------------
 interface/src/avatar/MyAvatar.h          |  3 +-
 interface/src/avatar/MySkeletonModel.cpp |  3 +-
 3 files changed, 33 insertions(+), 45 deletions(-)

diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index ac919d4420..e4ff88974a 100755
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -538,33 +538,24 @@ void MyAvatar::update(float deltaTime) {
 
     // put the average hand azimuth into sensor space.
     // then mix it with head facing direction to determine rotation recenter
-    if (getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() && getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND).isValid()) {
-        int spine2Index = _skeletonModel->getRig().indexOfJoint("Spine2");
-        if (!(spine2Index < 0)) {
-            // use the spine for the azimuth origin.
-            glm::quat spine2Rot = getAbsoluteJointRotationInObjectFrame(spine2Index);
-            glm::vec3 handHipAzimuthAvatarSpace = spine2Rot * glm::vec3(_hipToHandController.x, 0.0f, _hipToHandController.y);
-            glm::vec3 handHipAzimuthWorldSpace = transformVectorFast(getTransform().getMatrix(), handHipAzimuthAvatarSpace);
-            glm::mat4 sensorToWorldMat = getSensorToWorldMatrix();
-            glm::mat4 worldToSensorMat = glm::inverse(sensorToWorldMat);
-            glm::vec3 handHipAzimuthSensorSpace = transformVectorFast(worldToSensorMat, handHipAzimuthWorldSpace);
-            glm::vec2 normedHandHipAzimuthSensorSpace(0.0f, 1.0f);
-            if (glm::length(glm::vec2(handHipAzimuthSensorSpace.x, handHipAzimuthSensorSpace.z)) > 0.0f) {
-                normedHandHipAzimuthSensorSpace = glm::normalize(glm::vec2(handHipAzimuthSensorSpace.x, handHipAzimuthSensorSpace.z));
-            }
+    int spine2Index = _skeletonModel->getRig().indexOfJoint("Spine2");
+    if (getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() && getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND).isValid() && !(spine2Index < 0)) {
+
+        // use the spine for the azimuth origin.
+        glm::quat spine2Rot = getAbsoluteJointRotationInObjectFrame(spine2Index);
+        glm::vec3 handHipAzimuthAvatarSpace = spine2Rot * glm::vec3(_hipToHandController.x, 0.0f, _hipToHandController.y);
+        glm::vec3 handHipAzimuthWorldSpace = transformVectorFast(getTransform().getMatrix(), handHipAzimuthAvatarSpace);
+        glm::mat4 sensorToWorldMat = getSensorToWorldMatrix();
+        glm::mat4 worldToSensorMat = glm::inverse(sensorToWorldMat);
+        glm::vec3 handHipAzimuthSensorSpace = transformVectorFast(worldToSensorMat, handHipAzimuthWorldSpace);
+        glm::vec2 normedHandHipAzimuthSensorSpace(0.0f, 1.0f);
+        if (glm::length(glm::vec2(handHipAzimuthSensorSpace.x, handHipAzimuthSensorSpace.z)) > 0.0f) {
+            normedHandHipAzimuthSensorSpace = glm::normalize(glm::vec2(handHipAzimuthSensorSpace.x, handHipAzimuthSensorSpace.z));
             glm::vec2 headFacingPlusHandHipAzimuthMix = lerp(normedHandHipAzimuthSensorSpace, _headControllerFacing, PERCENTAGE_WEIGHT_HEAD_VS_SHOULDERS_AZIMUTH);
             _headControllerFacingMovingAverage = lerp(_headControllerFacingMovingAverage, headFacingPlusHandHipAzimuthMix, tau);
         } else {
-            glm::vec3 handHipAzimuthWorldSpace = transformVectorFast(getTransform().getMatrix(), glm::vec3(_hipToHandController.x, 0.0f, _hipToHandController.y));
-            glm::mat4 sensorToWorldMat = getSensorToWorldMatrix();
-            glm::mat4 worldToSensorMat = glm::inverse(sensorToWorldMat);
-            glm::vec3 handHipAzimuthSensorSpace = transformVectorFast(worldToSensorMat, handHipAzimuthWorldSpace);
-            glm::vec2 normedHandHipAzimuthSensorSpace(0.0f, 1.0f);
-            if (glm::length(glm::vec2(handHipAzimuthSensorSpace.x, handHipAzimuthSensorSpace.z)) > 0.0f) {
-                normedHandHipAzimuthSensorSpace = glm::normalize(glm::vec2(handHipAzimuthSensorSpace.x, handHipAzimuthSensorSpace.z));
-            }
-            glm::vec2 headFacingPlusHandHipAzimuthMix = lerp(normedHandHipAzimuthSensorSpace, _headControllerFacing, PERCENTAGE_WEIGHT_HEAD_VS_SHOULDERS_AZIMUTH);
-            _headControllerFacingMovingAverage = lerp(_headControllerFacingMovingAverage, headFacingPlusHandHipAzimuthMix, tau);
+            // use head facing if the chest arms vector is up or down.
+            _headControllerFacingMovingAverage = lerp(_headControllerFacingMovingAverage, _headControllerFacing, tau);
         }
     } else {
         _headControllerFacingMovingAverage = lerp(_headControllerFacingMovingAverage, _headControllerFacing, tau);
@@ -996,30 +987,27 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) {
 }
 
 // Find the vector halfway between the hip to hand azimuth vectors
-// This midpoint hand azimuth is in Avatar space
-glm::vec2 MyAvatar::computeHandAzimuth() {
+// This midpoint hand azimuth is in Spine2 space
+glm::vec2 MyAvatar::computeHandAzimuth() const {
     controller::Pose leftHandPoseAvatarSpace = getLeftHandPose();
     controller::Pose rightHandPoseAvatarSpace = getRightHandPose();
-
-    int spine2Index = _skeletonModel->getRig().indexOfJoint("Spine2");
-    glm::vec3 spine2Position(0.0f,0.0f,0.0f);
-    glm::quat spine2Rotation(0.0f, 0.0f, 0.0f, 1.0f);
-    if (!(spine2Index < 0)) {
-        // use the spine for the azimuth origin.
-        spine2Position = getAbsoluteJointTranslationInObjectFrame(spine2Index);
-        _spine2PositionAvatarSpace = _spine2PositionAvatarSpace + ((0.05f) * (spine2Position - _spine2PositionAvatarSpace));
-       spine2Rotation = getAbsoluteJointRotationInObjectFrame(spine2Index);
-    }
-    glm::vec3 rightHandOffset = rightHandPoseAvatarSpace.translation - spine2Position;
-    glm::vec3 leftHandOffset = leftHandPoseAvatarSpace.translation - spine2Position;
-    glm::vec3 rightHandSpine2Space = glm::inverse(spine2Rotation) * rightHandOffset;
-    glm::vec3 leftHandSpine2Space = glm::inverse(spine2Rotation) * leftHandOffset;
-
     controller::Pose headPoseAvatarSpace = getControllerPoseInAvatarFrame(controller::Action::HEAD);
     const float HALFWAY = 0.50f;
+    const float SPINE2_POSITION_FILTER = 0.05f;
+
     glm::vec2 latestHipToHandController = _hipToHandController;
 
-    if (leftHandPoseAvatarSpace.isValid() && rightHandPoseAvatarSpace.isValid() && headPoseAvatarSpace.isValid()) {
+    int spine2Index = _skeletonModel->getRig().indexOfJoint("Spine2");
+    if (leftHandPoseAvatarSpace.isValid() && rightHandPoseAvatarSpace.isValid() && headPoseAvatarSpace.isValid() && !(spine2Index < 0)) {
+
+        glm::vec3 spine2Position = getAbsoluteJointTranslationInObjectFrame(spine2Index);
+        glm::quat spine2Rotation = getAbsoluteJointRotationInObjectFrame(spine2Index);
+
+        glm::vec3 rightHandOffset = rightHandPoseAvatarSpace.translation - spine2Position;
+        glm::vec3 leftHandOffset = leftHandPoseAvatarSpace.translation - spine2Position;
+        glm::vec3 rightHandSpine2Space = glm::inverse(spine2Rotation) * rightHandOffset;
+        glm::vec3 leftHandSpine2Space = glm::inverse(spine2Rotation) * leftHandOffset;
+
         // we need the old azimuth reading to prevent flipping the facing direction 180
         // in the case where the hands go from being slightly less than 180 apart to slightly more than 180 apart.
         glm::vec2 oldAzimuthReading = _hipToHandController;
diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h
index 1be07a244e..85ad6fe7a4 100644
--- a/interface/src/avatar/MyAvatar.h
+++ b/interface/src/avatar/MyAvatar.h
@@ -337,7 +337,7 @@ public:
     void updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix);
 
     // compute the hip to hand average azimuth.
-    glm::vec2 computeHandAzimuth();
+    glm::vec2 computeHandAzimuth() const;
 
     // read the location of a hand controller and save the transform
     void updateJointFromController(controller::Action poseKey, ThreadSafeValueCache<glm::mat4>& matrixCache);
@@ -1732,7 +1732,6 @@ private:
     glm::quat _averageHeadRotation { 0.0f, 0.0f, 0.0f, 1.0f };
 
     glm::vec2 _hipToHandController { 0.0f, 1.0f };  // spine2 facing vector in xz plane (spine2 space)
-    glm::vec3 _spine2PositionAvatarSpace { 0.0f, 0.0f, 0.0f };
 
     float _currentStandingHeight { 0.0f };
     bool _resetMode { true };
diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp
index 0cda71ca77..b5a495d296 100644
--- a/interface/src/avatar/MySkeletonModel.cpp
+++ b/interface/src/avatar/MySkeletonModel.cpp
@@ -243,6 +243,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
                 myAvatar->getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() &&
                 !(params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] & (uint8_t)Rig::ControllerFlags::Enabled)) {
 
+            const float SPINE2_ROTATION_FILTER = 0.5f;
             AnimPose currentSpine2Pose;
             AnimPose currentHeadPose;
             AnimPose currentHipsPose;
@@ -262,7 +263,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
                 }
                 generateBasisVectors(up, fwd, u, v, w);
                 AnimPose newSpinePose(glm::mat4(glm::vec4(w, 0.0f), glm::vec4(u, 0.0f), glm::vec4(v, 0.0f), glm::vec4(glm::vec3(0.0f, 0.0f, 0.0f), 1.0f)));
-                currentSpine2Pose.rot() = safeLerp(currentSpine2Pose.rot(), newSpinePose.rot(), 0.5f);
+                currentSpine2Pose.rot() = safeLerp(currentSpine2Pose.rot(), newSpinePose.rot(), SPINE2_ROTATION_FILTER);
                 params.primaryControllerPoses[Rig::PrimaryControllerType_Spine2] = currentSpine2Pose;
                 params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] = (uint8_t)Rig::ControllerFlags::Enabled | (uint8_t)Rig::ControllerFlags::Estimated;
             }

From 7fe53d4399f8a24043b6154c7d7785bc321bf7af Mon Sep 17 00:00:00 2001
From: amantley <amantley@googlemail.com>
Date: Wed, 24 Oct 2018 16:32:18 -0700
Subject: [PATCH 11/31] removed extra const float value

---
 interface/src/avatar/MyAvatar.cpp | 1 -
 1 file changed, 1 deletion(-)

diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index e4ff88974a..15ddb2fe87 100755
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -993,7 +993,6 @@ glm::vec2 MyAvatar::computeHandAzimuth() const {
     controller::Pose rightHandPoseAvatarSpace = getRightHandPose();
     controller::Pose headPoseAvatarSpace = getControllerPoseInAvatarFrame(controller::Action::HEAD);
     const float HALFWAY = 0.50f;
-    const float SPINE2_POSITION_FILTER = 0.05f;
 
     glm::vec2 latestHipToHandController = _hipToHandController;
 

From 51d56ddf22ffd2cedb2ab26f9da6ffc981debf02 Mon Sep 17 00:00:00 2001
From: David Rowe <david@ctrlaltstudio.com>
Date: Fri, 26 Oct 2018 12:17:04 +1300
Subject: [PATCH 12/31] Make tablet page indicators clickable to scroll tablet

---
 .../resources/qml/hifi/tablet/TabletHome.qml   | 18 +++++++++++++++---
 1 file changed, 15 insertions(+), 3 deletions(-)

diff --git a/interface/resources/qml/hifi/tablet/TabletHome.qml b/interface/resources/qml/hifi/tablet/TabletHome.qml
index 1922b02f93..c2dd847821 100644
--- a/interface/resources/qml/hifi/tablet/TabletHome.qml
+++ b/interface/resources/qml/hifi/tablet/TabletHome.qml
@@ -251,17 +251,29 @@ Item {
                 height: 15
 
                 Rectangle {
+                    property bool isHovered: false
                     anchors.centerIn: parent
-                    opacity: index === pageIndicator.currentIndex ? 0.95 : 0.45
-                    implicitWidth: index === pageIndicator.currentIndex ? 15 : 10
+                    opacity: index === pageIndicator.currentIndex || isHovered ? 0.95 : 0.45
+                    implicitWidth: index === pageIndicator.currentIndex || isHovered ? 15 : 10
                     implicitHeight: implicitWidth
                     radius: width/2
-                    color: "white"
+                    color: isHovered && index !== pageIndicator.currentIndex ? "#1fc6a6" : "white"
                     Behavior on opacity {
                         OpacityAnimator {
                             duration: 100
                         }
                     }
+
+                    MouseArea {
+                        anchors.centerIn: parent
+                        width: 20
+                        height: 30 // Make it easier to target with laser.
+                        hoverEnabled: true
+                        enabled: true
+                        onEntered: parent.isHovered = true;
+                        onExited: parent.isHovered = false;
+                        onClicked: swipeView.currentIndex = index;
+                    }
                 }
             }
 

From 1a129335dd93d44253d3e11bcf77cc633a0f007e Mon Sep 17 00:00:00 2001
From: David Rowe <david@ctrlaltstudio.com>
Date: Tue, 30 Oct 2018 19:57:07 +1300
Subject: [PATCH 13/31] Fix Goto app cards staying highlighted if move off
 dialog while dragging

---
 interface/resources/qml/hifi/Card.qml | 18 +++++++++++++-----
 interface/resources/qml/hifi/Feed.qml |  2 ++
 2 files changed, 15 insertions(+), 5 deletions(-)

diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml
index 83bf1e2c54..e848dcd862 100644
--- a/interface/resources/qml/hifi/Card.qml
+++ b/interface/resources/qml/hifi/Card.qml
@@ -49,6 +49,7 @@ Item {
     property string defaultThumbnail: Qt.resolvedUrl("../../images/default-domain.gif");
     property int shadowHeight: 10;
     property bool hovered: false
+    property bool scrolling: false
 
     HifiConstants { id: hifi }
 
@@ -236,11 +237,12 @@ Item {
     property var hoverThunk: function () { };
     property var unhoverThunk: function () { };
     Rectangle {
-        anchors.fill: parent;
-        visible: root.hovered
-        color: "transparent";
-        border.width: 4; border.color: hifiStyleConstants.colors.primaryHighlight;
-        z: 1;
+        anchors.fill: parent
+        visible: root.hovered && !root.scrolling
+        color: "transparent"
+        border.width: 4
+        border.color: hifiStyleConstants.colors.primaryHighlight
+        z: 1
     }
     MouseArea {
         anchors.fill: parent;
@@ -255,6 +257,12 @@ Item {
             hoverThunk();
         }
         onExited: unhoverThunk();
+        onCanceled: unhoverThunk();
+    }
+    MouseArea {
+        // This second mouse area causes onEntered to fire on the first if you scroll just a little and the cursor stays on
+        // the original card. I.e., the original card is re-highlighted if the cursor is on it after scrolling finishes.
+        anchors.fill: parent
     }
     StateImage {
         id: actionIcon;
diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml
index 346481fe1f..a8337530c6 100644
--- a/interface/resources/qml/hifi/Feed.qml
+++ b/interface/resources/qml/hifi/Feed.qml
@@ -141,6 +141,8 @@ Column {
             textSizeSmall: root.textSizeSmall;
             stackShadowNarrowing: root.stackShadowNarrowing;
             shadowHeight: root.stackedCardShadowHeight;
+            scrolling: scroll.moving
+
             hoverThunk: function () { 
                 hovered = true;
                 if(root.autoScrollTimerEnabled) {

From ae2715af31f5469f1cacac45dc175a541d84a08a Mon Sep 17 00:00:00 2001
From: David Rowe <david@ctrlaltstudio.com>
Date: Thu, 1 Nov 2018 08:57:12 +1300
Subject: [PATCH 14/31] Add option to start up at home location

---
 interface/src/Application.cpp | 12 +++++++++++-
 interface/src/Menu.cpp        |  8 ++++++++
 interface/src/Menu.h          |  3 +++
 3 files changed, 22 insertions(+), 1 deletion(-)

diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 7cd91d76a0..04d3a07ad4 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -3503,7 +3503,17 @@ void Application::handleSandboxStatus(QNetworkReply* reply) {
 
     } else {
 #if !defined(Q_OS_ANDROID)
-        qCDebug(interfaceapp) << "Not first run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("previous location") : addressLookupString);
+        QString goingTo = "";
+        if (addressLookupString.isEmpty()) {
+            if (Menu::getInstance()->isOptionChecked(MenuOption::HomeLocation)) {
+                auto locationBookmarks = DependencyManager::get<LocationBookmarks>();
+                addressLookupString = locationBookmarks->addressForBookmark(LocationBookmarks::HOME_BOOKMARK);
+                goingTo = "home location";
+            } else {
+                goingTo = "previous location";
+            }
+        }
+        qCDebug(interfaceapp) << "Not first run... going to" << qPrintable(!goingTo.isEmpty() ? goingTo : addressLookupString);
         DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
         sentTo = SENT_TO_PREVIOUS_LOCATION;
 #endif
diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp
index 539bdabe7d..df6de25118 100644
--- a/interface/src/Menu.cpp
+++ b/interface/src/Menu.cpp
@@ -226,6 +226,14 @@ Menu::Menu() {
     addActionToQMenuAndActionHash(navigateMenu, MenuOption::CopyPath, 0,
         addressManager.data(), SLOT(copyPath()));
 
+    // Navigate > Start-up Location
+    MenuWrapper* startupLocationMenu = navigateMenu->addMenu(MenuOption::StartUpLocation);
+    QActionGroup* startupLocatiopnGroup = new QActionGroup(startupLocationMenu);
+    startupLocatiopnGroup->setExclusive(true);
+    startupLocatiopnGroup->addAction(addCheckableActionToQMenuAndActionHash(startupLocationMenu, MenuOption::HomeLocation, 0, 
+        false));
+    startupLocatiopnGroup->addAction(addCheckableActionToQMenuAndActionHash(startupLocationMenu, MenuOption::LastLocation, 0, 
+        true));
 
     // Settings menu ----------------------------------
     MenuWrapper* settingsMenu = addMenu("Settings");
diff --git a/interface/src/Menu.h b/interface/src/Menu.h
index 1e9955a760..885ef08ce9 100644
--- a/interface/src/Menu.h
+++ b/interface/src/Menu.h
@@ -117,9 +117,11 @@ namespace MenuOption {
     const QString FrameTimer = "Show Timer";
     const QString FullscreenMirror = "Mirror";
     const QString Help = "Help...";
+    const QString HomeLocation = "Home";
     const QString IncreaseAvatarSize = "Increase Avatar Size";
     const QString IndependentMode = "Independent Mode";
     const QString ActionMotorControl = "Enable Default Motor Control";
+    const QString LastLocation = "Last Location";
     const QString LoadScript = "Open and Run Script File...";
     const QString LoadScriptURL = "Open and Run Script from URL...";
     const QString LodTools = "LOD Tools";
@@ -197,6 +199,7 @@ namespace MenuOption {
     const QString SimulateEyeTracking = "Simulate";
     const QString SMIEyeTracking = "SMI Eye Tracking";
     const QString SparseTextureManagement = "Enable Sparse Texture Management";
+    const QString StartUpLocation = "Start-Up Location";
     const QString Stats = "Show Statistics";
     const QString AnimStats = "Show Animation Stats";
     const QString StopAllScripts = "Stop All Scripts";

From db45aced6e0f932d853c1813989a03e5c769986e Mon Sep 17 00:00:00 2001
From: Gabriel Calero <gcalero1984@gmail.com>
Date: Mon, 5 Nov 2018 16:53:03 -0300
Subject: [PATCH 15/31] Mute when entering a domain

---
 android/app/CMakeLists.txt          | 2 +-
 android/app/src/main/cpp/native.cpp | 5 +++++
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/android/app/CMakeLists.txt b/android/app/CMakeLists.txt
index 9930a9e152..0138b45146 100644
--- a/android/app/CMakeLists.txt
+++ b/android/app/CMakeLists.txt
@@ -1,6 +1,6 @@
 set(TARGET_NAME native-lib)
 setup_hifi_library()
-link_hifi_libraries(shared task networking gl gpu qml image fbx render-utils physics entities octree ${PLATFORM_GL_BACKEND})
+link_hifi_libraries(shared task networking gl gpu qml image fbx render-utils physics entities octree audio-client ${PLATFORM_GL_BACKEND})
 target_opengl()
 target_bullet()
 
diff --git a/android/app/src/main/cpp/native.cpp b/android/app/src/main/cpp/native.cpp
index c7bca428e9..2f05a54fd5 100644
--- a/android/app/src/main/cpp/native.cpp
+++ b/android/app/src/main/cpp/native.cpp
@@ -21,6 +21,7 @@
 
 #include <AddressManager.h>
 #include "AndroidHelper.h"
+#include "../../../../../libraries/audio-client/src/AudioClient.h"
 #include <udt/PacketHeaders.h>
 #include <SettingHandle.h>
 
@@ -210,11 +211,15 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnDest
 JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeGotoUrl(JNIEnv* env, jobject obj, jstring url) {
     QAndroidJniObject jniUrl("java/lang/String", "(Ljava/lang/String;)V", url);
     DependencyManager::get<AddressManager>()->loadSettings(jniUrl.toString());
+    auto audioClient = DependencyManager::get<AudioClient>();
+    audioClient->setMuted(true, true);
 }
 
 JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeGoToUser(JNIEnv* env, jobject obj, jstring username) {
     QAndroidJniObject jniUsername("java/lang/String", "(Ljava/lang/String;)V", username);
     DependencyManager::get<AddressManager>()->goToUser(jniUsername.toString(), false);
+    auto audioClient = DependencyManager::get<AudioClient>();
+    audioClient->setMuted(true, true);
 }
 
 JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnPause(JNIEnv* env, jobject obj) {

From 611881e0024a10638aadd8c78555f4ba2800a697 Mon Sep 17 00:00:00 2001
From: Gabriel Calero <gcalero1984@gmail.com>
Date: Mon, 5 Nov 2018 18:35:57 -0300
Subject: [PATCH 16/31] Mute when entering a domain

---
 android/app/CMakeLists.txt          | 2 +-
 android/app/src/main/cpp/native.cpp | 7 ++-----
 interface/src/AndroidHelper.cpp     | 8 ++++++++
 interface/src/AndroidHelper.h       | 1 +
 4 files changed, 12 insertions(+), 6 deletions(-)

diff --git a/android/app/CMakeLists.txt b/android/app/CMakeLists.txt
index 0138b45146..9930a9e152 100644
--- a/android/app/CMakeLists.txt
+++ b/android/app/CMakeLists.txt
@@ -1,6 +1,6 @@
 set(TARGET_NAME native-lib)
 setup_hifi_library()
-link_hifi_libraries(shared task networking gl gpu qml image fbx render-utils physics entities octree audio-client ${PLATFORM_GL_BACKEND})
+link_hifi_libraries(shared task networking gl gpu qml image fbx render-utils physics entities octree ${PLATFORM_GL_BACKEND})
 target_opengl()
 target_bullet()
 
diff --git a/android/app/src/main/cpp/native.cpp b/android/app/src/main/cpp/native.cpp
index 2f05a54fd5..760fdcb0b5 100644
--- a/android/app/src/main/cpp/native.cpp
+++ b/android/app/src/main/cpp/native.cpp
@@ -21,7 +21,6 @@
 
 #include <AddressManager.h>
 #include "AndroidHelper.h"
-#include "../../../../../libraries/audio-client/src/AudioClient.h"
 #include <udt/PacketHeaders.h>
 #include <SettingHandle.h>
 
@@ -211,15 +210,13 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnDest
 JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeGotoUrl(JNIEnv* env, jobject obj, jstring url) {
     QAndroidJniObject jniUrl("java/lang/String", "(Ljava/lang/String;)V", url);
     DependencyManager::get<AddressManager>()->loadSettings(jniUrl.toString());
-    auto audioClient = DependencyManager::get<AudioClient>();
-    audioClient->setMuted(true, true);
+    AndroidHelper::instance().muteMic();
 }
 
 JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeGoToUser(JNIEnv* env, jobject obj, jstring username) {
     QAndroidJniObject jniUsername("java/lang/String", "(Ljava/lang/String;)V", username);
     DependencyManager::get<AddressManager>()->goToUser(jniUsername.toString(), false);
-    auto audioClient = DependencyManager::get<AudioClient>();
-    audioClient->setMuted(true, true);
+    AndroidHelper::instance().muteMic();
 }
 
 JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnPause(JNIEnv* env, jobject obj) {
diff --git a/interface/src/AndroidHelper.cpp b/interface/src/AndroidHelper.cpp
index ae6aa34cc9..708859c00a 100644
--- a/interface/src/AndroidHelper.cpp
+++ b/interface/src/AndroidHelper.cpp
@@ -73,6 +73,14 @@ void AndroidHelper::notifyHeadsetOn(bool pluggedIn) {
 #endif
 }
 
+void AndroidHelper::muteMic() {
+    auto audioClient = DependencyManager::get<AudioClient>();
+    if (audioClient) {
+        QMetaObject::invokeMethod(audioClient.data(), "setMuted", Q_ARG(bool, true), Q_ARG(bool, true));
+    }
+}
+
+
 void AndroidHelper::signup(QString email, QString username, QString password) {
     JSONCallbackParameters callbackParams;
     callbackParams.callbackReceiver = this;
diff --git a/interface/src/AndroidHelper.h b/interface/src/AndroidHelper.h
index f1917deca0..f0aaa84214 100644
--- a/interface/src/AndroidHelper.h
+++ b/interface/src/AndroidHelper.h
@@ -35,6 +35,7 @@ public:
     void performHapticFeedback(int duration);
     void processURL(const QString &url);
     void notifyHeadsetOn(bool pluggedIn);
+    void muteMic();
 
     AndroidHelper(AndroidHelper const&)  = delete;
     void operator=(AndroidHelper const&) = delete;

From 37c69ebe625000b61a95f79f95b21c09b9a22357 Mon Sep 17 00:00:00 2001
From: Clement <clement.brisset@gmail.com>
Date: Mon, 5 Nov 2018 17:18:28 -0800
Subject: [PATCH 17/31] Add audio soloing feature

---
 assignment-client/src/audio/AudioMixer.cpp    |  3 +-
 .../src/audio/AudioMixerClientData.cpp        | 22 ++++++
 .../src/audio/AudioMixerClientData.h          |  6 ++
 .../src/audio/AudioMixerSlave.cpp             |  4 +
 libraries/audio-client/src/AudioClient.cpp    |  8 +-
 libraries/audio-client/src/AudioClient.h      |  4 +-
 libraries/audio/src/AbstractAudioInterface.h  |  3 +
 libraries/audio/src/AudioSolo.cpp             | 77 +++++++++++++++++++
 libraries/audio/src/AudioSolo.h               | 38 +++++++++
 libraries/networking/src/udt/PacketHeaders.h  |  3 +-
 .../src/AudioScriptingInterface.h             | 22 +++++-
 11 files changed, 183 insertions(+), 7 deletions(-)
 create mode 100644 libraries/audio/src/AudioSolo.cpp
 create mode 100644 libraries/audio/src/AudioSolo.h

diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp
index afd4047c68..d6f893c42e 100644
--- a/assignment-client/src/audio/AudioMixer.cpp
+++ b/assignment-client/src/audio/AudioMixer.cpp
@@ -89,7 +89,8 @@ AudioMixer::AudioMixer(ReceivedMessage& message) :
             PacketType::NodeIgnoreRequest,
             PacketType::RadiusIgnoreRequest,
             PacketType::RequestsDomainListData,
-            PacketType::PerAvatarGainSet },
+            PacketType::PerAvatarGainSet,
+            PacketType::AudioSoloRequest },
             this, "queueAudioPacket");
 
     // packets whose consequences are global should be processed on the main thread
diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp
index 7e1420ef60..0aee185088 100644
--- a/assignment-client/src/audio/AudioMixerClientData.cpp
+++ b/assignment-client/src/audio/AudioMixerClientData.cpp
@@ -98,6 +98,9 @@ int AudioMixerClientData::processPackets(ConcurrentAddedStreams& addedStreams) {
             case PacketType::RadiusIgnoreRequest:
                 parseRadiusIgnoreRequest(packet, node);
                 break;
+            case PacketType::AudioSoloRequest:
+                parseSoloRequest(packet, node);
+                break;
             default:
                 Q_UNREACHABLE();
         }
@@ -295,6 +298,25 @@ void AudioMixerClientData::parseRadiusIgnoreRequest(QSharedPointer<ReceivedMessa
     }
 }
 
+
+void AudioMixerClientData::parseSoloRequest(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& node) {
+
+    bool addToSolo;
+    message->readPrimitive(&addToSolo);
+
+    while (message->getBytesLeftToRead()) {
+        // parse out the UUID being solod from the packet
+        QUuid solodUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
+
+        if (addToSolo) {
+            _solodNodes.push_back(solodUUID);
+        } else {
+            auto it = std::find(std::begin(_solodNodes), std::end(_solodNodes), solodUUID);
+            _solodNodes.erase(it);
+        }
+    }
+}
+
 AvatarAudioStream* AudioMixerClientData::getAvatarAudioStream() {
     auto it = std::find_if(_audioStreams.begin(), _audioStreams.end(), [](const SharedStreamPointer& stream){
         return stream->getStreamIdentifier().isNull();
diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h
index 610b258789..dd5681c537 100644
--- a/assignment-client/src/audio/AudioMixerClientData.h
+++ b/assignment-client/src/audio/AudioMixerClientData.h
@@ -65,6 +65,7 @@ public:
     void parsePerAvatarGainSet(ReceivedMessage& message, const SharedNodePointer& node);
     void parseNodeIgnoreRequest(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& node);
     void parseRadiusIgnoreRequest(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& node);
+    void parseSoloRequest(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& node);
 
     // attempt to pop a frame from each audio stream, and return the number of streams from this client
     int checkBuffersBeforeFrameSend();
@@ -150,6 +151,9 @@ public:
 
     const Node::IgnoredNodeIDs& getIgnoringNodeIDs() const { return _ignoringNodeIDs; }
 
+
+    const std::vector<QUuid>& getSolodNodes() const { return _solodNodes; }
+
     bool getHasReceivedFirstMix() const { return _hasReceivedFirstMix; }
     void setHasReceivedFirstMix(bool hasReceivedFirstMix) { _hasReceivedFirstMix = hasReceivedFirstMix; }
 
@@ -209,6 +213,8 @@ private:
 
     std::atomic_bool _isIgnoreRadiusEnabled { false };
 
+    std::vector<QUuid> _solodNodes;
+
     bool _hasReceivedFirstMix { false };
 };
 
diff --git a/assignment-client/src/audio/AudioMixerSlave.cpp b/assignment-client/src/audio/AudioMixerSlave.cpp
index 57bada47f1..7007e684cb 100644
--- a/assignment-client/src/audio/AudioMixerSlave.cpp
+++ b/assignment-client/src/audio/AudioMixerSlave.cpp
@@ -272,6 +272,10 @@ bool shouldBeSkipped(MixableStream& stream, const Node& listener,
         return true;
     }
 
+    if (!listenerData.getSolodNodes().empty()) {
+        return !contains(listenerData.getSolodNodes(), stream.nodeStreamID.nodeID);
+    }
+
     bool shouldCheckIgnoreBox = (listenerAudioStream.isIgnoreBoxEnabled() ||
                                  stream.positionalStream->isIgnoreBoxEnabled());
     if (shouldCheckIgnoreBox &&
diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp
index bdd6d0edc1..ace8f72875 100644
--- a/libraries/audio-client/src/AudioClient.cpp
+++ b/libraries/audio-client/src/AudioClient.cpp
@@ -270,7 +270,8 @@ AudioClient::AudioClient() :
 
     configureReverb();
 
-    auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
+    auto nodeList = DependencyManager::get<NodeList>();
+    auto& packetReceiver = nodeList->getPacketReceiver();
     packetReceiver.registerListener(PacketType::AudioStreamStats, &_stats, "processStreamStatsPacket");
     packetReceiver.registerListener(PacketType::AudioEnvironment, this, "handleAudioEnvironmentDataPacket");
     packetReceiver.registerListener(PacketType::SilentAudioFrame, this, "handleAudioDataPacket");
@@ -278,6 +279,11 @@ AudioClient::AudioClient() :
     packetReceiver.registerListener(PacketType::NoisyMute, this, "handleNoisyMutePacket");
     packetReceiver.registerListener(PacketType::MuteEnvironment, this, "handleMuteEnvironmentPacket");
     packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat");
+
+    auto& domainHandler = nodeList->getDomainHandler();
+    connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, [this] {
+        _solo.reset();
+    });
 }
 
 AudioClient::~AudioClient() {
diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h
index 5e7f1fb8a0..751bddd35d 100644
--- a/libraries/audio-client/src/AudioClient.h
+++ b/libraries/audio-client/src/AudioClient.h
@@ -46,7 +46,6 @@
 #include <AudioConstants.h>
 #include <AudioGate.h>
 
-
 #include <shared/RateCounter.h>
 
 #include <plugins/CodecPlugin.h>
@@ -171,6 +170,7 @@ public:
     void stopRecording();
     void setAudioPaused(bool pause);
 
+    AudioSolo& getAudioSolo() override { return _solo; }
 
 #ifdef Q_OS_WIN
     static QString getWinDeviceName(wchar_t* guid);
@@ -446,6 +446,8 @@ private:
 #if defined(Q_OS_ANDROID)
     bool _shouldRestartInputSetup { true }; // Should we restart the input device because of an unintended stop?
 #endif
+
+    AudioSolo _solo;
     
     Mutex _checkDevicesMutex;
     QTimer* _checkDevicesTimer { nullptr };
diff --git a/libraries/audio/src/AbstractAudioInterface.h b/libraries/audio/src/AbstractAudioInterface.h
index bbfd79d0aa..0f075ab224 100644
--- a/libraries/audio/src/AbstractAudioInterface.h
+++ b/libraries/audio/src/AbstractAudioInterface.h
@@ -19,6 +19,7 @@
 
 #include "AudioInjectorOptions.h"
 #include "AudioInjector.h"
+#include "AudioSolo.h"
 
 class AudioInjector;
 class AudioInjectorLocalBuffer;
@@ -38,6 +39,8 @@ public:
     // take care to delete it when ~AudioInjector, as parenting Qt semantics will not work
     virtual bool outputLocalInjector(const AudioInjectorPointer& injector) = 0;
 
+    virtual AudioSolo& getAudioSolo() = 0;
+
 public slots:
     virtual bool shouldLoopbackInjectors() { return false; }
 
diff --git a/libraries/audio/src/AudioSolo.cpp b/libraries/audio/src/AudioSolo.cpp
new file mode 100644
index 0000000000..306c728dcb
--- /dev/null
+++ b/libraries/audio/src/AudioSolo.cpp
@@ -0,0 +1,77 @@
+//
+//  AudioSolo.cpp
+//
+//
+//  Created by Clement Brisset on 11/5/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
+//
+
+#include "AudioSolo.h"
+
+#include <NodeList.h>
+
+bool AudioSolo::isSoloing() const {
+    Lock lock(_mutex);
+    return !_nodesSoloed.empty();
+}
+
+QVector<QUuid> AudioSolo::getUUIDs() const {
+    Lock lock(_mutex);
+    return _nodesSoloed.values().toVector();
+}
+
+void AudioSolo::addUUIDs(QVector<QUuid> uuidList) {
+    // create a reliable NLPacket with space for the solo UUIDs
+    auto soloPacket = NLPacket::create(PacketType::AudioSoloRequest,
+                                       uuidList.size() * NUM_BYTES_RFC4122_UUID + sizeof(bool), true);
+    soloPacket->writePrimitive(true);
+
+    {
+        Lock lock(_mutex);
+        for (auto uuid : uuidList) {
+            if (_nodesSoloed.contains(uuid)) {
+                qWarning() << "Uuid already in solo list:" << uuid;
+            } else {
+                // write the node ID to the packet
+                soloPacket->write(uuid.toRfc4122());
+                _nodesSoloed.insert(uuid);
+            }
+        }
+    }
+
+    // send off this ignore packet reliably to the matching node
+    auto nodeList = DependencyManager::get<NodeList>();
+    nodeList->broadcastToNodes(std::move(soloPacket), { NodeType::AudioMixer });
+}
+
+void AudioSolo::removeUUIDs(QVector<QUuid> uuidList) {
+    // create a reliable NLPacket with space for the solo UUIDs
+    auto soloPacket = NLPacket::create(PacketType::AudioSoloRequest,
+                                       uuidList.size() * NUM_BYTES_RFC4122_UUID + sizeof(bool), true);
+    soloPacket->writePrimitive(false);
+
+    {
+        Lock lock(_mutex);
+        for (auto uuid : uuidList) {
+            if (!_nodesSoloed.contains(uuid)) {
+                qWarning() << "Uuid not in solo list:" << uuid;
+            } else {
+                // write the node ID to the packet
+                soloPacket->write(uuid.toRfc4122());
+                _nodesSoloed.remove(uuid);
+            }
+        }
+    }
+
+    // send off this ignore packet reliably to the matching node
+    auto nodeList = DependencyManager::get<NodeList>();
+    nodeList->broadcastToNodes(std::move(soloPacket), { NodeType::AudioMixer });
+}
+
+void AudioSolo::reset() {
+    removeUUIDs(getUUIDs());
+}
+
diff --git a/libraries/audio/src/AudioSolo.h b/libraries/audio/src/AudioSolo.h
new file mode 100644
index 0000000000..ef2d2d0bec
--- /dev/null
+++ b/libraries/audio/src/AudioSolo.h
@@ -0,0 +1,38 @@
+//
+//  AudioSolo.h
+//  libraries/audio/src
+//
+//  Created by Clement Brisset on 11/5/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
+//
+
+#pragma once
+
+#ifndef hifi_AudioSolo_h
+#define hifi_AudioSolo_h
+
+#include <mutex>
+
+#include <QSet>
+#include <QUuid>
+
+class AudioSolo {
+    using Mutex = std::mutex;
+    using Lock = std::unique_lock<Mutex>;
+
+public:
+    bool isSoloing() const;
+    QVector<QUuid> getUUIDs() const;
+    void addUUIDs(QVector<QUuid> uuidList);
+    void removeUUIDs(QVector<QUuid> uuidList);
+    void reset();
+
+private:
+    mutable Mutex _mutex;
+    QSet<QUuid> _nodesSoloed;
+};
+
+#endif // hifi_AudioSolo_h
diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h
index 37a4b32940..e5efe05ad0 100644
--- a/libraries/networking/src/udt/PacketHeaders.h
+++ b/libraries/networking/src/udt/PacketHeaders.h
@@ -126,14 +126,13 @@ public:
         EntityScriptCallMethod,
         ChallengeOwnershipRequest,
         ChallengeOwnershipReply,
-
         OctreeDataFileRequest,
         OctreeDataFileReply,
         OctreeDataPersist,
-
         EntityClone,
         EntityQueryInitialResultsComplete,
         BulkAvatarTraits,
+        AudioSoloRequest,
 
         NUM_PACKET_TYPE
     };
diff --git a/libraries/script-engine/src/AudioScriptingInterface.h b/libraries/script-engine/src/AudioScriptingInterface.h
index 1220a9b769..7808a86566 100644
--- a/libraries/script-engine/src/AudioScriptingInterface.h
+++ b/libraries/script-engine/src/AudioScriptingInterface.h
@@ -25,13 +25,31 @@ class AudioScriptingInterface : public QObject, public Dependency {
 
     // JSDoc for property is in Audio.h.
     Q_PROPERTY(bool isStereoInput READ isStereoInput WRITE setStereoInput NOTIFY isStereoInputChanged)
+    Q_PROPERTY(bool isSoloing READ isSoloing)
+    Q_PROPERTY(QVector<QUuid> soloList READ getSoloList)
 
 public:
-    virtual ~AudioScriptingInterface() {}
+    virtual ~AudioScriptingInterface() = default;
     void setLocalAudioInterface(AbstractAudioInterface* audioInterface);
 
 protected:
-    AudioScriptingInterface() {}
+    AudioScriptingInterface() = default;
+
+    bool isSoloing() const {
+        return _localAudioInterface->getAudioSolo().isSoloing();
+    }
+    QVector<QUuid> getSoloList() const {
+        return _localAudioInterface->getAudioSolo().getUUIDs();
+    }
+    Q_INVOKABLE void addToSoloList(QVector<QUuid> uuidList) {
+        _localAudioInterface->getAudioSolo().addUUIDs(uuidList);
+    }
+    Q_INVOKABLE void removeFromSoloList(QVector<QUuid> uuidList) {
+        _localAudioInterface->getAudioSolo().removeUUIDs(uuidList);
+    }
+    Q_INVOKABLE void resetSoloList() {
+        _localAudioInterface->getAudioSolo().reset();
+    }
 
     // these methods are protected to stop C++ callers from calling, but invokable from script
 

From 4e802b0f3fd83f553e667a8d04c10e92e9f4df30 Mon Sep 17 00:00:00 2001
From: Clement <clement.brisset@gmail.com>
Date: Tue, 6 Nov 2018 11:59:15 -0800
Subject: [PATCH 18/31] Add JSDoc comments

---
 interface/src/scripting/Audio.h               |  2 ++
 .../src/AudioScriptingInterface.h             | 24 ++++++++++++++++---
 2 files changed, 23 insertions(+), 3 deletions(-)

diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h
index 4b8eb6aabc..738eeb5dfe 100644
--- a/interface/src/scripting/Audio.h
+++ b/interface/src/scripting/Audio.h
@@ -50,6 +50,8 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable {
      *     <em>Read-only.</em>
      * @property {object} devices <em>Read-only.</em> <strong>Deprecated:</strong> This property is deprecated and will be
      *     removed.
+     * @property {boolean} isSoloing <em>Read-only.</em> <code>true</code> if any nodes are soloed.
+     * @property {QVector<QUuid>} soloList <em>Read-only.</em> Get the list of currently soloed node UUIDs.
      */
 
     Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged)
diff --git a/libraries/script-engine/src/AudioScriptingInterface.h b/libraries/script-engine/src/AudioScriptingInterface.h
index 7808a86566..ba570fd7c0 100644
--- a/libraries/script-engine/src/AudioScriptingInterface.h
+++ b/libraries/script-engine/src/AudioScriptingInterface.h
@@ -32,25 +32,43 @@ public:
     virtual ~AudioScriptingInterface() = default;
     void setLocalAudioInterface(AbstractAudioInterface* audioInterface);
 
-protected:
-    AudioScriptingInterface() = default;
-
     bool isSoloing() const {
         return _localAudioInterface->getAudioSolo().isSoloing();
     }
+
     QVector<QUuid> getSoloList() const {
         return _localAudioInterface->getAudioSolo().getUUIDs();
     }
+
+    /**jsdoc
+     * Add nodes to the audio solo list
+     * @function Audio.addToSoloList
+     * @param {QVector<QUuid>} uuidList - List of node UUIDs to add to the solo list.
+     */
     Q_INVOKABLE void addToSoloList(QVector<QUuid> uuidList) {
         _localAudioInterface->getAudioSolo().addUUIDs(uuidList);
     }
+
+    /**jsdoc
+     * Remove nodes from the audio solo list
+     * @function Audio.removeFromSoloList
+     * @param {QVector<QUuid>} uuidList - List of node UUIDs to remove from the solo list.
+     */
     Q_INVOKABLE void removeFromSoloList(QVector<QUuid> uuidList) {
         _localAudioInterface->getAudioSolo().removeUUIDs(uuidList);
     }
+
+    /**jsdoc
+     * Reset the list of soloed nodes.
+     * @function Audio.resetSoloList
+     */
     Q_INVOKABLE void resetSoloList() {
         _localAudioInterface->getAudioSolo().reset();
     }
 
+protected:
+    AudioScriptingInterface() = default;
+
     // these methods are protected to stop C++ callers from calling, but invokable from script
 
     /**jsdoc

From 6ecf850159d4d1f43826aa2a6ca47fcab3d665f6 Mon Sep 17 00:00:00 2001
From: Andrew Meadows <andrew@highfidelity.io>
Date: Tue, 6 Nov 2018 12:37:55 -0800
Subject: [PATCH 19/31] clarify ShapeInfo::setCapsuleY() API and use it
 correctly

---
 .../src/avatars-renderer/Avatar.cpp           | 17 ++++++-------
 libraries/shared/src/ShapeInfo.cpp            | 24 +++++++++----------
 libraries/shared/src/ShapeInfo.h              |  2 +-
 3 files changed, 22 insertions(+), 21 deletions(-)

diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
index f694148b30..03938dd3de 100644
--- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
+++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
@@ -1724,22 +1724,23 @@ void Avatar::computeShapeInfo(ShapeInfo& shapeInfo) {
 }
 
 void Avatar::getCapsule(glm::vec3& start, glm::vec3& end, float& radius) {
-    // FIXME: this doesn't take into account Avatar rotation
     ShapeInfo shapeInfo;
     computeShapeInfo(shapeInfo);
-    glm::vec3 halfExtents = shapeInfo.getHalfExtents(); // x = radius, y = halfHeight
-    start = getWorldPosition() - glm::vec3(0, halfExtents.y, 0) + shapeInfo.getOffset();
-    end = getWorldPosition() + glm::vec3(0, halfExtents.y, 0) + shapeInfo.getOffset();
+    glm::vec3 halfExtents = shapeInfo.getHalfExtents(); // x = radius, y = cylinderHalfHeight + radius
     radius = halfExtents.x;
+    glm::vec3 halfCylinderAxis(0.0f, halfExtents.y - radius, 0.0f);
+    Transform transform = getTransform();
+    start = transform.getTranslation() + transform.getRotation() * (shapeInfo.getOffset() - halfCylinderAxis);
+    end = transform.getTranslation() + transform.getRotation() * (shapeInfo.getOffset() + halfCylinderAxis);
 }
 
 glm::vec3 Avatar::getWorldFeetPosition() {
     ShapeInfo shapeInfo;
-
     computeShapeInfo(shapeInfo);
-    glm::vec3 halfExtents = shapeInfo.getHalfExtents(); // x = radius, y = halfHeight
-    glm::vec3 localFeet(0.0f, shapeInfo.getOffset().y - halfExtents.y - halfExtents.x, 0.0f);
-    return getWorldOrientation() * localFeet + getWorldPosition();
+    glm::vec3 halfExtents = shapeInfo.getHalfExtents(); // x = radius, y = cylinderHalfHeight + radius
+    glm::vec3 localFeet(0.0f, shapeInfo.getOffset().y - halfExtents.y, 0.0f);
+    Transform transform = getTransform();
+    return transform.getTranslation() + transform.getRotation() * localFeet;
 }
 
 float Avatar::computeMass() {
diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp
index 152e305bf2..52a8ad7254 100644
--- a/libraries/shared/src/ShapeInfo.cpp
+++ b/libraries/shared/src/ShapeInfo.cpp
@@ -148,12 +148,12 @@ void ShapeInfo::setPointCollection(const ShapeInfo::PointCollection& pointCollec
     _hashKey.clear();
 }
 
-void ShapeInfo::setCapsuleY(float radius, float halfHeight) {
+void ShapeInfo::setCapsuleY(float radius, float cylinderHalfHeight) {
     _url = "";
     _type = SHAPE_TYPE_CAPSULE_Y;
     radius = glm::max(radius, MIN_HALF_EXTENT);
-    halfHeight = glm::max(halfHeight, 0.0f);
-    _halfExtents = glm::vec3(radius, halfHeight, radius);
+    cylinderHalfHeight = glm::max(cylinderHalfHeight, 0.0f);
+    _halfExtents = glm::vec3(radius, cylinderHalfHeight + radius, radius);
     _hashKey.clear();
 }
 
@@ -262,26 +262,26 @@ bool ShapeInfo::contains(const glm::vec3& point) const {
             return glm::length(glm::vec2(point.x, point.y)) <= _halfExtents.y;
         case SHAPE_TYPE_CAPSULE_X: {
             if (glm::abs(point.x) <= _halfExtents.x) {
-                return glm::length(glm::vec2(point.y, point.z)) <= _halfExtents.z;
+                return glm::length(glm::vec2(point.y, point.z)) <= _halfExtents.y;
             } else {
-                glm::vec3 absPoint = glm::abs(point) - _halfExtents.x;
-                return glm::length(absPoint) <= _halfExtents.z;
+                glm::vec3 absPoint = glm::abs(point) - glm::vec3(_halfExtents.x, 0.0f, 0.0f);
+                return glm::length(absPoint) <= _halfExtents.y;
             }
         }
         case SHAPE_TYPE_CAPSULE_Y: {
             if (glm::abs(point.y) <= _halfExtents.y) {
-                return glm::length(glm::vec2(point.x, point.z)) <= _halfExtents.x;
+                return glm::length(glm::vec2(point.x, point.z)) <= _halfExtents.z;
             } else {
-                glm::vec3 absPoint = glm::abs(point) - _halfExtents.y;
-                return glm::length(absPoint) <= _halfExtents.x;
+                glm::vec3 absPoint = glm::abs(point) - glm::vec3(0.0f, _halfExtents.y, 0.0f);
+                return glm::length(absPoint) <= _halfExtents.z;
             }
         }
         case SHAPE_TYPE_CAPSULE_Z: {
             if (glm::abs(point.z) <= _halfExtents.z) {
-                return glm::length(glm::vec2(point.x, point.y)) <= _halfExtents.y;
+                return glm::length(glm::vec2(point.x, point.y)) <= _halfExtents.x;
             } else {
-                glm::vec3 absPoint = glm::abs(point) - _halfExtents.z;
-                return glm::length(absPoint) <= _halfExtents.y;
+                glm::vec3 absPoint = glm::abs(point) - glm::vec3(0.0f, 0.0f, _halfExtents.z);
+                return glm::length(absPoint) <= _halfExtents.x;
             }
         }
         case SHAPE_TYPE_BOX:
diff --git a/libraries/shared/src/ShapeInfo.h b/libraries/shared/src/ShapeInfo.h
index 5020e492cf..16e260d9db 100644
--- a/libraries/shared/src/ShapeInfo.h
+++ b/libraries/shared/src/ShapeInfo.h
@@ -67,7 +67,7 @@ public:
     void setBox(const glm::vec3& halfExtents);
     void setSphere(float radius);
     void setPointCollection(const PointCollection& pointCollection);
-    void setCapsuleY(float radius, float halfHeight);
+    void setCapsuleY(float radius, float cylinderHalfHeight);
     void setOffset(const glm::vec3& offset);
 
     ShapeType getType() const { return _type; }

From b38c263e604ef9663ce3e4ab1d4e644ecb7b3eee Mon Sep 17 00:00:00 2001
From: Andrew Meadows <andrew@highfidelity.io>
Date: Tue, 6 Nov 2018 12:44:14 -0800
Subject: [PATCH 20/31] use correct capsule dimensions in teleport.js

---
 .../controllers/controllerModules/teleport.js  | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/scripts/system/controllers/controllerModules/teleport.js b/scripts/system/controllers/controllerModules/teleport.js
index 1aba6b92f6..44aa04b497 100644
--- a/scripts/system/controllers/controllerModules/teleport.js
+++ b/scripts/system/controllers/controllerModules/teleport.js
@@ -358,9 +358,9 @@ Script.include("/~/system/libraries/controllers.js");
 
             var sensorToWorldScale = MyAvatar.getSensorToWorldScale();
 
-            var radius = capsuleData.radius / sensorToWorldScale;
-            var height = (Vec3.distance(capsuleData.start, capsuleData.end) + (capsuleData.radius * 2.0)) / sensorToWorldScale;
-            var capsuleRatio = 10.0 * radius / height;
+            var diameter = 2.0 * capsuleData.radius / sensorToWorldScale;
+            var height = (Vec3.distance(capsuleData.start, capsuleData.end) + diameter) / sensorToWorldScale;
+            var capsuleRatio = 5.0 * diameter / height;
             var offset = _this.pickHeightOffset * capsuleRatio;
 
             _this.teleportHandCollisionPick = Picks.createPick(PickType.Collision, {
@@ -370,9 +370,9 @@ Script.include("/~/system/libraries/controllers.js");
                 shape: {
                     shapeType: "capsule-y",
                     dimensions: {
-                        x: radius * 2.0,
-                        y: height - (radius * 2.0),
-                        z: radius * 2.0
+                        x: diameter,
+                        y: height,
+                        z: diameter
                     }
                 },
                 position: { x: 0, y: offset + height * 0.5, z: 0 },
@@ -386,9 +386,9 @@ Script.include("/~/system/libraries/controllers.js");
                 shape: {
                     shapeType: "capsule-y",
                     dimensions: {
-                        x: radius * 2.0,
-                        y: height - (radius * 2.0),
-                        z: radius * 2.0
+                        x: diameter,
+                        y: height,
+                        z: diameter
                     }
                 },
                 position: { x: 0, y: offset + height * 0.5, z: 0 },

From 91f6a5057a884ee1b226043d74f64c84ec772a00 Mon Sep 17 00:00:00 2001
From: Andrew Meadows <andrew@highfidelity.io>
Date: Tue, 6 Nov 2018 12:54:56 -0800
Subject: [PATCH 21/31] correct capsule height when building btCapsuleShape

---
 libraries/physics/src/ShapeFactory.cpp | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/libraries/physics/src/ShapeFactory.cpp b/libraries/physics/src/ShapeFactory.cpp
index 8057eb0e0c..d7ba2f0661 100644
--- a/libraries/physics/src/ShapeFactory.cpp
+++ b/libraries/physics/src/ShapeFactory.cpp
@@ -307,21 +307,21 @@ const btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info)
         case SHAPE_TYPE_CAPSULE_Y: {
             glm::vec3 halfExtents = info.getHalfExtents();
             float radius = halfExtents.x;
-            float height = 2.0f * halfExtents.y;
+            float height = 2.0f * (halfExtents.y - radius);
             shape = new btCapsuleShape(radius, height);
         }
         break;
         case SHAPE_TYPE_CAPSULE_X: {
             glm::vec3 halfExtents = info.getHalfExtents();
             float radius = halfExtents.y;
-            float height = 2.0f * halfExtents.x;
+            float height = 2.0f * (halfExtents.x - radius);
             shape = new btCapsuleShapeX(radius, height);
         }
         break;
         case SHAPE_TYPE_CAPSULE_Z: {
             glm::vec3 halfExtents = info.getHalfExtents();
             float radius = halfExtents.x;
-            float height = 2.0f * halfExtents.z;
+            float height = 2.0f * (halfExtents.z - radius);
             shape = new btCapsuleShapeZ(radius, height);
         }
         break;

From 0ca0a6f3bebc44c192e1038d5220546945a52ca2 Mon Sep 17 00:00:00 2001
From: Clement <clement.brisset@gmail.com>
Date: Tue, 6 Nov 2018 12:56:51 -0800
Subject: [PATCH 22/31] Fix JSDoc arrays

---
 interface/src/scripting/Audio.h                       | 2 +-
 libraries/script-engine/src/AudioScriptingInterface.h | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h
index 738eeb5dfe..4c4bf6dd60 100644
--- a/interface/src/scripting/Audio.h
+++ b/interface/src/scripting/Audio.h
@@ -51,7 +51,7 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable {
      * @property {object} devices <em>Read-only.</em> <strong>Deprecated:</strong> This property is deprecated and will be
      *     removed.
      * @property {boolean} isSoloing <em>Read-only.</em> <code>true</code> if any nodes are soloed.
-     * @property {QVector<QUuid>} soloList <em>Read-only.</em> Get the list of currently soloed node UUIDs.
+     * @property {Uuid[]} soloList <em>Read-only.</em> Get the list of currently soloed node UUIDs.
      */
 
     Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged)
diff --git a/libraries/script-engine/src/AudioScriptingInterface.h b/libraries/script-engine/src/AudioScriptingInterface.h
index ba570fd7c0..2854445b4f 100644
--- a/libraries/script-engine/src/AudioScriptingInterface.h
+++ b/libraries/script-engine/src/AudioScriptingInterface.h
@@ -43,7 +43,7 @@ public:
     /**jsdoc
      * Add nodes to the audio solo list
      * @function Audio.addToSoloList
-     * @param {QVector<QUuid>} uuidList - List of node UUIDs to add to the solo list.
+     * @param {Uuid[]} uuidList - List of node UUIDs to add to the solo list.
      */
     Q_INVOKABLE void addToSoloList(QVector<QUuid> uuidList) {
         _localAudioInterface->getAudioSolo().addUUIDs(uuidList);
@@ -52,7 +52,7 @@ public:
     /**jsdoc
      * Remove nodes from the audio solo list
      * @function Audio.removeFromSoloList
-     * @param {QVector<QUuid>} uuidList - List of node UUIDs to remove from the solo list.
+     * @param {Uuid[]} uuidList - List of node UUIDs to remove from the solo list.
      */
     Q_INVOKABLE void removeFromSoloList(QVector<QUuid> uuidList) {
         _localAudioInterface->getAudioSolo().removeUUIDs(uuidList);

From beb575a88d3a20c18c3884860bc48052d5de90ae Mon Sep 17 00:00:00 2001
From: Clement <clement.brisset@gmail.com>
Date: Tue, 6 Nov 2018 13:18:26 -0800
Subject: [PATCH 23/31] Don't attenuate soloed nodes.

---
 assignment-client/src/audio/AudioMixerSlave.cpp | 17 ++++++++++++-----
 assignment-client/src/audio/AudioMixerSlave.h   |  2 +-
 2 files changed, 13 insertions(+), 6 deletions(-)

diff --git a/assignment-client/src/audio/AudioMixerSlave.cpp b/assignment-client/src/audio/AudioMixerSlave.cpp
index 7007e684cb..0308acc697 100644
--- a/assignment-client/src/audio/AudioMixerSlave.cpp
+++ b/assignment-client/src/audio/AudioMixerSlave.cpp
@@ -314,6 +314,7 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
     memset(_mixSamples, 0, sizeof(_mixSamples));
 
     bool isThrottling = _numToRetain != -1;
+    bool isSoloing = !listenerData->getSolodNodes().empty();
 
     auto& streams = listenerData->getStreams();
 
@@ -380,13 +381,14 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
             stream.approximateVolume = approximateVolume(stream, listenerAudioStream);
         } else {
             if (shouldBeSkipped(stream, *listener, *listenerAudioStream, *listenerData)) {
-                addStream(stream, *listenerAudioStream, 0.0f);
+                addStream(stream, *listenerAudioStream, 0.0f, isSoloing);
                 streams.skipped.push_back(move(stream));
                 ++stats.activeToSkipped;
                 return true;
             }
 
-            addStream(stream, *listenerAudioStream, listenerData->getMasterAvatarGain());
+            addStream(stream, *listenerAudioStream, listenerData->getMasterAvatarGain(),
+                      isSoloing);
 
             if (shouldBeInactive(stream)) {
                 // To reduce artifacts we still call render to flush the HRTF for every silent
@@ -421,7 +423,8 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
                 return true;
             }
 
-            addStream(stream, *listenerAudioStream, listenerData->getMasterAvatarGain());
+            addStream(stream, *listenerAudioStream, listenerData->getMasterAvatarGain(),
+                      isSoloing);
 
             if (shouldBeInactive(stream)) {
                 // To reduce artifacts we still call render to flush the HRTF for every silent
@@ -488,7 +491,7 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
 
 void AudioMixerSlave::addStream(AudioMixerClientData::MixableStream& mixableStream,
                                 AvatarAudioStream& listeningNodeStream,
-                                float masterListenerGain) {
+                                float masterListenerGain, bool isSoloing) {
     ++stats.totalMixes;
 
     auto streamToAdd = mixableStream.positionalStream;
@@ -499,9 +502,13 @@ void AudioMixerSlave::addStream(AudioMixerClientData::MixableStream& mixableStre
     glm::vec3 relativePosition = streamToAdd->getPosition() - listeningNodeStream.getPosition();
 
     float distance = glm::max(glm::length(relativePosition), EPSILON);
-    float gain = computeGain(masterListenerGain, listeningNodeStream, *streamToAdd, relativePosition, distance, isEcho);
     float azimuth = isEcho ? 0.0f : computeAzimuth(listeningNodeStream, listeningNodeStream, relativePosition);
 
+    float gain = 1.0f;
+    if (!isSoloing) {
+        gain = computeGain(masterListenerGain, listeningNodeStream, *streamToAdd, relativePosition, distance, isEcho);
+    }
+
     const int HRTF_DATASET_INDEX = 1;
 
     if (!streamToAdd->lastPopSucceeded()) {
diff --git a/assignment-client/src/audio/AudioMixerSlave.h b/assignment-client/src/audio/AudioMixerSlave.h
index 6566c839b8..3d979da1fc 100644
--- a/assignment-client/src/audio/AudioMixerSlave.h
+++ b/assignment-client/src/audio/AudioMixerSlave.h
@@ -57,7 +57,7 @@ private:
     bool prepareMix(const SharedNodePointer& listener);
     void addStream(AudioMixerClientData::MixableStream& mixableStream,
                    AvatarAudioStream& listeningNodeStream,
-                   float masterListenerGain);
+                   float masterListenerGain, bool isSoloing);
     void updateHRTFParameters(AudioMixerClientData::MixableStream& mixableStream,
                               AvatarAudioStream& listeningNodeStream,
                               float masterListenerGain);

From b8cb433fa13b0127572a9b4e6dbe8a03c3d188d6 Mon Sep 17 00:00:00 2001
From: Clement <clement.brisset@gmail.com>
Date: Tue, 6 Nov 2018 13:20:03 -0800
Subject: [PATCH 24/31] Spelling

---
 assignment-client/src/audio/AudioMixerClientData.cpp | 10 +++++-----
 assignment-client/src/audio/AudioMixerClientData.h   |  4 ++--
 assignment-client/src/audio/AudioMixerSlave.cpp      |  6 +++---
 3 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp
index 0aee185088..3b47b058e7 100644
--- a/assignment-client/src/audio/AudioMixerClientData.cpp
+++ b/assignment-client/src/audio/AudioMixerClientData.cpp
@@ -305,14 +305,14 @@ void AudioMixerClientData::parseSoloRequest(QSharedPointer<ReceivedMessage> mess
     message->readPrimitive(&addToSolo);
 
     while (message->getBytesLeftToRead()) {
-        // parse out the UUID being solod from the packet
-        QUuid solodUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
+        // parse out the UUID being soloed from the packet
+        QUuid soloedUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
 
         if (addToSolo) {
-            _solodNodes.push_back(solodUUID);
+            _soloedNodes.push_back(soloedUUID);
         } else {
-            auto it = std::find(std::begin(_solodNodes), std::end(_solodNodes), solodUUID);
-            _solodNodes.erase(it);
+            auto it = std::find(std::begin(_soloedNodes), std::end(_soloedNodes), soloedUUID);
+            _soloedNodes.erase(it);
         }
     }
 }
diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h
index dd5681c537..0232fe6bb6 100644
--- a/assignment-client/src/audio/AudioMixerClientData.h
+++ b/assignment-client/src/audio/AudioMixerClientData.h
@@ -152,7 +152,7 @@ public:
     const Node::IgnoredNodeIDs& getIgnoringNodeIDs() const { return _ignoringNodeIDs; }
 
 
-    const std::vector<QUuid>& getSolodNodes() const { return _solodNodes; }
+    const std::vector<QUuid>& getSoloedNodes() const { return _soloedNodes; }
 
     bool getHasReceivedFirstMix() const { return _hasReceivedFirstMix; }
     void setHasReceivedFirstMix(bool hasReceivedFirstMix) { _hasReceivedFirstMix = hasReceivedFirstMix; }
@@ -213,7 +213,7 @@ private:
 
     std::atomic_bool _isIgnoreRadiusEnabled { false };
 
-    std::vector<QUuid> _solodNodes;
+    std::vector<QUuid> _soloedNodes;
 
     bool _hasReceivedFirstMix { false };
 };
diff --git a/assignment-client/src/audio/AudioMixerSlave.cpp b/assignment-client/src/audio/AudioMixerSlave.cpp
index 0308acc697..7a6ab9c3e2 100644
--- a/assignment-client/src/audio/AudioMixerSlave.cpp
+++ b/assignment-client/src/audio/AudioMixerSlave.cpp
@@ -272,8 +272,8 @@ bool shouldBeSkipped(MixableStream& stream, const Node& listener,
         return true;
     }
 
-    if (!listenerData.getSolodNodes().empty()) {
-        return !contains(listenerData.getSolodNodes(), stream.nodeStreamID.nodeID);
+    if (!listenerData.getSoloedNodes().empty()) {
+        return !contains(listenerData.getSoloedNodes(), stream.nodeStreamID.nodeID);
     }
 
     bool shouldCheckIgnoreBox = (listenerAudioStream.isIgnoreBoxEnabled() ||
@@ -314,7 +314,7 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
     memset(_mixSamples, 0, sizeof(_mixSamples));
 
     bool isThrottling = _numToRetain != -1;
-    bool isSoloing = !listenerData->getSolodNodes().empty();
+    bool isSoloing = !listenerData->getSoloedNodes().empty();
 
     auto& streams = listenerData->getStreams();
 

From 65a83cb026b6b04210e1203b7b82b13eb7cda8c9 Mon Sep 17 00:00:00 2001
From: Clement <clement.brisset@gmail.com>
Date: Wed, 7 Nov 2018 11:41:04 -0800
Subject: [PATCH 25/31] Resend solo nodes on mixer reconnect

---
 libraries/audio-client/src/AudioClient.cpp | 5 +++++
 libraries/audio/src/AudioSolo.cpp          | 9 +++++++++
 libraries/audio/src/AudioSolo.h            | 4 +++-
 3 files changed, 17 insertions(+), 1 deletion(-)

diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp
index ace8f72875..8bc211cf9a 100644
--- a/libraries/audio-client/src/AudioClient.cpp
+++ b/libraries/audio-client/src/AudioClient.cpp
@@ -284,6 +284,11 @@ AudioClient::AudioClient() :
     connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, [this] {
         _solo.reset();
     });
+    connect(nodeList.data(), &NodeList::nodeAdded, this, [this](SharedNodePointer node) {
+        if (node->getType() == NodeType::AudioMixer) {
+            _solo.resend();
+        }
+    });
 }
 
 AudioClient::~AudioClient() {
diff --git a/libraries/audio/src/AudioSolo.cpp b/libraries/audio/src/AudioSolo.cpp
index 306c728dcb..83ecc6e130 100644
--- a/libraries/audio/src/AudioSolo.cpp
+++ b/libraries/audio/src/AudioSolo.cpp
@@ -72,6 +72,15 @@ void AudioSolo::removeUUIDs(QVector<QUuid> uuidList) {
 }
 
 void AudioSolo::reset() {
+    Lock lock(_mutex);
     removeUUIDs(getUUIDs());
 }
 
+
+void AudioSolo::resend() {
+    Lock lock(_mutex);
+    auto uuids = getUUIDs();
+    _nodesSoloed.clear();
+    addUUIDs(uuids);
+}
+
diff --git a/libraries/audio/src/AudioSolo.h b/libraries/audio/src/AudioSolo.h
index ef2d2d0bec..790280a14b 100644
--- a/libraries/audio/src/AudioSolo.h
+++ b/libraries/audio/src/AudioSolo.h
@@ -20,7 +20,7 @@
 #include <QUuid>
 
 class AudioSolo {
-    using Mutex = std::mutex;
+    using Mutex = std::recursive_mutex;
     using Lock = std::unique_lock<Mutex>;
 
 public:
@@ -30,6 +30,8 @@ public:
     void removeUUIDs(QVector<QUuid> uuidList);
     void reset();
 
+    void resend();
+
 private:
     mutable Mutex _mutex;
     QSet<QUuid> _nodesSoloed;

From 8f763dfd5036384ca496b67a50d897b639191679 Mon Sep 17 00:00:00 2001
From: Clement <clement.brisset@gmail.com>
Date: Wed, 7 Nov 2018 17:44:48 -0800
Subject: [PATCH 26/31] Fix bug and crash in solo logic

---
 assignment-client/src/audio/AudioMixerClientData.cpp | 4 ++--
 libraries/audio-client/src/AudioClient.cpp           | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp
index 3b47b058e7..68727a16be 100644
--- a/assignment-client/src/audio/AudioMixerClientData.cpp
+++ b/assignment-client/src/audio/AudioMixerClientData.cpp
@@ -311,8 +311,8 @@ void AudioMixerClientData::parseSoloRequest(QSharedPointer<ReceivedMessage> mess
         if (addToSolo) {
             _soloedNodes.push_back(soloedUUID);
         } else {
-            auto it = std::find(std::begin(_soloedNodes), std::end(_soloedNodes), soloedUUID);
-            _soloedNodes.erase(it);
+            auto it = std::remove(std::begin(_soloedNodes), std::end(_soloedNodes), soloedUUID);
+            _soloedNodes.erase(it, std::end(_soloedNodes));
         }
     }
 }
diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp
index 8bc211cf9a..cab02e215e 100644
--- a/libraries/audio-client/src/AudioClient.cpp
+++ b/libraries/audio-client/src/AudioClient.cpp
@@ -284,7 +284,7 @@ AudioClient::AudioClient() :
     connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, [this] {
         _solo.reset();
     });
-    connect(nodeList.data(), &NodeList::nodeAdded, this, [this](SharedNodePointer node) {
+    connect(nodeList.data(), &NodeList::nodeActivated, this, [this](SharedNodePointer node) {
         if (node->getType() == NodeType::AudioMixer) {
             _solo.resend();
         }

From ef54a63de622f5362d9ae763a22614b7c7b35a95 Mon Sep 17 00:00:00 2001
From: Clement <clement.brisset@gmail.com>
Date: Wed, 7 Nov 2018 17:54:49 -0800
Subject: [PATCH 27/31] Use uint8_t instead of bool on the wire

---
 .../src/audio/AudioMixerClientData.cpp             |  2 +-
 libraries/audio/src/AudioSolo.cpp                  | 14 ++++++++------
 2 files changed, 9 insertions(+), 7 deletions(-)

diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp
index 68727a16be..a7edd3169c 100644
--- a/assignment-client/src/audio/AudioMixerClientData.cpp
+++ b/assignment-client/src/audio/AudioMixerClientData.cpp
@@ -301,7 +301,7 @@ void AudioMixerClientData::parseRadiusIgnoreRequest(QSharedPointer<ReceivedMessa
 
 void AudioMixerClientData::parseSoloRequest(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& node) {
 
-    bool addToSolo;
+    uint8_t addToSolo;
     message->readPrimitive(&addToSolo);
 
     while (message->getBytesLeftToRead()) {
diff --git a/libraries/audio/src/AudioSolo.cpp b/libraries/audio/src/AudioSolo.cpp
index 83ecc6e130..3302636096 100644
--- a/libraries/audio/src/AudioSolo.cpp
+++ b/libraries/audio/src/AudioSolo.cpp
@@ -26,8 +26,9 @@ QVector<QUuid> AudioSolo::getUUIDs() const {
 void AudioSolo::addUUIDs(QVector<QUuid> uuidList) {
     // create a reliable NLPacket with space for the solo UUIDs
     auto soloPacket = NLPacket::create(PacketType::AudioSoloRequest,
-                                       uuidList.size() * NUM_BYTES_RFC4122_UUID + sizeof(bool), true);
-    soloPacket->writePrimitive(true);
+                                       uuidList.size() * NUM_BYTES_RFC4122_UUID + sizeof(uint8_t), true);
+    uint8_t addToSoloList = (uint8_t)true;
+    soloPacket->writePrimitive(addToSoloList);
 
     {
         Lock lock(_mutex);
@@ -42,7 +43,7 @@ void AudioSolo::addUUIDs(QVector<QUuid> uuidList) {
         }
     }
 
-    // send off this ignore packet reliably to the matching node
+    // send off this solo packet reliably to the matching node
     auto nodeList = DependencyManager::get<NodeList>();
     nodeList->broadcastToNodes(std::move(soloPacket), { NodeType::AudioMixer });
 }
@@ -50,8 +51,9 @@ void AudioSolo::addUUIDs(QVector<QUuid> uuidList) {
 void AudioSolo::removeUUIDs(QVector<QUuid> uuidList) {
     // create a reliable NLPacket with space for the solo UUIDs
     auto soloPacket = NLPacket::create(PacketType::AudioSoloRequest,
-                                       uuidList.size() * NUM_BYTES_RFC4122_UUID + sizeof(bool), true);
-    soloPacket->writePrimitive(false);
+                                       uuidList.size() * NUM_BYTES_RFC4122_UUID + sizeof(uint8_t), true);
+    uint8_t addToSoloList = (uint8_t)false;
+    soloPacket->writePrimitive(addToSoloList);
 
     {
         Lock lock(_mutex);
@@ -66,7 +68,7 @@ void AudioSolo::removeUUIDs(QVector<QUuid> uuidList) {
         }
     }
 
-    // send off this ignore packet reliably to the matching node
+    // send off this solo packet reliably to the matching node
     auto nodeList = DependencyManager::get<NodeList>();
     nodeList->broadcastToNodes(std::move(soloPacket), { NodeType::AudioMixer });
 }

From 5bfb9c1f71bf2f6a569956f6911f1a27db2a5847 Mon Sep 17 00:00:00 2001
From: Andrew Meadows <andrew@highfidelity.io>
Date: Thu, 8 Nov 2018 08:39:43 -0800
Subject: [PATCH 28/31] fix ShapeInfo::contains() logic for capsule shapes

---
 libraries/shared/src/ShapeInfo.cpp | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp
index 52a8ad7254..df8e61114d 100644
--- a/libraries/shared/src/ShapeInfo.cpp
+++ b/libraries/shared/src/ShapeInfo.cpp
@@ -261,7 +261,7 @@ bool ShapeInfo::contains(const glm::vec3& point) const {
         case SHAPE_TYPE_CYLINDER_Z:
             return glm::length(glm::vec2(point.x, point.y)) <= _halfExtents.y;
         case SHAPE_TYPE_CAPSULE_X: {
-            if (glm::abs(point.x) <= _halfExtents.x) {
+            if (glm::abs(point.x) <= _halfExtents.x - _halfExtents.y) {
                 return glm::length(glm::vec2(point.y, point.z)) <= _halfExtents.y;
             } else {
                 glm::vec3 absPoint = glm::abs(point) - glm::vec3(_halfExtents.x, 0.0f, 0.0f);
@@ -269,7 +269,7 @@ bool ShapeInfo::contains(const glm::vec3& point) const {
             }
         }
         case SHAPE_TYPE_CAPSULE_Y: {
-            if (glm::abs(point.y) <= _halfExtents.y) {
+            if (glm::abs(point.y) <= _halfExtents.y - _halfExtents.z) {
                 return glm::length(glm::vec2(point.x, point.z)) <= _halfExtents.z;
             } else {
                 glm::vec3 absPoint = glm::abs(point) - glm::vec3(0.0f, _halfExtents.y, 0.0f);
@@ -277,7 +277,7 @@ bool ShapeInfo::contains(const glm::vec3& point) const {
             }
         }
         case SHAPE_TYPE_CAPSULE_Z: {
-            if (glm::abs(point.z) <= _halfExtents.z) {
+            if (glm::abs(point.z) <= _halfExtents.z - _halfExtents.x) {
                 return glm::length(glm::vec2(point.x, point.y)) <= _halfExtents.x;
             } else {
                 glm::vec3 absPoint = glm::abs(point) - glm::vec3(0.0f, 0.0f, _halfExtents.z);

From f337579616edfcd7a77606e5b7c09305d8cf7d2a Mon Sep 17 00:00:00 2001
From: Clement <clement.brisset@gmail.com>
Date: Thu, 8 Nov 2018 11:31:22 -0800
Subject: [PATCH 29/31] CR

---
 libraries/audio/src/AudioSolo.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libraries/audio/src/AudioSolo.cpp b/libraries/audio/src/AudioSolo.cpp
index 3302636096..9d63f01a8b 100644
--- a/libraries/audio/src/AudioSolo.cpp
+++ b/libraries/audio/src/AudioSolo.cpp
@@ -1,6 +1,6 @@
 //
 //  AudioSolo.cpp
-//
+//  libraries/audio/src
 //
 //  Created by Clement Brisset on 11/5/18.
 //  Copyright 2018 High Fidelity, Inc.

From cd4e56e7d54bb1f2f31955338a1b5785387e9ca6 Mon Sep 17 00:00:00 2001
From: Roxanne Skelly <roxanne@roxiware.com>
Date: Fri, 9 Nov 2018 14:26:20 -0800
Subject: [PATCH 30/31] Case 19808 - Change MARKET and WALLET in tray notifier
 to be INVENTORY along with other appropriate messaging changes.

---
 server-console/src/main.js                    | 34 ++++++-------
 .../src/modules/hf-notifications.js           | 48 +++++++++----------
 2 files changed, 40 insertions(+), 42 deletions(-)

diff --git a/server-console/src/main.js b/server-console/src/main.js
index dc3fbd4333..5adb4be4cb 100644
--- a/server-console/src/main.js
+++ b/server-console/src/main.js
@@ -337,7 +337,17 @@ var notificationState = NotificationState.UNNOTIFIED;
 
 function setNotificationState (notificationType, pending = undefined) {
     if (pending !== undefined) {
-        pendingNotifications[notificationType] = pending;
+        if ((notificationType === HifiNotificationType.TRANSACTIONS || 
+            notificationType === HifiNotificationType.ITEMS)) {
+            // special case, because we want to clear the indicator light
+            // on INVENTORY when either Transactions or Items are 
+            // clicked on in the notification popup, we detect that case
+            // here and force both to be unnotified.
+            pendingNotifications[HifiNotificationType.TRANSACTIONS] = pending;
+            pendingNotifications[HifiNotificationType.ITEMS] = pending;
+        } else {
+            pendingNotifications[notificationType] = pending;
+        }
         notificationState = NotificationState.UNNOTIFIED;
         for (var key in pendingNotifications) {
             if (pendingNotifications[key]) {
@@ -428,18 +438,12 @@ var labels = {
             setNotificationState(HifiNotificationType.PEOPLE, false);
         }
     },
-    wallet: {
-        label: 'Wallet',
+    inventory: {
+        label: 'Inventory',
         click: function () {
-            StartInterface("hifiapp:WALLET");
-            setNotificationState(HifiNotificationType.WALLET, false);
-        }
-    },
-    marketplace: {
-        label: 'Market',
-        click: function () {
-            StartInterface("hifiapp:MARKET");
-            setNotificationState(HifiNotificationType.MARKETPLACE, false);
+            StartInterface("hifiapp:INVENTORY");
+            setNotificationState(HifiNotificationType.ITEMS, false);
+            setNotificationState(HifiNotificationType.TRANSACTIONS, false);
         }
     },
     restart: {
@@ -528,8 +532,7 @@ function buildMenuArray(serverState) {
             if (trayNotifications.enabled()) {
                 menuArray.push(labels.goto);
                 menuArray.push(labels.people);
-                menuArray.push(labels.wallet);
-                menuArray.push(labels.marketplace);
+                menuArray.push(labels.inventory);
                 menuArray.push(separator);
             }
             menuArray.push(labels.showNotifications);
@@ -565,8 +568,7 @@ function updateLabels(serverState) {
     labels.showNotifications.checked = trayNotifications.enabled();
     labels.goto.icon = pendingNotifications[HifiNotificationType.GOTO] ? menuNotificationIcon : null;
     labels.people.icon = pendingNotifications[HifiNotificationType.PEOPLE] ? menuNotificationIcon : null;
-    labels.wallet.icon = pendingNotifications[HifiNotificationType.WALLET] ? menuNotificationIcon : null;
-    labels.marketplace.icon = pendingNotifications[HifiNotificationType.MARKETPLACE] ? menuNotificationIcon : null;
+    labels.inventory.icon = pendingNotifications[HifiNotificationType.ITEMS] || pendingNotifications[HifiNotificationType.TRANSACTIONS]? menuNotificationIcon : null;
     var onlineUsers = trayNotifications.getOnlineUsers();
     delete labels.people.submenu;
     if (onlineUsers) {
diff --git a/server-console/src/modules/hf-notifications.js b/server-console/src/modules/hf-notifications.js
index 8a812625b4..1ddbd1d307 100644
--- a/server-console/src/modules/hf-notifications.js
+++ b/server-console/src/modules/hf-notifications.js
@@ -32,10 +32,10 @@ const StartInterface=hfApp.startInterface;
 const IsInterfaceRunning=hfApp.isInterfaceRunning;
 
 const NotificationType = {
-    GOTO:        'goto',
-    PEOPLE:      'people',
-    WALLET:      'wallet',
-    MARKETPLACE: 'marketplace'
+    GOTO:         'goto',
+    PEOPLE:       'people',
+    ITEMS:        'items',
+    TRANSACTIONS: 'transactions'
 };
 
 
@@ -89,34 +89,34 @@ HifiNotification.prototype = {
                 }
                 break;
 
-            case NotificationType.WALLET:
+            case NotificationType.TRANSACTIONS:
                 if (typeof(this.data) === "number") {
                     if (this.data === 1) {
-                        text = "You have " + this.data + " unread Wallet transaction.";
+                        text = "You have " + this.data + " unread transaction.";
                     } else {
-                        text = "You have " + this.data + " unread Wallet transactions.";
+                        text = "You have " + this.data + " unread transactions.";
                     }
-                    message = "Click to open WALLET."
-                    url = "hifiapp:hifi/commerce/wallet/Wallet.qml";
+                    message = "Click to open INVENTORY."
+                    url = "hifiapp:INVENTORY";
                     break;
                 }
                 text = this.data.message.replace(/<\/?[^>]+(>|$)/g, "");
-                message = "Click to open WALLET.";
-                url = "hifiapp:WALLET";
+                message = "Click to open INVENTORY.";
+                url = "hifiapp:INVENTORY";
                 break;
 
-            case NotificationType.MARKETPLACE:
+            case NotificationType.ITEMS:
                 if (typeof(this.data) === "number") {
                     if (this.data === 1) {
-                        text = this.data + " of your purchased items has an update available.";
+                        text = this.data + " of your items has an update available.";
                     } else {  
-                        text = this.data + " of your purchased items have updates available.";
+                        text = this.data + " of your items have updates available.";
                     }
                 } else {
                     text = "Update available for " + this.data.base_item_title + ".";
                 }
-                message = "Click to open MARKET.";
-                url = "hifiapp:MARKET";
+                message = "Click to open INVENTORY.";
+                url = "hifiapp:INVENTORY";
                 break;
         }
         notifier.notify({
@@ -235,7 +235,6 @@ HifiNotifications.prototype = {
     },
     _showNotification: function () {
         var _this = this;
-
         if (osType === 'Darwin') {
             this.pendingNotifications[0].show(function () {
                 // For OSX
@@ -325,10 +324,10 @@ HifiNotifications.prototype = {
                         case NotificationType.PEOPLE:
                             notifyData = content.data.users;
                             break;
-                        case NotificationType.WALLET:
+                        case NotificationType.TRANSACTIONS:
                             notifyData = content.data.history;
                             break;
-                        case NotificationType.MARKETPLACE:
+                        case NotificationType.ITEMS:
                             notifyData = content.data.updates;
                             break;
                     }
@@ -376,19 +375,16 @@ HifiNotifications.prototype = {
                           }
                         }, function (error, data) {
                             if (error || !data.body) {
-                                console.log("Error: unable to get " + url);
-                                finished(false);
+                                console.log("Error: " + error + ": unable to get " + url);
                                 return;
                             }
                             var content = JSON.parse(data.body);
                             if (!content || content.status != 'success') {
                                 console.log("Error: unable to get " + url);
-                                finished(false);
                                 return;
                             }
 
                             if (!content.total_entries) {
-                                finished(true, token);
                                 return;
                             }
                             if (!content.total_entries) {
@@ -487,7 +483,7 @@ HifiNotifications.prototype = {
         console.log("Polling for economic activity");
         var url = METAVERSE_SERVER_URL + ECONOMIC_ACTIVITY_URL + '?' + options.join('&');
         console.log(url);
-        _this._pollCommon(NotificationType.WALLET, url, since, function () {});
+        _this._pollCommon(NotificationType.TRANSACTIONS, url, since, function () {});
     },
     pollForMarketplaceUpdates: function (since) {
         var _this = this;
@@ -499,7 +495,7 @@ HifiNotifications.prototype = {
         console.log("Polling for marketplace update");
         var url = METAVERSE_SERVER_URL + UPDATES_URL + '?' + options.join('&');
         console.log(url);
-        _this._pollCommon(NotificationType.MARKETPLACE, url, since, function (success, token) {
+        _this._pollCommon(NotificationType.ITEMS, url, since, function (success, token) {
             if (success) {
                 var options = [
                     'page=1',
@@ -512,7 +508,7 @@ HifiNotifications.prototype = {
                             'bearer': token
                         }
                     }, function (error, data) {
-                        _this._pollToDisableHighlight(NotificationType.MARKETPLACE, error, data);
+                        _this._pollToDisableHighlight(NotificationType.ITEMS, error, data);
                 });
             }
         });

From 3431bdbb0e246fd045a08a930738d0195cd63952 Mon Sep 17 00:00:00 2001
From: Howard Stearns <howard@highfidelity.io>
Date: Mon, 12 Nov 2018 14:51:32 -0800
Subject: [PATCH 31/31] new inventory icons, and align message waiting light in
 items tab

---
 .../icons/tablet-icons/inventory-a-msg.svg    | 24 +++++++++++++++---
 .../icons/tablet-icons/inventory-a.svg        | 17 +++++++++++--
 .../icons/tablet-icons/inventory-i-msg.svg    | 25 ++++++++++++++++---
 .../icons/tablet-icons/inventory-i.svg        | 20 +++++++++++++--
 .../qml/hifi/commerce/wallet/Wallet.qml       |  4 +--
 5 files changed, 76 insertions(+), 14 deletions(-)

diff --git a/interface/resources/icons/tablet-icons/inventory-a-msg.svg b/interface/resources/icons/tablet-icons/inventory-a-msg.svg
index 794bd1e414..b028c08b50 100644
--- a/interface/resources/icons/tablet-icons/inventory-a-msg.svg
+++ b/interface/resources/icons/tablet-icons/inventory-a-msg.svg
@@ -1,4 +1,20 @@
-<svg width="22" height="26" fill="none" version="1.1" viewBox="0 0 22 26" xmlns="http://www.w3.org/2000/svg">
- <path d="M1 7L11 1L21 7M1 7L11 13M1 7V19L11 25M11 13L21 7M11 13V25M21 7V19L11 25" stroke="#000" stroke-linejoin="round" stroke-width="2"/>
- <circle class="st1" cx="19.407" cy="2.5881" r="2.5846" fill="#ef3b4e" stroke-width=".24043"/>
-</svg>
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#EF3B4E;}
+</style>
+<g>
+	<circle cx="27.8" cy="13.3" r="2.4"/>
+	<circle cx="19.7" cy="7.2" r="2.3"/>
+	<circle cx="9.4" cy="6.9" r="2.2"/>
+	<path d="M41.8,17.5l-8.9-5.2c0.1,0.3,0.1,0.7,0.1,1c0,1-0.3,1.8-0.8,2.6l5.1,2.9L25,26l-12.3-7.1l3.1-1.8c-0.4-0.7-0.7-1.6-0.7-2.5
+		c0-0.4,0.1-0.8,0.2-1.2l-7.1,4.1c-0.5,0.3-0.9,0.9-0.9,1.5v16.5c0,0.6,0.3,1.2,0.9,1.5l16,9.2c0.3,0.2,0.6,0.2,0.9,0.2
+		s0.6-0.1,0.9-0.2l16-9.2c0.5-0.3,0.9-0.9,0.9-1.5V19C42.7,18.4,42.3,17.8,41.8,17.5z M10.7,21.7L23.3,29v12.8l-12.5-7.2V21.7z
+		 M39.2,34.5l-12.5,7.2V28.9l12.5-7.2V34.5z"/>
+	<circle cx="25" cy="20.3" r="2.8"/>
+	<circle cx="20" cy="14.6" r="2.4"/>
+</g>
+<circle class="st0" cx="44.1" cy="6" r="5.6"/>
+</svg>
diff --git a/interface/resources/icons/tablet-icons/inventory-a.svg b/interface/resources/icons/tablet-icons/inventory-a.svg
index 8b6f34eaa3..584ebd3b9b 100644
--- a/interface/resources/icons/tablet-icons/inventory-a.svg
+++ b/interface/resources/icons/tablet-icons/inventory-a.svg
@@ -1,3 +1,16 @@
-<svg width="22" height="26" viewBox="0 0 22 26" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M1 7L11 1L21 7M1 7L11 13M1 7V19L11 25M11 13L21 7M11 13V25M21 7V19L11 25" stroke="black" stroke-width="2" stroke-linejoin="round"/>
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
+<g>
+	<circle cx="27.8" cy="13.3" r="2.4"/>
+	<circle cx="19.7" cy="7.2" r="2.3"/>
+	<circle cx="9.4" cy="6.9" r="2.2"/>
+	<path d="M41.8,17.5l-8.9-5.2c0.1,0.3,0.1,0.7,0.1,1c0,1-0.3,1.8-0.8,2.6l5.1,2.9L25,26l-12.3-7.1l3.1-1.8c-0.4-0.7-0.7-1.6-0.7-2.5
+		c0-0.4,0.1-0.8,0.2-1.2l-7.1,4.1c-0.5,0.3-0.9,0.9-0.9,1.5v16.5c0,0.6,0.3,1.2,0.9,1.5l16,9.2c0.3,0.2,0.6,0.2,0.9,0.2
+		s0.6-0.1,0.9-0.2l16-9.2c0.5-0.3,0.9-0.9,0.9-1.5V19C42.7,18.4,42.3,17.8,41.8,17.5z M10.7,21.7L23.3,29v12.8l-12.5-7.2V21.7z
+		 M39.2,34.5l-12.5,7.2V28.9l12.5-7.2V34.5z"/>
+	<circle cx="25" cy="20.3" r="2.8"/>
+	<circle cx="20" cy="14.6" r="2.4"/>
+</g>
 </svg>
diff --git a/interface/resources/icons/tablet-icons/inventory-i-msg.svg b/interface/resources/icons/tablet-icons/inventory-i-msg.svg
index 35d4fb54ae..d2abc7b7ca 100644
--- a/interface/resources/icons/tablet-icons/inventory-i-msg.svg
+++ b/interface/resources/icons/tablet-icons/inventory-i-msg.svg
@@ -1,4 +1,21 @@
-<svg width="22" height="26" fill="none" version="1.1" viewBox="0 0 22 26" xmlns="http://www.w3.org/2000/svg">
- <path d="M1 7L11 1L21 7M1 7L11 13M1 7V19L11 25M11 13L21 7M11 13V25M21 7V19L11 25" stroke="#fff" stroke-linejoin="round" stroke-width="2"/>
- <circle class="st1" cx="19.41" cy="2.5828" r="2.5846" fill="#ef3b4e" stroke-width=".24043"/>
-</svg>
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#FFFFFF;}
+	.st1{fill:#EF3B4E;}
+</style>
+<g>
+	<circle class="st0" cx="27.8" cy="13.3" r="2.4"/>
+	<circle class="st0" cx="19.7" cy="7.2" r="2.3"/>
+	<circle class="st0" cx="9.4" cy="6.9" r="2.2"/>
+	<path class="st0" d="M41.8,17.5l-8.9-5.2c0.1,0.3,0.1,0.7,0.1,1c0,1-0.3,1.8-0.8,2.6l5.1,2.9L25,26l-12.3-7.1l3.1-1.8
+		c-0.4-0.7-0.7-1.6-0.7-2.5c0-0.4,0.1-0.8,0.2-1.2l-7.1,4.1c-0.5,0.3-0.9,0.9-0.9,1.5v16.5c0,0.6,0.3,1.2,0.9,1.5l16,9.2
+		c0.3,0.2,0.6,0.2,0.9,0.2s0.6-0.1,0.9-0.2l16-9.2c0.5-0.3,0.9-0.9,0.9-1.5V19C42.7,18.4,42.3,17.8,41.8,17.5z M10.7,21.7L23.3,29
+		v12.8l-12.5-7.2V21.7z M39.2,34.5l-12.5,7.2V28.9l12.5-7.2V34.5z"/>
+	<circle class="st0" cx="25" cy="20.3" r="2.8"/>
+	<circle class="st0" cx="20" cy="14.6" r="2.4"/>
+</g>
+<circle class="st1" cx="44.1" cy="6" r="5.6"/>
+</svg>
diff --git a/interface/resources/icons/tablet-icons/inventory-i.svg b/interface/resources/icons/tablet-icons/inventory-i.svg
index 071fabce88..733ea6df9a 100644
--- a/interface/resources/icons/tablet-icons/inventory-i.svg
+++ b/interface/resources/icons/tablet-icons/inventory-i.svg
@@ -1,3 +1,19 @@
-<svg width="22" height="26" viewBox="0 0 22 26" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M1 7L11 1L21 7M1 7L11 13M1 7V19L11 25M11 13L21 7M11 13V25M21 7V19L11 25" stroke="white" stroke-width="2" stroke-linejoin="round"/>
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#FFFFFF;}
+</style>
+<g>
+	<circle class="st0" cx="27.8" cy="13.3" r="2.4"/>
+	<circle class="st0" cx="19.7" cy="7.2" r="2.3"/>
+	<circle class="st0" cx="9.4" cy="6.9" r="2.2"/>
+	<path class="st0" d="M41.8,17.5l-8.9-5.2c0.1,0.3,0.1,0.7,0.1,1c0,1-0.3,1.8-0.8,2.6l5.1,2.9L25,26l-12.3-7.1l3.1-1.8
+		c-0.4-0.7-0.7-1.6-0.7-2.5c0-0.4,0.1-0.8,0.2-1.2l-7.1,4.1c-0.5,0.3-0.9,0.9-0.9,1.5v16.5c0,0.6,0.3,1.2,0.9,1.5l16,9.2
+		c0.3,0.2,0.6,0.2,0.9,0.2s0.6-0.1,0.9-0.2l16-9.2c0.5-0.3,0.9-0.9,0.9-1.5V19C42.7,18.4,42.3,17.8,41.8,17.5z M10.7,21.7L23.3,29
+		v12.8l-12.5-7.2V21.7z M39.2,34.5l-12.5,7.2V28.9l12.5-7.2V34.5z"/>
+	<circle class="st0" cx="25" cy="20.3" r="2.8"/>
+	<circle class="st0" cx="20" cy="14.6" r="2.4"/>
+</g>
 </svg>
diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml
index 81fec4ace3..37ce50b469 100644
--- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml
+++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml
@@ -536,8 +536,8 @@ Rectangle {
             Rectangle {
                 id: exchangeMoneyMessagesWaitingLight;
                 visible: parent.messagesWaiting;
-                anchors.right: exchangeMoneyTabIcon.left;
-                anchors.rightMargin: 9;
+                anchors.left: parent.left;
+                anchors.leftMargin: 16;
                 anchors.top: exchangeMoneyTabIcon.top;
                 anchors.topMargin: 4;
                 height: 10;