From 37541e4ed2ce178508db014664139d650e78b324 Mon Sep 17 00:00:00 2001
From: samcake <samuel.gateau@gmail.com>
Date: Fri, 10 Feb 2017 14:29:29 -0800
Subject: [PATCH 01/64] Start capping the time budget for draw opaque

---
 .../render-utils/src/RenderDeferredTask.cpp   |   3 +-
 libraries/render/src/render/DrawTask.cpp      | 105 ++++++++++++++++++
 libraries/render/src/render/DrawTask.h        |   2 +-
 3 files changed, 108 insertions(+), 2 deletions(-)

diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp
index 55a9c8b9e4..82db502af1 100644
--- a/libraries/render-utils/src/RenderDeferredTask.cpp
+++ b/libraries/render-utils/src/RenderDeferredTask.cpp
@@ -295,7 +295,8 @@ void DrawStateSortDeferred::run(const SceneContextPointer& sceneContext, const R
         batch.setUniformBuffer(render::ShapePipeline::Slot::LIGHTING_MODEL, lightingModel->getParametersBuffer());
 
         if (_stateSort) {
-            renderStateSortShapes(sceneContext, renderContext, _shapePlumber, inItems, _maxDrawn);
+            // renderStateSortShapes(sceneContext, renderContext, _shapePlumber, inItems, _maxDrawn);
+            renderStateSortShapesCapped(sceneContext, renderContext, _shapePlumber, inItems, 2.0, _maxDrawn);
         } else {
             renderShapes(sceneContext, renderContext, _shapePlumber, inItems, _maxDrawn);
         }
diff --git a/libraries/render/src/render/DrawTask.cpp b/libraries/render/src/render/DrawTask.cpp
index 2829c6f8e7..d6f09f735c 100755
--- a/libraries/render/src/render/DrawTask.cpp
+++ b/libraries/render/src/render/DrawTask.cpp
@@ -123,6 +123,111 @@ void render::renderStateSortShapes(const SceneContextPointer& sceneContext, cons
     }
 }
 
+
+void render::renderStateSortShapesCapped(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ShapePlumberPointer& shapeContext, const ItemBounds& inItems, double mstimeBudget, int maxDrawnItems) {
+
+    auto& scene = sceneContext->_scene;
+    RenderArgs* args = renderContext->args;
+    auto now = usecTimestampNow();
+
+    int numItemsToDraw = (int)inItems.size();
+    if (maxDrawnItems != -1) {
+        numItemsToDraw = glm::min(numItemsToDraw, maxDrawnItems);
+    }
+
+    using SortedPipelines = std::vector<render::ShapeKey>;
+    using SortedShapes = std::unordered_map<render::ShapeKey, std::vector<Item>, render::ShapeKey::Hash, render::ShapeKey::KeyEqual>;
+    SortedPipelines sortedPipelines;
+    SortedShapes sortedShapes;
+    std::vector<Item> ownPipelineBucket;
+
+    {
+        PROFILE_RANGE(render_detail, "sort");
+
+        for (auto i = 0; i < numItemsToDraw; ++i) {
+            auto item = scene->getItem(inItems[i].id);
+
+            {
+                assert(item.getKey().isShape());
+                const auto key = item.getShapeKey();
+                if (key.isValid() && !key.hasOwnPipeline()) {
+                    auto& bucket = sortedShapes[key];
+                    if (bucket.empty()) {
+                        sortedPipelines.push_back(key);
+                    }
+                    bucket.push_back(item);
+                } else if (key.hasOwnPipeline()) {
+                    ownPipelineBucket.push_back(item);
+                } else {
+                    qCDebug(renderlogging) << "Item could not be rendered with invalid key" << key;
+                }
+            }
+        }
+    }
+
+    {
+        PROFILE_RANGE(render_detail, "render");
+
+        // Then render
+        quint64 usecHalfBudget = 1000 * 0.5 * mstimeBudget;
+        quint64 usecBudget = 1000 * mstimeBudget;
+
+        int numDrawCalls = 0;
+        int numDrawcallsChecks = 128;
+        int numChecks = 0;
+        int numChecksBudget = 4;
+
+        for (auto& pipelineKey : sortedPipelines) {
+            auto& bucket = sortedShapes[pipelineKey];
+            args->_pipeline = shapeContext->pickPipeline(args, pipelineKey);
+            if (!args->_pipeline) {
+                continue;
+            }
+            for (auto& item : bucket) {
+                item.render(args);
+
+                numDrawCalls++;
+                if ((numDrawCalls % numDrawcallsChecks) == 0) {
+                    auto newNow = usecTimestampNow();
+                    if ((newNow - now) > usecHalfBudget) {
+                        if ((newNow - now) > usecBudget) {
+                            return;
+                        }
+
+                        usecHalfBudget += (usecHalfBudget / 2);
+                        numDrawcallsChecks / 2;
+                        numChecks++;
+                        if (numChecks > numChecksBudget) {
+                            return;
+                        }
+                    }
+                }
+            }
+        }
+        args->_pipeline = nullptr;
+        for (auto& item : ownPipelineBucket) {
+            item.render(args);
+
+            numDrawCalls++;
+            if ((numDrawCalls % numDrawcallsChecks) == 0) {
+                auto newNow = usecTimestampNow();
+                if ((newNow - now) > usecHalfBudget) {
+                    if ((newNow - now) > usecBudget) {
+                        return;
+                    }
+
+                    usecHalfBudget += (usecHalfBudget / 2);
+                    numDrawcallsChecks / 2;
+                    numChecks++;
+                    if (numChecks > numChecksBudget) {
+                        return;
+                    }
+                }
+            }
+        }
+    }
+}
+
 void DrawLight::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inLights) {
     assert(renderContext->args);
     assert(renderContext->args->hasViewFrustum());
diff --git a/libraries/render/src/render/DrawTask.h b/libraries/render/src/render/DrawTask.h
index 27f07921c3..042a927caf 100755
--- a/libraries/render/src/render/DrawTask.h
+++ b/libraries/render/src/render/DrawTask.h
@@ -19,7 +19,7 @@ namespace render {
 void renderItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems, int maxDrawnItems = -1);
 void renderShapes(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ShapePlumberPointer& shapeContext, const ItemBounds& inItems, int maxDrawnItems = -1);
 void renderStateSortShapes(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ShapePlumberPointer& shapeContext, const ItemBounds& inItems, int maxDrawnItems = -1);
-
+void renderStateSortShapesCapped(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ShapePlumberPointer& shapeContext, const ItemBounds& inItems, double mstimeBudget, int maxDrawnItems = -1);
 
 
 class DrawLightConfig : public Job::Config {

From 9261e81ba2f4004fad6b613e54ee29acaf6ee8ce Mon Sep 17 00:00:00 2001
From: samcake <samuel.gateau@gmail.com>
Date: Fri, 10 Feb 2017 17:47:56 -0800
Subject: [PATCH 02/64] Cleaning up the interface

---
 .../render-utils/src/RenderDeferredTask.cpp      |  2 +-
 libraries/render-utils/src/RenderDeferredTask.h  |  5 ++++-
 scripts/developer/utilities/render/culling.qml   | 16 ++++++++++++++++
 3 files changed, 21 insertions(+), 2 deletions(-)

diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp
index 82db502af1..24e284a416 100644
--- a/libraries/render-utils/src/RenderDeferredTask.cpp
+++ b/libraries/render-utils/src/RenderDeferredTask.cpp
@@ -296,7 +296,7 @@ void DrawStateSortDeferred::run(const SceneContextPointer& sceneContext, const R
 
         if (_stateSort) {
             // renderStateSortShapes(sceneContext, renderContext, _shapePlumber, inItems, _maxDrawn);
-            renderStateSortShapesCapped(sceneContext, renderContext, _shapePlumber, inItems, 2.0, _maxDrawn);
+            renderStateSortShapesCapped(sceneContext, renderContext, _shapePlumber, inItems, _maxTimeBudget, _maxDrawn);
         } else {
             renderShapes(sceneContext, renderContext, _shapePlumber, inItems, _maxDrawn);
         }
diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h
index 8a95447e67..2d9a7d4888 100644
--- a/libraries/render-utils/src/RenderDeferredTask.h
+++ b/libraries/render-utils/src/RenderDeferredTask.h
@@ -85,6 +85,7 @@ class DrawStateSortConfig : public render::Job::Config {
     Q_OBJECT
         Q_PROPERTY(int numDrawn READ getNumDrawn NOTIFY numDrawnChanged)
         Q_PROPERTY(int maxDrawn MEMBER maxDrawn NOTIFY dirty)
+        Q_PROPERTY(float maxTimeBudget MEMBER maxTimeBudget NOTIFY dirty)
         Q_PROPERTY(bool stateSort MEMBER stateSort NOTIFY dirty)
 public:
 
@@ -92,6 +93,7 @@ public:
     void setNumDrawn(int num) { numDrawn = num; emit numDrawnChanged(); }
 
     int maxDrawn{ -1 };
+    float maxTimeBudget { 8.0f }; // 8ms maximum
     bool stateSort{ true };
 
 signals:
@@ -111,12 +113,13 @@ public:
 
     DrawStateSortDeferred(render::ShapePlumberPointer shapePlumber) : _shapePlumber{ shapePlumber } {}
 
-    void configure(const Config& config) { _maxDrawn = config.maxDrawn; _stateSort = config.stateSort; }
+    void configure(const Config& config) { _maxDrawn = config.maxDrawn; _maxTimeBudget = config.maxTimeBudget; _stateSort = config.stateSort; }
     void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const Inputs& inputs);
 
 protected:
     render::ShapePlumberPointer _shapePlumber;
     int _maxDrawn; // initialized by Config
+    float _maxTimeBudget; //ms time budget
     bool _stateSort;
 };
 
diff --git a/scripts/developer/utilities/render/culling.qml b/scripts/developer/utilities/render/culling.qml
index e3f5e67bbe..4157b5e4dd 100644
--- a/scripts/developer/utilities/render/culling.qml
+++ b/scripts/developer/utilities/render/culling.qml
@@ -95,6 +95,7 @@ Column {
 
     GroupBox {
         title: "Render Items"
+        Column{
 
         Column{
             Repeater {
@@ -110,5 +111,20 @@ Column {
                 }
             }
         }
+
+        Column{
+            Repeater {
+                model: [ "Draw Opaque [ms]:DrawOpaqueDeferred" ]
+                ConfigSlider {
+                    label: qsTr(modelData.split(":")[0])
+                    integral: false
+                    config: Render.getConfig(modelData.split(":")[1])
+                    property: "maxTimeBudget"
+                    max: 10.0
+                    min: 0.0
+                }
+            }
+        }
+        }
     }
 }

From 127a014b4694fdbd8dac2d16799249b82d49a203 Mon Sep 17 00:00:00 2001
From: Brad Davis <bdavis@saintandreas.org>
Date: Wed, 15 Feb 2017 15:21:08 -0800
Subject: [PATCH 03/64] Clear caches on domain switch

---
 interface/src/Application.cpp              | 7 +++++++
 libraries/networking/src/ResourceCache.cpp | 6 +++---
 libraries/networking/src/ResourceCache.h   | 4 ++--
 3 files changed, 12 insertions(+), 5 deletions(-)

diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 488e97b5e6..1b83419db7 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -5152,6 +5152,7 @@ void Application::updateWindowTitle() const {
 #endif
     _window->setWindowTitle(title);
 }
+
 void Application::clearDomainOctreeDetails() {
 
     // if we're about to quit, we really don't need to do any of these things...
@@ -5181,6 +5182,12 @@ void Application::clearDomainOctreeDetails() {
     skyStage->setBackgroundMode(model::SunSkyStage::SKY_DEFAULT);
 
     _recentlyClearedDomain = true;
+
+    DependencyManager::get<AvatarManager>()->clearOtherAvatars();
+    DependencyManager::get<AnimationCache>()->clearUnusedResources();
+    DependencyManager::get<ModelCache>()->clearUnusedResources();
+    DependencyManager::get<SoundCache>()->clearUnusedResources();
+    DependencyManager::get<TextureCache>()->clearUnusedResources();
 }
 
 void Application::domainChanged(const QString& domainHostname) {
diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp
index d95c6f140f..0396e0ed94 100644
--- a/libraries/networking/src/ResourceCache.cpp
+++ b/libraries/networking/src/ResourceCache.cpp
@@ -221,7 +221,7 @@ ResourceCache::ResourceCache(QObject* parent) : QObject(parent) {
 }
 
 ResourceCache::~ResourceCache() {
-    clearUnusedResource();
+    clearUnusedResources();
 }
 
 void ResourceCache::clearATPAssets() {
@@ -265,7 +265,7 @@ void ResourceCache::clearATPAssets() {
 
 void ResourceCache::refreshAll() {
     // Clear all unused resources so we don't have to reload them
-    clearUnusedResource();
+    clearUnusedResources();
     resetResourceCounters();
 
     QHash<QUrl, QWeakPointer<Resource>> resources;
@@ -418,7 +418,7 @@ void ResourceCache::reserveUnusedResource(qint64 resourceSize) {
     }
 }
 
-void ResourceCache::clearUnusedResource() {
+void ResourceCache::clearUnusedResources() {
     // the unused resources may themselves reference resources that will be added to the unused
     // list on destruction, so keep clearing until there are no references left
     QWriteLocker locker(&_unusedResourcesLock);
diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h
index a369416ebe..8f1f1baed2 100644
--- a/libraries/networking/src/ResourceCache.h
+++ b/libraries/networking/src/ResourceCache.h
@@ -249,6 +249,7 @@ public:
     
     void refreshAll();
     void refresh(const QUrl& url);
+    void clearUnusedResources();
 
 signals:
     void dirty();
@@ -298,7 +299,7 @@ protected:
     
     void addUnusedResource(const QSharedPointer<Resource>& resource);
     void removeUnusedResource(const QSharedPointer<Resource>& resource);
-    
+
     /// Attempt to load a resource if requests are below the limit, otherwise queue the resource for loading
     /// \return true if the resource began loading, otherwise false if the resource is in the pending queue
     static bool attemptRequest(QSharedPointer<Resource> resource);
@@ -309,7 +310,6 @@ private:
     friend class Resource;
 
     void reserveUnusedResource(qint64 resourceSize);
-    void clearUnusedResource();
     void resetResourceCounters();
     void removeResource(const QUrl& url, qint64 size = 0);
 

From 55611d2373f6fd4fa7f1e1ced5687ac634529c3a Mon Sep 17 00:00:00 2001
From: samcake <samuel.gateau@gmail.com>
Date: Tue, 21 Feb 2017 14:56:07 -0800
Subject: [PATCH 04/64] Backing up to master

---
 .../render-utils/src/RenderDeferredTask.cpp   |   3 +-
 .../render-utils/src/RenderDeferredTask.h     |   5 +-
 libraries/render/src/render/DrawTask.cpp      | 105 ------------------
 libraries/render/src/render/DrawTask.h        |   1 -
 .../developer/utilities/render/culling.qml    |  16 ---
 5 files changed, 2 insertions(+), 128 deletions(-)

diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp
index 24e284a416..55a9c8b9e4 100644
--- a/libraries/render-utils/src/RenderDeferredTask.cpp
+++ b/libraries/render-utils/src/RenderDeferredTask.cpp
@@ -295,8 +295,7 @@ void DrawStateSortDeferred::run(const SceneContextPointer& sceneContext, const R
         batch.setUniformBuffer(render::ShapePipeline::Slot::LIGHTING_MODEL, lightingModel->getParametersBuffer());
 
         if (_stateSort) {
-            // renderStateSortShapes(sceneContext, renderContext, _shapePlumber, inItems, _maxDrawn);
-            renderStateSortShapesCapped(sceneContext, renderContext, _shapePlumber, inItems, _maxTimeBudget, _maxDrawn);
+            renderStateSortShapes(sceneContext, renderContext, _shapePlumber, inItems, _maxDrawn);
         } else {
             renderShapes(sceneContext, renderContext, _shapePlumber, inItems, _maxDrawn);
         }
diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h
index 2d9a7d4888..8a95447e67 100644
--- a/libraries/render-utils/src/RenderDeferredTask.h
+++ b/libraries/render-utils/src/RenderDeferredTask.h
@@ -85,7 +85,6 @@ class DrawStateSortConfig : public render::Job::Config {
     Q_OBJECT
         Q_PROPERTY(int numDrawn READ getNumDrawn NOTIFY numDrawnChanged)
         Q_PROPERTY(int maxDrawn MEMBER maxDrawn NOTIFY dirty)
-        Q_PROPERTY(float maxTimeBudget MEMBER maxTimeBudget NOTIFY dirty)
         Q_PROPERTY(bool stateSort MEMBER stateSort NOTIFY dirty)
 public:
 
@@ -93,7 +92,6 @@ public:
     void setNumDrawn(int num) { numDrawn = num; emit numDrawnChanged(); }
 
     int maxDrawn{ -1 };
-    float maxTimeBudget { 8.0f }; // 8ms maximum
     bool stateSort{ true };
 
 signals:
@@ -113,13 +111,12 @@ public:
 
     DrawStateSortDeferred(render::ShapePlumberPointer shapePlumber) : _shapePlumber{ shapePlumber } {}
 
-    void configure(const Config& config) { _maxDrawn = config.maxDrawn; _maxTimeBudget = config.maxTimeBudget; _stateSort = config.stateSort; }
+    void configure(const Config& config) { _maxDrawn = config.maxDrawn; _stateSort = config.stateSort; }
     void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const Inputs& inputs);
 
 protected:
     render::ShapePlumberPointer _shapePlumber;
     int _maxDrawn; // initialized by Config
-    float _maxTimeBudget; //ms time budget
     bool _stateSort;
 };
 
diff --git a/libraries/render/src/render/DrawTask.cpp b/libraries/render/src/render/DrawTask.cpp
index d6f09f735c..2829c6f8e7 100755
--- a/libraries/render/src/render/DrawTask.cpp
+++ b/libraries/render/src/render/DrawTask.cpp
@@ -123,111 +123,6 @@ void render::renderStateSortShapes(const SceneContextPointer& sceneContext, cons
     }
 }
 
-
-void render::renderStateSortShapesCapped(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ShapePlumberPointer& shapeContext, const ItemBounds& inItems, double mstimeBudget, int maxDrawnItems) {
-
-    auto& scene = sceneContext->_scene;
-    RenderArgs* args = renderContext->args;
-    auto now = usecTimestampNow();
-
-    int numItemsToDraw = (int)inItems.size();
-    if (maxDrawnItems != -1) {
-        numItemsToDraw = glm::min(numItemsToDraw, maxDrawnItems);
-    }
-
-    using SortedPipelines = std::vector<render::ShapeKey>;
-    using SortedShapes = std::unordered_map<render::ShapeKey, std::vector<Item>, render::ShapeKey::Hash, render::ShapeKey::KeyEqual>;
-    SortedPipelines sortedPipelines;
-    SortedShapes sortedShapes;
-    std::vector<Item> ownPipelineBucket;
-
-    {
-        PROFILE_RANGE(render_detail, "sort");
-
-        for (auto i = 0; i < numItemsToDraw; ++i) {
-            auto item = scene->getItem(inItems[i].id);
-
-            {
-                assert(item.getKey().isShape());
-                const auto key = item.getShapeKey();
-                if (key.isValid() && !key.hasOwnPipeline()) {
-                    auto& bucket = sortedShapes[key];
-                    if (bucket.empty()) {
-                        sortedPipelines.push_back(key);
-                    }
-                    bucket.push_back(item);
-                } else if (key.hasOwnPipeline()) {
-                    ownPipelineBucket.push_back(item);
-                } else {
-                    qCDebug(renderlogging) << "Item could not be rendered with invalid key" << key;
-                }
-            }
-        }
-    }
-
-    {
-        PROFILE_RANGE(render_detail, "render");
-
-        // Then render
-        quint64 usecHalfBudget = 1000 * 0.5 * mstimeBudget;
-        quint64 usecBudget = 1000 * mstimeBudget;
-
-        int numDrawCalls = 0;
-        int numDrawcallsChecks = 128;
-        int numChecks = 0;
-        int numChecksBudget = 4;
-
-        for (auto& pipelineKey : sortedPipelines) {
-            auto& bucket = sortedShapes[pipelineKey];
-            args->_pipeline = shapeContext->pickPipeline(args, pipelineKey);
-            if (!args->_pipeline) {
-                continue;
-            }
-            for (auto& item : bucket) {
-                item.render(args);
-
-                numDrawCalls++;
-                if ((numDrawCalls % numDrawcallsChecks) == 0) {
-                    auto newNow = usecTimestampNow();
-                    if ((newNow - now) > usecHalfBudget) {
-                        if ((newNow - now) > usecBudget) {
-                            return;
-                        }
-
-                        usecHalfBudget += (usecHalfBudget / 2);
-                        numDrawcallsChecks / 2;
-                        numChecks++;
-                        if (numChecks > numChecksBudget) {
-                            return;
-                        }
-                    }
-                }
-            }
-        }
-        args->_pipeline = nullptr;
-        for (auto& item : ownPipelineBucket) {
-            item.render(args);
-
-            numDrawCalls++;
-            if ((numDrawCalls % numDrawcallsChecks) == 0) {
-                auto newNow = usecTimestampNow();
-                if ((newNow - now) > usecHalfBudget) {
-                    if ((newNow - now) > usecBudget) {
-                        return;
-                    }
-
-                    usecHalfBudget += (usecHalfBudget / 2);
-                    numDrawcallsChecks / 2;
-                    numChecks++;
-                    if (numChecks > numChecksBudget) {
-                        return;
-                    }
-                }
-            }
-        }
-    }
-}
-
 void DrawLight::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inLights) {
     assert(renderContext->args);
     assert(renderContext->args->hasViewFrustum());
diff --git a/libraries/render/src/render/DrawTask.h b/libraries/render/src/render/DrawTask.h
index 042a927caf..890001ded0 100755
--- a/libraries/render/src/render/DrawTask.h
+++ b/libraries/render/src/render/DrawTask.h
@@ -19,7 +19,6 @@ namespace render {
 void renderItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems, int maxDrawnItems = -1);
 void renderShapes(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ShapePlumberPointer& shapeContext, const ItemBounds& inItems, int maxDrawnItems = -1);
 void renderStateSortShapes(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ShapePlumberPointer& shapeContext, const ItemBounds& inItems, int maxDrawnItems = -1);
-void renderStateSortShapesCapped(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ShapePlumberPointer& shapeContext, const ItemBounds& inItems, double mstimeBudget, int maxDrawnItems = -1);
 
 
 class DrawLightConfig : public Job::Config {
diff --git a/scripts/developer/utilities/render/culling.qml b/scripts/developer/utilities/render/culling.qml
index 4157b5e4dd..e3f5e67bbe 100644
--- a/scripts/developer/utilities/render/culling.qml
+++ b/scripts/developer/utilities/render/culling.qml
@@ -95,7 +95,6 @@ Column {
 
     GroupBox {
         title: "Render Items"
-        Column{
 
         Column{
             Repeater {
@@ -111,20 +110,5 @@ Column {
                 }
             }
         }
-
-        Column{
-            Repeater {
-                model: [ "Draw Opaque [ms]:DrawOpaqueDeferred" ]
-                ConfigSlider {
-                    label: qsTr(modelData.split(":")[0])
-                    integral: false
-                    config: Render.getConfig(modelData.split(":")[1])
-                    property: "maxTimeBudget"
-                    max: 10.0
-                    min: 0.0
-                }
-            }
-        }
-        }
     }
 }

From 3cd422e5081b69e206975ac2f3effc5ea54419b4 Mon Sep 17 00:00:00 2001
From: samcake <samuel.gateau@gmail.com>
Date: Tue, 21 Feb 2017 15:02:12 -0800
Subject: [PATCH 05/64] Last clean up

---
 libraries/render/src/render/DrawTask.h | 1 -
 1 file changed, 1 deletion(-)

diff --git a/libraries/render/src/render/DrawTask.h b/libraries/render/src/render/DrawTask.h
index 890001ded0..6e0e5ba10b 100755
--- a/libraries/render/src/render/DrawTask.h
+++ b/libraries/render/src/render/DrawTask.h
@@ -20,7 +20,6 @@ void renderItems(const SceneContextPointer& sceneContext, const RenderContextPoi
 void renderShapes(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ShapePlumberPointer& shapeContext, const ItemBounds& inItems, int maxDrawnItems = -1);
 void renderStateSortShapes(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ShapePlumberPointer& shapeContext, const ItemBounds& inItems, int maxDrawnItems = -1);
 
-
 class DrawLightConfig : public Job::Config {
     Q_OBJECT
     Q_PROPERTY(int numDrawn READ getNumDrawn NOTIFY numDrawnChanged)

From 6d7fab40530c781ffed6f6b6506352c678435033 Mon Sep 17 00:00:00 2001
From: David Kelly <david@highfidelity.io>
Date: Tue, 21 Feb 2017 16:59:08 -0800
Subject: [PATCH 06/64] Agent Avatars sending loudness in AvatarData

---
 assignment-client/src/Agent.cpp | 53 +++++++++++++++++++++++++--------
 assignment-client/src/Agent.h   |  1 +
 2 files changed, 42 insertions(+), 12 deletions(-)

diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp
index bea677aeb6..cd8c8189b2 100644
--- a/assignment-client/src/Agent.cpp
+++ b/assignment-client/src/Agent.cpp
@@ -88,9 +88,9 @@ void Agent::playAvatarSound(SharedSoundPointer sound) {
         QMetaObject::invokeMethod(this, "playAvatarSound", Q_ARG(SharedSoundPointer, sound));
         return;
     } else {
-        // TODO: seems to add occasional artifact in tests.  I believe it is 
+        // TODO: seems to add occasional artifact in tests.  I believe it is
         // correct to do this, but need to figure out for sure, so commenting this
-        // out until I verify.  
+        // out until I verify.
         // _numAvatarSoundSentBytes = 0;
         setAvatarSound(sound);
     }
@@ -105,7 +105,7 @@ void Agent::handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNo
         if (message->getSize() > statsMessageLength) {
             // pull out the piggybacked packet and create a new QSharedPointer<NLPacket> for it
             int piggyBackedSizeWithHeader = message->getSize() - statsMessageLength;
-            
+
             auto buffer = std::unique_ptr<char[]>(new char[piggyBackedSizeWithHeader]);
             memcpy(buffer.get(), message->getRawMessage() + statsMessageLength, piggyBackedSizeWithHeader);
 
@@ -284,7 +284,7 @@ void Agent::selectAudioFormat(const QString& selectedCodecName) {
     for (auto& plugin : codecPlugins) {
         if (_selectedCodecName == plugin->getName()) {
             _codec = plugin;
-            _receivedAudioStream.setupCodec(plugin, _selectedCodecName, AudioConstants::STEREO); 
+            _receivedAudioStream.setupCodec(plugin, _selectedCodecName, AudioConstants::STEREO);
             _encoder = plugin->createEncoder(AudioConstants::SAMPLE_RATE, AudioConstants::MONO);
             qDebug() << "Selected Codec Plugin:" << _codec.get();
             break;
@@ -424,16 +424,16 @@ void Agent::executeScript() {
     entityScriptingInterface->setEntityTree(_entityViewer.getTree());
 
     DependencyManager::set<AssignmentParentFinder>(_entityViewer.getTree());
-    
+
     // 100Hz timer for audio
     AvatarAudioTimer* audioTimerWorker = new AvatarAudioTimer();
     audioTimerWorker->moveToThread(&_avatarAudioTimerThread);
     connect(audioTimerWorker, &AvatarAudioTimer::avatarTick, this, &Agent::processAgentAvatarAudio);
     connect(this, &Agent::startAvatarAudioTimer, audioTimerWorker, &AvatarAudioTimer::start);
     connect(this, &Agent::stopAvatarAudioTimer, audioTimerWorker, &AvatarAudioTimer::stop);
-    connect(&_avatarAudioTimerThread, &QThread::finished, audioTimerWorker, &QObject::deleteLater); 
+    connect(&_avatarAudioTimerThread, &QThread::finished, audioTimerWorker, &QObject::deleteLater);
     _avatarAudioTimerThread.start();
-    
+
     // 60Hz timer for avatar
     QObject::connect(_scriptEngine.get(), &ScriptEngine::update, this, &Agent::processAgentAvatar);
     _scriptEngine->run();
@@ -448,14 +448,14 @@ QUuid Agent::getSessionUUID() const {
     return DependencyManager::get<NodeList>()->getSessionUUID();
 }
 
-void Agent::setIsListeningToAudioStream(bool isListeningToAudioStream) { 
+void Agent::setIsListeningToAudioStream(bool isListeningToAudioStream) {
     // this must happen on Agent's main thread
     if (QThread::currentThread() != thread()) {
         QMetaObject::invokeMethod(this, "setIsListeningToAudioStream", Q_ARG(bool, isListeningToAudioStream));
         return;
     }
     if (_isListeningToAudioStream) {
-        // have to tell just the audio mixer to KillAvatar. 
+        // have to tell just the audio mixer to KillAvatar.
 
         auto nodeList = DependencyManager::get<NodeList>();
         nodeList->eachMatchingNode(
@@ -471,7 +471,7 @@ void Agent::setIsListeningToAudioStream(bool isListeningToAudioStream) {
         });
 
     }
-    _isListeningToAudioStream = isListeningToAudioStream; 
+    _isListeningToAudioStream = isListeningToAudioStream;
 }
 
 void Agent::setIsAvatar(bool isAvatar) {
@@ -552,6 +552,7 @@ void Agent::processAgentAvatar() {
         nodeList->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer);
     }
 }
+
 void Agent::encodeFrameOfZeros(QByteArray& encodedZeros) {
     _flushEncoder = false;
     static const QByteArray zeros(AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL, 0);
@@ -562,6 +563,23 @@ void Agent::encodeFrameOfZeros(QByteArray& encodedZeros) {
     }
 }
 
+void Agent::computeLoudness(QByteArray* decodedBuffer) {
+    float loudness = 0.0f;
+    auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
+    if (decodedBuffer) {
+        auto soundData = reinterpret_cast<const int16_t*>(decodedBuffer->constData());
+        auto numFrames = decodedBuffer->size() / sizeof(int16_t);
+        // now iterate and come up with average
+        if (numFrames > 0) {
+            for(int i = 0; i < numFrames; i++) {
+                loudness += (float) std::abs(soundData[i]);
+            }
+            loudness /= numFrames;
+        }
+    }
+    scriptedAvatar->setAudioLoudness(loudness);
+}
+
 void Agent::processAgentAvatarAudio() {
     auto recordingInterface = DependencyManager::get<RecordingScriptingInterface>();
     bool isPlayingRecording = recordingInterface->isPlaying();
@@ -611,6 +629,9 @@ void Agent::processAgentAvatarAudio() {
         audioPacket->seek(sizeof(quint16));
 
         if (silentFrame) {
+            // no matter what, the loudness should be set to 0
+            computeLoudness(nullptr);
+
             if (!_isListeningToAudioStream) {
                 // if we have a silent frame and we're not listening then just send nothing and break out of here
                 return;
@@ -618,7 +639,7 @@ void Agent::processAgentAvatarAudio() {
 
             // write the codec
             audioPacket->writeString(_selectedCodecName);
-            
+
             // write the number of silent samples so the audio-mixer can uphold timing
             audioPacket->writePrimitive(numAvailableSamples);
 
@@ -628,8 +649,9 @@ void Agent::processAgentAvatarAudio() {
             audioPacket->writePrimitive(headOrientation);
             audioPacket->writePrimitive(scriptedAvatar->getPosition());
             audioPacket->writePrimitive(glm::vec3(0));
+
         } else if (nextSoundOutput) {
-            
+
             // write the codec
             audioPacket->writeString(_selectedCodecName);
 
@@ -646,6 +668,8 @@ void Agent::processAgentAvatarAudio() {
             QByteArray encodedBuffer;
             if (_flushEncoder) {
                 encodeFrameOfZeros(encodedBuffer);
+                // loudness is 0
+                computeLoudness(nullptr);
             } else {
                 QByteArray decodedBuffer(reinterpret_cast<const char*>(nextSoundOutput), numAvailableSamples*sizeof(int16_t));
                 if (_encoder) {
@@ -654,10 +678,15 @@ void Agent::processAgentAvatarAudio() {
                 } else {
                     encodedBuffer = decodedBuffer;
                 }
+                computeLoudness(&decodedBuffer);
             }
             audioPacket->write(encodedBuffer.constData(), encodedBuffer.size());
         }
 
+        // we should never have both nextSoundOutput being null and silentFrame being false, but lets
+        // assert on it in case things above change in a bad way
+        assert(nextSoundOutput || silentFrame);
+
         // write audio packet to AudioMixer nodes
         auto nodeList = DependencyManager::get<NodeList>();
         nodeList->eachNode([this, &nodeList, &audioPacket](const SharedNodePointer& node) {
diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h
index c9b1707101..7f04b4746f 100644
--- a/assignment-client/src/Agent.h
+++ b/assignment-client/src/Agent.h
@@ -82,6 +82,7 @@ private:
     void negotiateAudioFormat();
     void selectAudioFormat(const QString& selectedCodecName);
     void encodeFrameOfZeros(QByteArray& encodedZeros);
+    void computeLoudness(QByteArray* decodedBuffer);
 
     std::unique_ptr<ScriptEngine> _scriptEngine;
     EntityEditPacketSender _entityEditSender;

From a00216cb4f43d967ac49f6e05a528eab00a44b5b Mon Sep 17 00:00:00 2001
From: samcake <samuel.gateau@gmail.com>
Date: Tue, 21 Feb 2017 18:20:55 -0800
Subject: [PATCH 07/64] debugging the emissive issue for verlay in front

---
 .../render-utils/src/RenderPipelines.cpp      |   7 +-
 .../render-utils/src/overlay3D_model.slf      | 134 ++++++++++++++++++
 .../src/overlay3D_translucent.slf             |   2 +
 3 files changed, 141 insertions(+), 2 deletions(-)
 create mode 100644 libraries/render-utils/src/overlay3D_model.slf

diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp
index c5a6c4b6ca..53fc9153e8 100644
--- a/libraries/render-utils/src/RenderPipelines.cpp
+++ b/libraries/render-utils/src/RenderPipelines.cpp
@@ -50,6 +50,7 @@
 
 #include "overlay3D_vert.h"
 #include "overlay3D_frag.h"
+#include "overlay3D_model_frag.h"
 #include "overlay3D_translucent_frag.h"
 #include "overlay3D_unlit_frag.h"
 #include "overlay3D_translucent_unlit_frag.h"
@@ -70,17 +71,19 @@ void lightBatchSetter(const ShapePipeline& pipeline, gpu::Batch& batch);
 
 void initOverlay3DPipelines(ShapePlumber& plumber) {
     auto vertex = gpu::Shader::createVertex(std::string(overlay3D_vert));
-    auto pixel = gpu::Shader::createPixel(std::string(overlay3D_frag));
+    auto pixel = gpu::Shader::createPixel(std::string(overlay3D_model_frag));
     auto pixelTranslucent = gpu::Shader::createPixel(std::string(overlay3D_translucent_frag));
     auto pixelUnlit = gpu::Shader::createPixel(std::string(overlay3D_unlit_frag));
     auto pixelTranslucentUnlit = gpu::Shader::createPixel(std::string(overlay3D_translucent_unlit_frag));
+    auto pixelMaterial = gpu::Shader::createPixel(std::string(overlay3D_model_frag));
 
     auto opaqueProgram = gpu::Shader::createProgram(vertex, pixel);
     auto translucentProgram = gpu::Shader::createProgram(vertex, pixelTranslucent);
     auto unlitOpaqueProgram = gpu::Shader::createProgram(vertex, pixelUnlit);
     auto unlitTranslucentProgram = gpu::Shader::createProgram(vertex, pixelTranslucentUnlit);
+    auto opaqueMaterialProgram = gpu::Shader::createProgram(vertex, pixelMaterial);
 
-    for (int i = 0; i < 8; i++) {
+    for (int i = 0; i < 16; i++) {
         bool isCulled = (i & 1);
         bool isBiased = (i & 2);
         bool isOpaque = (i & 4);
diff --git a/libraries/render-utils/src/overlay3D_model.slf b/libraries/render-utils/src/overlay3D_model.slf
new file mode 100644
index 0000000000..dcc5eac58c
--- /dev/null
+++ b/libraries/render-utils/src/overlay3D_model.slf
@@ -0,0 +1,134 @@
+<@include gpu/Config.slh@>
+<$VERSION_HEADER$>
+//  Generated on <$_SCRIBE_DATE$>
+//  overlay3D.slf
+//  fragment shader
+//
+//  Created by Sam Gateau on 6/16/15.
+//  Copyright 2015 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+
+<@include model/Light.slh@>
+<$declareLightBuffer()$>
+<$declareLightAmbientBuffer()$>
+
+<@include LightingModel.slh@>
+
+<@include LightDirectional.slh@>
+<$declareLightingDirectional()$>
+
+<@include model/Material.slh@>
+
+<@include gpu/Transform.slh@>
+<$declareStandardCameraTransform()$>
+
+<@include MaterialTextures.slh@>
+<$declareMaterialTextures(ALBEDO, ROUGHNESS, _SCRIBE_NULL, _SCRIBE_NULL, EMISSIVE, OCCLUSION)$>
+
+vec4 evalGlobalColor(float shadowAttenuation, vec3 position, vec3 normal, vec3 albedo, float metallic, vec3 fresnel, float roughness, float opacity) {
+
+    // Need the light now
+    Light light = getLight();
+    vec3 lightDirection = getLightDirection(light);
+    vec3 lightIrradiance = getLightIrradiance(light);
+
+    LightAmbient ambient = getLightAmbient();
+
+    TransformCamera cam = getTransformCamera();
+    vec3 fragNormal;
+    <$transformEyeToWorldDir(cam, normal, fragNormal)$>
+    vec3 fragEyeVectorView = normalize(-position);
+    vec3 fragEyeDir;
+    <$transformEyeToWorldDir(cam, fragEyeVectorView, fragEyeDir)$>
+
+    vec3 color = opacity * albedo * getLightColor(light) * getLightAmbientIntensity(ambient);
+
+    // Directional
+    vec3 directionalDiffuse;
+    vec3 directionalSpecular;
+    evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, fragEyeDir, fragNormal, roughness, metallic, fresnel, albedo, shadowAttenuation);
+    color += directionalDiffuse * isDiffuseEnabled() * isDirectionalEnabled();
+    color += directionalSpecular * isSpecularEnabled() * isDirectionalEnabled();
+
+    return vec4(color, opacity);
+}
+
+
+in vec2 _texCoord0;
+in vec2 _texCoord1;
+in vec4 _position;
+in vec3 _normal;
+in vec3 _color;
+in float _alpha;
+
+out vec4 _fragColor;
+
+void main(void) {
+    Material mat = getMaterial();
+    int matKey = getMaterialKey(mat);
+    <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, _SCRIBE_NULL, emissiveTex)$>
+    <$fetchMaterialTexturesCoord1(matKey, _texCoord1, occlusionTex)$>
+
+    float opacity = 1.0;
+    <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>;
+    <$discardTransparent(opacity)$>;
+
+    vec3 albedo = getMaterialAlbedo(mat);
+    <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>;
+    albedo *= _color;
+
+    float metallic = getMaterialMetallic(mat);
+    vec3 fresnel = vec3(0.03); // Default Di-electric fresnel value
+    if (metallic <= 0.5) {
+        metallic = 0.0;
+    } else {
+        fresnel = albedo;
+        metallic = 1.0;
+    }
+
+    float roughness = getMaterialRoughness(mat);
+    <$evalMaterialRoughness(roughnessTex, roughness, matKey, roughness)$>;
+
+    vec3 emissive = getMaterialEmissive(mat);
+    <$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>;
+
+
+    vec3 fragPosition = _position.xyz;
+    vec3 fragNormal = normalize(_normal);
+
+    TransformCamera cam = getTransformCamera();
+
+    vec4 color = evalGlobalColor(occlusionTex,
+        fragPosition,
+        fragNormal,
+        albedo,
+        metallic,
+        fresnel,
+        roughness,
+        opacity);
+
+    color.rgb += emissive;
+
+    // Apply standard tone mapping
+    _fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), color.w);
+
+    /*_fragColor = vec4(evalGlobalLightingAlphaBlended(
+        cam._viewInverse,
+        1.0,
+        occlusionTex,
+        fragPosition,
+        fragNormal,
+        albedo,
+        fresnel,
+        metallic,
+        emissive,
+        roughness, opacity),
+        opacity);
+
+    // Apply standard tone mapping
+    _fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), color.w);*/
+}
\ No newline at end of file
diff --git a/libraries/render-utils/src/overlay3D_translucent.slf b/libraries/render-utils/src/overlay3D_translucent.slf
index 9bdac2d21f..0343567127 100644
--- a/libraries/render-utils/src/overlay3D_translucent.slf
+++ b/libraries/render-utils/src/overlay3D_translucent.slf
@@ -82,6 +82,8 @@ void main(void) {
         fragRoughness,
         fragOpacity);
 
+    color.xyz += vec3(1.0, 0.0, 0.0) * fragOpacity;
+
     // Apply standard tone mapping
     _fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), color.w);
 }

From 397eb89c143d2e746ca6e5ecd3980c210ea95e04 Mon Sep 17 00:00:00 2001
From: David Kelly <david@highfidelity.io>
Date: Wed, 22 Feb 2017 08:38:49 -0800
Subject: [PATCH 08/64] compiler warning - odd

---
 assignment-client/src/Agent.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp
index cd8c8189b2..2f91571d8b 100644
--- a/assignment-client/src/Agent.cpp
+++ b/assignment-client/src/Agent.cpp
@@ -568,7 +568,7 @@ void Agent::computeLoudness(QByteArray* decodedBuffer) {
     auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
     if (decodedBuffer) {
         auto soundData = reinterpret_cast<const int16_t*>(decodedBuffer->constData());
-        auto numFrames = decodedBuffer->size() / sizeof(int16_t);
+        int numFrames = decodedBuffer->size() / sizeof(int16_t);
         // now iterate and come up with average
         if (numFrames > 0) {
             for(int i = 0; i < numFrames; i++) {

From c7b164d8f26d1bb45666f1c1ad96c354b648b4ae Mon Sep 17 00:00:00 2001
From: samcake <samuel.gateau@gmail.com>
Date: Wed, 22 Feb 2017 18:21:18 -0800
Subject: [PATCH 09/64] Adding differenciation for the Material shapeKey bit

---
 .../render-utils/src/MeshPartPayload.cpp      |  4 ++
 .../render-utils/src/RenderPipelines.cpp      | 66 ++++++++++---------
 libraries/render/src/render/ShapePipeline.h   |  9 ++-
 3 files changed, 46 insertions(+), 33 deletions(-)

diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp
index 4cb4e2a316..5b3d285b47 100644
--- a/libraries/render-utils/src/MeshPartPayload.cpp
+++ b/libraries/render-utils/src/MeshPartPayload.cpp
@@ -97,6 +97,8 @@ ShapeKey MeshPartPayload::getShapeKey() const {
     }
 
     ShapeKey::Builder builder;
+    builder.withMaterial();
+
     if (drawMaterialKey.isTranslucent()) {
         builder.withTranslucent();
     }
@@ -478,6 +480,8 @@ ShapeKey ModelMeshPartPayload::getShapeKey() const {
     }
 
     ShapeKey::Builder builder;
+    builder.withMaterial();
+
     if (isTranslucent || _fadeState != FADE_COMPLETE) {
         builder.withTranslucent();
     }
diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp
index 53fc9153e8..19284814fc 100644
--- a/libraries/render-utils/src/RenderPipelines.cpp
+++ b/libraries/render-utils/src/RenderPipelines.cpp
@@ -106,6 +106,7 @@ void initOverlay3DPipelines(ShapePlumber& plumber) {
         }
 
         ShapeKey::Filter::Builder builder;
+
         isCulled ? builder.withCullFace() : builder.withoutCullFace();
         isBiased ? builder.withDepthBias() : builder.withoutDepthBias();
         isOpaque ? builder.withOpaque() : builder.withTranslucent();
@@ -113,6 +114,7 @@ void initOverlay3DPipelines(ShapePlumber& plumber) {
         auto simpleProgram = isOpaque ? opaqueProgram : translucentProgram;
         auto unlitProgram = isOpaque ? unlitOpaqueProgram : unlitTranslucentProgram;
         plumber.addPipeline(builder.withoutUnlit().build(), simpleProgram, state, &lightBatchSetter);
+        plumber.addPipeline(builder.withMaterial().build(), opaqueMaterialProgram, state, &lightBatchSetter);
         plumber.addPipeline(builder.withUnlit().build(), unlitProgram, state, &batchSetter);
     }
 }
@@ -147,78 +149,78 @@ void initDeferredPipelines(render::ShapePlumber& plumber) {
     // TODO: Refactor this to use a filter
     // Opaques
     addPipeline(
-        Key::Builder(),
+        Key::Builder().withMaterial(),
         modelVertex, modelPixel);
     addPipeline(
-        Key::Builder().withUnlit(),
+        Key::Builder().withMaterial().withUnlit(),
         modelVertex, modelUnlitPixel);
     addPipeline(
-        Key::Builder().withTangents(),
+        Key::Builder().withMaterial().withTangents(),
         modelNormalMapVertex, modelNormalMapPixel);
     addPipeline(
-        Key::Builder().withSpecular(),
+        Key::Builder().withMaterial().withSpecular(),
         modelVertex, modelSpecularMapPixel);
     addPipeline(
-        Key::Builder().withTangents().withSpecular(),
+        Key::Builder().withMaterial().withTangents().withSpecular(),
         modelNormalMapVertex, modelNormalSpecularMapPixel);
     // Translucents
     addPipeline(
-        Key::Builder().withTranslucent(),
+        Key::Builder().withMaterial().withTranslucent(),
         modelVertex, modelTranslucentPixel);
     addPipeline(
-        Key::Builder().withTranslucent().withUnlit(),
+        Key::Builder().withMaterial().withTranslucent().withUnlit(),
         modelVertex, modelTranslucentUnlitPixel);
     addPipeline(
-        Key::Builder().withTranslucent().withTangents(),
+        Key::Builder().withMaterial().withTranslucent().withTangents(),
         modelNormalMapVertex, modelTranslucentPixel);
     addPipeline(
-        Key::Builder().withTranslucent().withSpecular(),
+        Key::Builder().withMaterial().withTranslucent().withSpecular(),
         modelVertex, modelTranslucentPixel);
     addPipeline(
-        Key::Builder().withTranslucent().withTangents().withSpecular(),
+        Key::Builder().withMaterial().withTranslucent().withTangents().withSpecular(),
         modelNormalMapVertex, modelTranslucentPixel);
     addPipeline(
         // FIXME: Ignore lightmap for translucents meshpart
-        Key::Builder().withTranslucent().withLightmap(),
+        Key::Builder().withMaterial().withTranslucent().withLightmap(),
         modelVertex, modelTranslucentPixel);
     // Lightmapped
     addPipeline(
-        Key::Builder().withLightmap(),
+        Key::Builder().withMaterial().withLightmap(),
         modelLightmapVertex, modelLightmapPixel);
     addPipeline(
-        Key::Builder().withLightmap().withTangents(),
+        Key::Builder().withMaterial().withLightmap().withTangents(),
         modelLightmapNormalMapVertex, modelLightmapNormalMapPixel);
     addPipeline(
-        Key::Builder().withLightmap().withSpecular(),
+        Key::Builder().withMaterial().withLightmap().withSpecular(),
         modelLightmapVertex, modelLightmapSpecularMapPixel);
     addPipeline(
-        Key::Builder().withLightmap().withTangents().withSpecular(),
+        Key::Builder().withMaterial().withLightmap().withTangents().withSpecular(),
         modelLightmapNormalMapVertex, modelLightmapNormalSpecularMapPixel);
     // Skinned
     addPipeline(
-        Key::Builder().withSkinned(),
+        Key::Builder().withMaterial().withSkinned(),
         skinModelVertex, modelPixel);
     addPipeline(
-        Key::Builder().withSkinned().withTangents(),
+        Key::Builder().withMaterial().withSkinned().withTangents(),
         skinModelNormalMapVertex, modelNormalMapPixel);
     addPipeline(
-        Key::Builder().withSkinned().withSpecular(),
+        Key::Builder().withMaterial().withSkinned().withSpecular(),
         skinModelVertex, modelSpecularMapPixel);
     addPipeline(
-        Key::Builder().withSkinned().withTangents().withSpecular(),
+        Key::Builder().withMaterial().withSkinned().withTangents().withSpecular(),
         skinModelNormalMapVertex, modelNormalSpecularMapPixel);
     // Skinned and Translucent
     addPipeline(
-        Key::Builder().withSkinned().withTranslucent(),
+        Key::Builder().withMaterial().withSkinned().withTranslucent(),
         skinModelVertex, modelTranslucentPixel);
     addPipeline(
-        Key::Builder().withSkinned().withTranslucent().withTangents(),
+        Key::Builder().withMaterial().withSkinned().withTranslucent().withTangents(),
         skinModelNormalMapVertex, modelTranslucentPixel);
     addPipeline(
-        Key::Builder().withSkinned().withTranslucent().withSpecular(),
+        Key::Builder().withMaterial().withSkinned().withTranslucent().withSpecular(),
         skinModelVertex, modelTranslucentPixel);
     addPipeline(
-        Key::Builder().withSkinned().withTranslucent().withTangents().withSpecular(),
+        Key::Builder().withMaterial().withSkinned().withTranslucent().withTangents().withSpecular(),
         skinModelNormalMapVertex, modelTranslucentPixel);
     // Depth-only
     addPipeline(
@@ -247,32 +249,32 @@ void initForwardPipelines(render::ShapePlumber& plumber) {
     auto addPipeline = std::bind(&addPlumberPipeline, std::ref(plumber), _1, _2, _3);
     // Opaques
     addPipeline(
-        Key::Builder(),
+        Key::Builder().withMaterial(),
         modelVertex, modelPixel);
     addPipeline(
-        Key::Builder().withUnlit(),
+        Key::Builder().withMaterial().withUnlit(),
         modelVertex, modelUnlitPixel);
     addPipeline(
-        Key::Builder().withTangents(),
+        Key::Builder().withMaterial().withTangents(),
         modelNormalMapVertex, modelNormalMapPixel);
     addPipeline(
-        Key::Builder().withSpecular(),
+        Key::Builder().withMaterial().withSpecular(),
         modelVertex, modelSpecularMapPixel);
     addPipeline(
-        Key::Builder().withTangents().withSpecular(),
+        Key::Builder().withMaterial().withTangents().withSpecular(),
         modelNormalMapVertex, modelNormalSpecularMapPixel);
     // Skinned
     addPipeline(
-        Key::Builder().withSkinned(),
+        Key::Builder().withMaterial().withSkinned(),
         skinModelVertex, modelPixel);
     addPipeline(
-        Key::Builder().withSkinned().withTangents(),
+        Key::Builder().withMaterial().withSkinned().withTangents(),
         skinModelNormalMapVertex, modelNormalMapPixel);
     addPipeline(
-        Key::Builder().withSkinned().withSpecular(),
+        Key::Builder().withMaterial().withSkinned().withSpecular(),
         skinModelVertex, modelSpecularMapPixel);
     addPipeline(
-        Key::Builder().withSkinned().withTangents().withSpecular(),
+        Key::Builder().withMaterial().withSkinned().withTangents().withSpecular(),
         skinModelNormalMapVertex, modelNormalSpecularMapPixel);
 }
 
diff --git a/libraries/render/src/render/ShapePipeline.h b/libraries/render/src/render/ShapePipeline.h
index e7a14d2f2b..e55c1d4bc4 100644
--- a/libraries/render/src/render/ShapePipeline.h
+++ b/libraries/render/src/render/ShapePipeline.h
@@ -22,7 +22,8 @@ namespace render {
 class ShapeKey {
 public:
     enum FlagBit {
-        TRANSLUCENT = 0,
+        MATERIAL = 0,
+        TRANSLUCENT,
         LIGHTMAP,
         TANGENTS,
         SPECULAR,
@@ -53,6 +54,7 @@ public:
 
         ShapeKey build() const { return ShapeKey{_flags}; }
 
+        Builder& withMaterial() { _flags.set(MATERIAL); return (*this); }
         Builder& withTranslucent() { _flags.set(TRANSLUCENT); return (*this); }
         Builder& withLightmap() { _flags.set(LIGHTMAP); return (*this); }
         Builder& withTangents() { _flags.set(TANGENTS); return (*this); }
@@ -89,6 +91,9 @@ public:
 
             Filter build() const { return Filter(_flags, _mask); }
 
+            Builder& withMaterial() { _flags.set(MATERIAL); _mask.set(MATERIAL); return (*this); }
+            Builder& withoutMaterial() { _flags.reset(MATERIAL); _mask.set(MATERIAL); return (*this); }
+
             Builder& withTranslucent() { _flags.set(TRANSLUCENT); _mask.set(TRANSLUCENT); return (*this); }
             Builder& withOpaque() { _flags.reset(TRANSLUCENT); _mask.set(TRANSLUCENT); return (*this); }
 
@@ -134,6 +139,7 @@ public:
         Flags _mask{0};
     };
 
+    bool useMaterial() const { return _flags[MATERIAL]; }
     bool hasLightmap() const { return _flags[LIGHTMAP]; }
     bool hasTangents() const { return _flags[TANGENTS]; }
     bool hasSpecular() const { return _flags[SPECULAR]; }
@@ -170,6 +176,7 @@ inline QDebug operator<<(QDebug debug, const ShapeKey& key) {
             debug << "[ShapeKey: OWN_PIPELINE]";
         } else {
             debug << "[ShapeKey:"
+                << "useMaterial:" << key.useMaterial()
                 << "hasLightmap:" << key.hasLightmap()
                 << "hasTangents:" << key.hasTangents()
                 << "hasSpecular:" << key.hasSpecular()

From 6d59144a4f6be81935376fb8893a1d1c04ea6363 Mon Sep 17 00:00:00 2001
From: samcake <samuel.gateau@gmail.com>
Date: Thu, 23 Feb 2017 17:42:02 -0800
Subject: [PATCH 10/64] Trying to fix the emissive for overlay in front ?

---
 interface/src/ui/overlays/OverlaysPayload.cpp  | 6 +++++-
 libraries/render-utils/src/RenderPipelines.cpp | 9 +++++----
 libraries/render-utils/src/overlay3D_model.slf | 5 +++--
 3 files changed, 13 insertions(+), 7 deletions(-)

diff --git a/interface/src/ui/overlays/OverlaysPayload.cpp b/interface/src/ui/overlays/OverlaysPayload.cpp
index 277a86e93f..4aa3edbe6c 100644
--- a/interface/src/ui/overlays/OverlaysPayload.cpp
+++ b/interface/src/ui/overlays/OverlaysPayload.cpp
@@ -62,7 +62,11 @@ namespace render {
         if (overlay->is3D()) {
             auto overlay3D = std::dynamic_pointer_cast<Base3DOverlay>(overlay);
             if (overlay3D->isAA())
-                return (overlay3D->getDrawInFront() ? LAYER_3D_FRONT : LAYER_3D);
+                if (overlay3D->getDrawInFront()) {
+                    return LAYER_3D_FRONT;
+                } else {
+                    return LAYER_3D;
+                }
             else
                 return LAYER_NO_AA;
         } else {
diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp
index 19284814fc..cbbd1cd193 100644
--- a/libraries/render-utils/src/RenderPipelines.cpp
+++ b/libraries/render-utils/src/RenderPipelines.cpp
@@ -71,6 +71,7 @@ void lightBatchSetter(const ShapePipeline& pipeline, gpu::Batch& batch);
 
 void initOverlay3DPipelines(ShapePlumber& plumber) {
     auto vertex = gpu::Shader::createVertex(std::string(overlay3D_vert));
+    auto vertexModel = gpu::Shader::createVertex(std::string(model_vert));
     auto pixel = gpu::Shader::createPixel(std::string(overlay3D_model_frag));
     auto pixelTranslucent = gpu::Shader::createPixel(std::string(overlay3D_translucent_frag));
     auto pixelUnlit = gpu::Shader::createPixel(std::string(overlay3D_unlit_frag));
@@ -81,7 +82,7 @@ void initOverlay3DPipelines(ShapePlumber& plumber) {
     auto translucentProgram = gpu::Shader::createProgram(vertex, pixelTranslucent);
     auto unlitOpaqueProgram = gpu::Shader::createProgram(vertex, pixelUnlit);
     auto unlitTranslucentProgram = gpu::Shader::createProgram(vertex, pixelTranslucentUnlit);
-    auto opaqueMaterialProgram = gpu::Shader::createProgram(vertex, pixelMaterial);
+    auto opaqueMaterialProgram = gpu::Shader::createProgram(vertexModel, pixelMaterial);
 
     for (int i = 0; i < 16; i++) {
         bool isCulled = (i & 1);
@@ -113,9 +114,9 @@ void initOverlay3DPipelines(ShapePlumber& plumber) {
 
         auto simpleProgram = isOpaque ? opaqueProgram : translucentProgram;
         auto unlitProgram = isOpaque ? unlitOpaqueProgram : unlitTranslucentProgram;
-        plumber.addPipeline(builder.withoutUnlit().build(), simpleProgram, state, &lightBatchSetter);
-        plumber.addPipeline(builder.withMaterial().build(), opaqueMaterialProgram, state, &lightBatchSetter);
-        plumber.addPipeline(builder.withUnlit().build(), unlitProgram, state, &batchSetter);
+        plumber.addPipeline(builder.withoutUnlit().withoutMaterial().build(), simpleProgram, state, &lightBatchSetter);
+        plumber.addPipeline(builder.withMaterial().withoutUnlit().build(), opaqueMaterialProgram, state, &lightBatchSetter);
+        plumber.addPipeline(builder.withUnlit().withoutMaterial().build(), unlitProgram, state, &batchSetter);
     }
 }
 
diff --git a/libraries/render-utils/src/overlay3D_model.slf b/libraries/render-utils/src/overlay3D_model.slf
index dcc5eac58c..cd4179b112 100644
--- a/libraries/render-utils/src/overlay3D_model.slf
+++ b/libraries/render-utils/src/overlay3D_model.slf
@@ -112,9 +112,10 @@ void main(void) {
         opacity);
 
     color.rgb += emissive;
-
+    color.rgb = vec3(0.5, 0.5, 1.0);
     // Apply standard tone mapping
-    _fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), color.w);
+    _fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), 0.9);
+    //_fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), color.w);
 
     /*_fragColor = vec4(evalGlobalLightingAlphaBlended(
         cam._viewInverse,

From d9a716bf3de4bc95ddd0d98f5ecd0e5a3487cc10 Mon Sep 17 00:00:00 2001
From: David Rowe <david@ctrlaltstudio.com>
Date: Fri, 24 Feb 2017 18:42:52 +1300
Subject: [PATCH 11/64] If someone else is grabbing entity you want to grab
 show their grab beam

---
 .../system/controllers/handControllerGrab.js  | 62 ++++++++++++++++---
 1 file changed, 54 insertions(+), 8 deletions(-)

diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js
index ea76490b7b..5064705f78 100644
--- a/scripts/system/controllers/handControllerGrab.js
+++ b/scripts/system/controllers/handControllerGrab.js
@@ -419,10 +419,10 @@ function entityIsGrabbedByOther(entityID) {
         }
         if (tag.slice(0, 5) == "grab-") {
             // we see a grab-*uuid* shaped tag and it's not ours, so someone else is grabbing it.
-            return true;
+            return tag.slice(5, 42);
         }
     }
-    return false;
+    return null;
 }
 
 function propsArePhysical(props) {
@@ -790,10 +790,10 @@ function MyController(hand) {
 
     // for visualizations
     this.overlayLine = null;
-
-    // for lights
-    this.overlayLine = null;
     this.searchSphere = null;
+    this.otherGrabbingLine = null;
+
+    this.otherGrabbingUUID = null;
 
     this.waitForTriggerRelease = false;
 
@@ -1050,6 +1050,29 @@ function MyController(hand) {
         }
     };
 
+    this.otherGrabbingLineOn = function(avatarPosition, entityPosition, color) {
+        if (this.otherGrabbingLine === null) {
+            var lineProperties = {
+                lineWidth: 5,
+                start: avatarPosition,
+                end: entityPosition,
+                color: color,
+                glow: 1.0,
+                ignoreRayIntersection: true,
+                drawInFront: true,
+                visible: true,
+                alpha: 1
+            };
+            this.otherGrabbingLine = Overlays.addOverlay("line3d", lineProperties);
+        } else {
+            Overlays.editOverlay(this.otherGrabbingLine, {
+                start: avatarPosition,
+                end: entityPosition,
+                color: color
+            });
+        }
+    };
+
     this.evalLightWorldTransform = function(modelPos, modelRot) {
 
         var MODEL_LIGHT_POSITION = {
@@ -1093,14 +1116,20 @@ function MyController(hand) {
         }
     };
 
-    this.turnOffVisualizations = function() {
+    this.otherGrabbingLineOff = function() {
+        if (this.otherGrabbingLine !== null) {
+            Overlays.deleteOverlay(this.otherGrabbingLine);
+        }
+        this.otherGrabbingLine = null;
+    };
 
+    this.turnOffVisualizations = function() {
         this.overlayLineOff();
         this.grabPointSphereOff();
         this.lineOff();
         this.searchSphereOff();
+        this.otherGrabbingLineOff();
         restore2DMode();
-
     };
 
     this.triggerPress = function(value) {
@@ -1504,7 +1533,8 @@ function MyController(hand) {
             return false;
         }
 
-        if (entityIsGrabbedByOther(entityID)) {
+        this.otherGrabbingUUID = entityIsGrabbedByOther(entityID);
+        if (this.otherGrabbingUUID !== null) {
             // don't distance grab something that is already grabbed.
             if (debug) {
                 print("distance grab is skipping '" + props.name + "': already grabbed by another.");
@@ -1683,6 +1713,7 @@ function MyController(hand) {
                 } else {
                     // potentialFarTriggerEntity = entity;
                 }
+                this.otherGrabbingLineOff();
             } else if (this.entityIsDistanceGrabbable(rayPickInfo.entityID, handPosition)) {
                 if (this.triggerSmoothedGrab() && !isEditing() && farGrabEnabled && farSearching) {
                     this.grabbedEntity = entity;
@@ -1692,7 +1723,21 @@ function MyController(hand) {
                 } else {
                     // potentialFarGrabEntity = entity;
                 }
+                this.otherGrabbingLineOff();
+            } else if (this.otherGrabbingUUID !== null) {
+                if (this.triggerSmoothedGrab() && !isEditing() && farGrabEnabled && farSearching) {
+                    var avatar = AvatarList.getAvatar(this.otherGrabbingUUID);
+                    var IN_FRONT_OF_AVATAR = { x: 0, y: 0, z: 0.2 };
+                    var startPosition = Vec3.sum(avatar.position, Vec3.multiplyQbyV(avatar.rotation, IN_FRONT_OF_AVATAR));
+                    this.otherGrabbingLineOn(startPosition, rayPickInfo.properties.position, COLORS_GRAB_DISTANCE_HOLD);
+                } else {
+                    this.otherGrabbingLineOff();
+                }
+            } else {
+                this.otherGrabbingLineOff();
             }
+        } else {
+            this.otherGrabbingLineOff();
         }
 
         this.updateEquipHaptics(potentialEquipHotspot, handPosition);
@@ -2268,6 +2313,7 @@ function MyController(hand) {
         this.lineOff();
         this.overlayLineOff();
         this.searchSphereOff();
+        this.otherGrabbingLineOff();
 
         this.dropGestureReset();
         this.clearEquipHaptics();

From 67031850aab6e723a230b8d06507c90b6a9790f1 Mon Sep 17 00:00:00 2001
From: sam <sam>
Date: Fri, 24 Feb 2017 00:05:54 -0800
Subject: [PATCH 12/64] Deep dive into the shape key and filters and the
 PLumber construction

---
 libraries/render-utils/src/RenderPipelines.cpp | 4 ++--
 libraries/render/src/render/ShapePipeline.cpp  | 4 ++++
 libraries/render/src/render/ShapePipeline.h    | 7 -------
 3 files changed, 6 insertions(+), 9 deletions(-)

diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp
index cbbd1cd193..59d8baacf8 100644
--- a/libraries/render-utils/src/RenderPipelines.cpp
+++ b/libraries/render-utils/src/RenderPipelines.cpp
@@ -84,7 +84,7 @@ void initOverlay3DPipelines(ShapePlumber& plumber) {
     auto unlitTranslucentProgram = gpu::Shader::createProgram(vertex, pixelTranslucentUnlit);
     auto opaqueMaterialProgram = gpu::Shader::createProgram(vertexModel, pixelMaterial);
 
-    for (int i = 0; i < 16; i++) {
+    for (int i = 0; i < 8; i++) {
         bool isCulled = (i & 1);
         bool isBiased = (i & 2);
         bool isOpaque = (i & 4);
@@ -115,7 +115,7 @@ void initOverlay3DPipelines(ShapePlumber& plumber) {
         auto simpleProgram = isOpaque ? opaqueProgram : translucentProgram;
         auto unlitProgram = isOpaque ? unlitOpaqueProgram : unlitTranslucentProgram;
         plumber.addPipeline(builder.withoutUnlit().withoutMaterial().build(), simpleProgram, state, &lightBatchSetter);
-        plumber.addPipeline(builder.withMaterial().withoutUnlit().build(), opaqueMaterialProgram, state, &lightBatchSetter);
+        plumber.addPipeline(builder.withoutUnlit().withMaterial().build(), opaqueMaterialProgram, state, &lightBatchSetter);
         plumber.addPipeline(builder.withUnlit().withoutMaterial().build(), unlitProgram, state, &batchSetter);
     }
 }
diff --git a/libraries/render/src/render/ShapePipeline.cpp b/libraries/render/src/render/ShapePipeline.cpp
index 48e8ee43d5..cec1971165 100644
--- a/libraries/render/src/render/ShapePipeline.cpp
+++ b/libraries/render/src/render/ShapePipeline.cpp
@@ -39,6 +39,10 @@ void ShapePlumber::addPipelineHelper(const Filter& filter, ShapeKey key, int bit
         }
     } else {
         // Add the brand new pipeline and cache its location in the lib
+        auto precedent = _pipelineMap.find(key);
+        if (precedent != _pipelineMap.end()) {
+            qCDebug(renderlogging) << "Key already assigned: " << key;
+        }
         _pipelineMap.insert(PipelineMap::value_type(key, pipeline));
     }
 }
diff --git a/libraries/render/src/render/ShapePipeline.h b/libraries/render/src/render/ShapePipeline.h
index e55c1d4bc4..c7e494ee4c 100644
--- a/libraries/render/src/render/ShapePipeline.h
+++ b/libraries/render/src/render/ShapePipeline.h
@@ -29,7 +29,6 @@ public:
         SPECULAR,
         UNLIT,
         SKINNED,
-        STEREO,
         DEPTH_ONLY,
         DEPTH_BIAS,
         WIREFRAME,
@@ -61,7 +60,6 @@ public:
         Builder& withSpecular() { _flags.set(SPECULAR); return (*this); }
         Builder& withUnlit() { _flags.set(UNLIT); return (*this); }
         Builder& withSkinned() { _flags.set(SKINNED); return (*this); }
-        Builder& withStereo() { _flags.set(STEREO); return (*this); }
         Builder& withDepthOnly() { _flags.set(DEPTH_ONLY); return (*this); }
         Builder& withDepthBias() { _flags.set(DEPTH_BIAS); return (*this); }
         Builder& withWireframe() { _flags.set(WIREFRAME); return (*this); }
@@ -112,9 +110,6 @@ public:
             Builder& withSkinned() { _flags.set(SKINNED); _mask.set(SKINNED); return (*this); }
             Builder& withoutSkinned() { _flags.reset(SKINNED); _mask.set(SKINNED); return (*this); }
 
-            Builder& withStereo() { _flags.set(STEREO); _mask.set(STEREO); return (*this); }
-            Builder& withoutStereo() { _flags.reset(STEREO); _mask.set(STEREO); return (*this); }
-
             Builder& withDepthOnly() { _flags.set(DEPTH_ONLY); _mask.set(DEPTH_ONLY); return (*this); }
             Builder& withoutDepthOnly() { _flags.reset(DEPTH_ONLY); _mask.set(DEPTH_ONLY); return (*this); }
 
@@ -146,7 +141,6 @@ public:
     bool isUnlit() const { return _flags[UNLIT]; }
     bool isTranslucent() const { return _flags[TRANSLUCENT]; }
     bool isSkinned() const { return _flags[SKINNED]; }
-    bool isStereo() const { return _flags[STEREO]; }
     bool isDepthOnly() const { return _flags[DEPTH_ONLY]; }
     bool isDepthBiased() const { return _flags[DEPTH_BIAS]; }
     bool isWireFrame() const { return _flags[WIREFRAME]; }
@@ -183,7 +177,6 @@ inline QDebug operator<<(QDebug debug, const ShapeKey& key) {
                 << "isUnlit:" << key.isUnlit()
                 << "isTranslucent:" << key.isTranslucent()
                 << "isSkinned:" << key.isSkinned()
-                << "isStereo:" << key.isStereo()
                 << "isDepthOnly:" << key.isDepthOnly()
                 << "isDepthBiased:" << key.isDepthBiased()
                 << "isWireFrame:" << key.isWireFrame()

From e1aad8bd63950baa11b5e520924d07a633f1af49 Mon Sep 17 00:00:00 2001
From: sam <sam>
Date: Fri, 24 Feb 2017 00:35:10 -0800
Subject: [PATCH 13/64] Deep dive into the shape key and filters and the
 PLumber construction

---
 .../src/model-networking/TextureCache.cpp     |  8 -------
 .../src/model-networking/TextureCache.h       |  4 ----
 libraries/render-utils/src/DeferredBuffer.slh | 21 -------------------
 libraries/render-utils/src/GeometryCache.cpp  | 15 -------------
 .../render-utils/src/RenderPipelines.cpp      |  7 ++-----
 libraries/render/src/render/ShapePipeline.cpp |  5 -----
 libraries/render/src/render/ShapePipeline.h   |  3 ---
 7 files changed, 2 insertions(+), 61 deletions(-)

diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index f371207981..8a4e85cfe6 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -154,14 +154,6 @@ const gpu::TexturePointer& TextureCache::getBlackTexture() {
     return _blackTexture;
 }
 
-
-const gpu::TexturePointer& TextureCache::getNormalFittingTexture() {
-    if (!_normalFittingTexture) {
-        _normalFittingTexture = getImageTexture(PathUtils::resourcesPath() + "images/normalFittingScale.dds");
-    }
-    return _normalFittingTexture;
-}
-
 /// Extra data for creating textures.
 class TextureExtra {
 public:
diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h
index cb509490c6..77311afae6 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.h
+++ b/libraries/model-networking/src/model-networking/TextureCache.h
@@ -124,9 +124,6 @@ public:
     /// Returns the a black texture (useful for a default).
     const gpu::TexturePointer& getBlackTexture();
 
-    // Returns a map used to compress the normals through a fitting scale algorithm
-    const gpu::TexturePointer& getNormalFittingTexture();
-
     /// Returns a texture version of an image file
     static gpu::TexturePointer getImageTexture(const QString& path, Type type = Type::DEFAULT_TEXTURE, QVariantMap options = QVariantMap());
 
@@ -151,7 +148,6 @@ private:
     gpu::TexturePointer _grayTexture;
     gpu::TexturePointer _blueTexture;
     gpu::TexturePointer _blackTexture;
-    gpu::TexturePointer _normalFittingTexture;
 };
 
 #endif // hifi_TextureCache_h
diff --git a/libraries/render-utils/src/DeferredBuffer.slh b/libraries/render-utils/src/DeferredBuffer.slh
index a4b69bd70e..a13c2ec5d1 100644
--- a/libraries/render-utils/src/DeferredBuffer.slh
+++ b/libraries/render-utils/src/DeferredBuffer.slh
@@ -65,25 +65,4 @@ float packUnlit() {
     return FRAG_PACK_UNLIT;
 }
 
-<!
-uniform sampler2D normalFittingMap;
-
-vec3 bestFitNormal(vec3 normal) {
-    vec3 absNorm = abs(normal);
-    float maxNAbs = max(absNorm.z, max(absNorm.x, absNorm.y));
-
-    vec2 texcoord = (absNorm.z < maxNAbs ? 
-                        (absNorm.y < maxNAbs ? absNorm.yz : absNorm.xz) :
-                        absNorm.xy);
-    texcoord = (texcoord.x < texcoord.y ? texcoord.yx : texcoord.xy);
-    texcoord.y /= texcoord.x;
-    vec3 cN = normal / maxNAbs;
-
-    float fittingScale = texture(normalFittingMap, texcoord).a;
-    cN *= fittingScale;
-
-    return (cN * 0.5 + 0.5);
-}
-!>
-
 <@endif@>
diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp
index c277b9be64..e0dee7b953 100644
--- a/libraries/render-utils/src/GeometryCache.cpp
+++ b/libraries/render-utils/src/GeometryCache.cpp
@@ -414,8 +414,6 @@ _nextID(0) {
                 // Set the defaults needed for a simple program
                 batch.setResourceTexture(render::ShapePipeline::Slot::MAP::ALBEDO,
                     DependencyManager::get<TextureCache>()->getWhiteTexture());
-                batch.setResourceTexture(render::ShapePipeline::Slot::MAP::NORMAL_FITTING,
-                    DependencyManager::get<TextureCache>()->getNormalFittingTexture());
             }
         );
     GeometryCache::_simpleTransparentPipeline =
@@ -424,8 +422,6 @@ _nextID(0) {
                 // Set the defaults needed for a simple program
                 batch.setResourceTexture(render::ShapePipeline::Slot::MAP::ALBEDO,
                     DependencyManager::get<TextureCache>()->getWhiteTexture());
-                batch.setResourceTexture(render::ShapePipeline::Slot::MAP::NORMAL_FITTING,
-                    DependencyManager::get<TextureCache>()->getNormalFittingTexture());
             }
         );
     GeometryCache::_simpleWirePipeline =
@@ -1770,7 +1766,6 @@ static void buildWebShader(const std::string& vertShaderText, const std::string&
     shaderPointerOut = gpu::Shader::createProgram(VS, PS);
 
     gpu::Shader::BindingSet slotBindings;
-    slotBindings.insert(gpu::Shader::Binding(std::string("normalFittingMap"), render::ShapePipeline::Slot::MAP::NORMAL_FITTING));
     gpu::Shader::makeProgram(*shaderPointerOut, slotBindings);
     auto state = std::make_shared<gpu::State>();
     state->setCullMode(gpu::State::CULL_NONE);
@@ -1784,9 +1779,6 @@ static void buildWebShader(const std::string& vertShaderText, const std::string&
 
 void GeometryCache::bindOpaqueWebBrowserProgram(gpu::Batch& batch, bool isAA) {
     batch.setPipeline(getOpaqueWebBrowserProgram(isAA));
-    // Set a default normal map
-    batch.setResourceTexture(render::ShapePipeline::Slot::MAP::NORMAL_FITTING,
-                             DependencyManager::get<TextureCache>()->getNormalFittingTexture());
 }
 
 gpu::PipelinePointer GeometryCache::getOpaqueWebBrowserProgram(bool isAA) {
@@ -1802,9 +1794,6 @@ gpu::PipelinePointer GeometryCache::getOpaqueWebBrowserProgram(bool isAA) {
 
 void GeometryCache::bindTransparentWebBrowserProgram(gpu::Batch& batch, bool isAA) {
     batch.setPipeline(getTransparentWebBrowserProgram(isAA));
-    // Set a default normal map
-    batch.setResourceTexture(render::ShapePipeline::Slot::MAP::NORMAL_FITTING,
-                             DependencyManager::get<TextureCache>()->getNormalFittingTexture());
 }
 
 gpu::PipelinePointer GeometryCache::getTransparentWebBrowserProgram(bool isAA) {
@@ -1827,9 +1816,6 @@ void GeometryCache::bindSimpleProgram(gpu::Batch& batch, bool textured, bool tra
         batch.setResourceTexture(render::ShapePipeline::Slot::MAP::ALBEDO,
             DependencyManager::get<TextureCache>()->getWhiteTexture());
     }
-    // Set a default normal map
-    batch.setResourceTexture(render::ShapePipeline::Slot::MAP::NORMAL_FITTING,
-        DependencyManager::get<TextureCache>()->getNormalFittingTexture());
 }
 
 gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool transparent, bool culled, bool unlit, bool depthBiased) {
@@ -1846,7 +1832,6 @@ gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool transp
         _unlitShader = gpu::Shader::createProgram(VS, PSUnlit);
 
         gpu::Shader::BindingSet slotBindings;
-        slotBindings.insert(gpu::Shader::Binding(std::string("normalFittingMap"), render::ShapePipeline::Slot::MAP::NORMAL_FITTING));
         gpu::Shader::makeProgram(*_simpleShader, slotBindings);
         gpu::Shader::makeProgram(*_unlitShader, slotBindings);
     });
diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp
index 59d8baacf8..158daad54c 100644
--- a/libraries/render-utils/src/RenderPipelines.cpp
+++ b/libraries/render-utils/src/RenderPipelines.cpp
@@ -114,9 +114,9 @@ void initOverlay3DPipelines(ShapePlumber& plumber) {
 
         auto simpleProgram = isOpaque ? opaqueProgram : translucentProgram;
         auto unlitProgram = isOpaque ? unlitOpaqueProgram : unlitTranslucentProgram;
-        plumber.addPipeline(builder.withoutUnlit().withoutMaterial().build(), simpleProgram, state, &lightBatchSetter);
         plumber.addPipeline(builder.withoutUnlit().withMaterial().build(), opaqueMaterialProgram, state, &lightBatchSetter);
-        plumber.addPipeline(builder.withUnlit().withoutMaterial().build(), unlitProgram, state, &batchSetter);
+        plumber.addPipeline(builder.withoutUnlit().build(), simpleProgram, state, &lightBatchSetter);
+        plumber.addPipeline(builder.withUnlit().build(), unlitProgram, state, &batchSetter);
     }
 }
 
@@ -325,9 +325,6 @@ void batchSetter(const ShapePipeline& pipeline, gpu::Batch& batch) {
     // Set a default albedo map
     batch.setResourceTexture(render::ShapePipeline::Slot::MAP::ALBEDO,
         DependencyManager::get<TextureCache>()->getWhiteTexture());
-    // Set a default normal map
-    batch.setResourceTexture(render::ShapePipeline::Slot::MAP::NORMAL_FITTING,
-        DependencyManager::get<TextureCache>()->getNormalFittingTexture());
 
     // Set a default material
     if (pipeline.locations->materialBufferUnit >= 0) {
diff --git a/libraries/render/src/render/ShapePipeline.cpp b/libraries/render/src/render/ShapePipeline.cpp
index cec1971165..1c8e73f5d7 100644
--- a/libraries/render/src/render/ShapePipeline.cpp
+++ b/libraries/render/src/render/ShapePipeline.cpp
@@ -69,16 +69,11 @@ void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& p
     slotBindings.insert(gpu::Shader::Binding(std::string("lightBuffer"), Slot::BUFFER::LIGHT));
     slotBindings.insert(gpu::Shader::Binding(std::string("lightAmbientBuffer"), Slot::BUFFER::LIGHT_AMBIENT_BUFFER));
     slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), Slot::MAP::LIGHT_AMBIENT));
-    slotBindings.insert(gpu::Shader::Binding(std::string("normalFittingMap"), Slot::NORMAL_FITTING));
 
     gpu::Shader::makeProgram(*program, slotBindings);
 
     auto locations = std::make_shared<Locations>();
-    locations->normalFittingMapUnit = program->getTextures().findLocation("normalFittingMap");
-    if (program->getTextures().findLocation("normalFittingMap") > -1) {
-        locations->normalFittingMapUnit = program->getTextures().findLocation("normalFittingMap");
 
-    }
     locations->albedoTextureUnit = program->getTextures().findLocation("albedoMap");
     locations->roughnessTextureUnit = program->getTextures().findLocation("roughnessMap");
     locations->normalTextureUnit = program->getTextures().findLocation("normalMap");
diff --git a/libraries/render/src/render/ShapePipeline.h b/libraries/render/src/render/ShapePipeline.h
index c7e494ee4c..1e0f1cad76 100644
--- a/libraries/render/src/render/ShapePipeline.h
+++ b/libraries/render/src/render/ShapePipeline.h
@@ -213,8 +213,6 @@ public:
             OCCLUSION,
             SCATTERING,
             LIGHT_AMBIENT,
-
-            NORMAL_FITTING = 10,
         };
     };
 
@@ -226,7 +224,6 @@ public:
         int metallicTextureUnit;
         int emissiveTextureUnit;
         int occlusionTextureUnit;
-        int normalFittingMapUnit;
         int lightingModelBufferUnit;
         int skinClusterBufferUnit;
         int materialBufferUnit;

From 5257194c4825406920c9aa614a7d7e9e5f2a703f Mon Sep 17 00:00:00 2001
From: sam <sam>
Date: Fri, 24 Feb 2017 01:11:59 -0800
Subject: [PATCH 14/64] SImplify the overlay PLumber

---
 libraries/render-utils/src/RenderPipelines.cpp       | 7 ++++---
 libraries/render-utils/src/overlay3D_model.slf       | 4 ++--
 libraries/render-utils/src/overlay3D_translucent.slf | 3 ++-
 libraries/render/src/render/ShapePipeline.h          | 1 +
 4 files changed, 9 insertions(+), 6 deletions(-)

diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp
index 158daad54c..508b035a5a 100644
--- a/libraries/render-utils/src/RenderPipelines.cpp
+++ b/libraries/render-utils/src/RenderPipelines.cpp
@@ -114,9 +114,10 @@ void initOverlay3DPipelines(ShapePlumber& plumber) {
 
         auto simpleProgram = isOpaque ? opaqueProgram : translucentProgram;
         auto unlitProgram = isOpaque ? unlitOpaqueProgram : unlitTranslucentProgram;
-        plumber.addPipeline(builder.withoutUnlit().withMaterial().build(), opaqueMaterialProgram, state, &lightBatchSetter);
-        plumber.addPipeline(builder.withoutUnlit().build(), simpleProgram, state, &lightBatchSetter);
-        plumber.addPipeline(builder.withUnlit().build(), unlitProgram, state, &batchSetter);
+
+        plumber.addPipeline(builder.withoutUnlit().withMaterial().build().key(), opaqueMaterialProgram, state, &lightBatchSetter);
+        plumber.addPipeline(builder.withoutUnlit().withoutMaterial().build().key(), simpleProgram, state, &lightBatchSetter);
+        plumber.addPipeline(builder.withUnlit().withoutMaterial().build().key(), unlitProgram, state, &batchSetter);
     }
 }
 
diff --git a/libraries/render-utils/src/overlay3D_model.slf b/libraries/render-utils/src/overlay3D_model.slf
index cd4179b112..979a6f2201 100644
--- a/libraries/render-utils/src/overlay3D_model.slf
+++ b/libraries/render-utils/src/overlay3D_model.slf
@@ -112,9 +112,9 @@ void main(void) {
         opacity);
 
     color.rgb += emissive;
-    color.rgb = vec3(0.5, 0.5, 1.0);
+   // color.rgb = vec3(0.5, 0.5, 1.0);
     // Apply standard tone mapping
-    _fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), 0.9);
+    _fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), 1.0);
     //_fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), color.w);
 
     /*_fragColor = vec4(evalGlobalLightingAlphaBlended(
diff --git a/libraries/render-utils/src/overlay3D_translucent.slf b/libraries/render-utils/src/overlay3D_translucent.slf
index 0343567127..9f00cdf982 100644
--- a/libraries/render-utils/src/overlay3D_translucent.slf
+++ b/libraries/render-utils/src/overlay3D_translucent.slf
@@ -82,7 +82,8 @@ void main(void) {
         fragRoughness,
         fragOpacity);
 
-    color.xyz += vec3(1.0, 0.0, 0.0) * fragOpacity;
+    color.xyz += vec3(0.0, 1.0, 0.0) * fragOpacity;
+    color.w = 1.0;
 
     // Apply standard tone mapping
     _fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), color.w);
diff --git a/libraries/render/src/render/ShapePipeline.h b/libraries/render/src/render/ShapePipeline.h
index 1e0f1cad76..0c77a15184 100644
--- a/libraries/render/src/render/ShapePipeline.h
+++ b/libraries/render/src/render/ShapePipeline.h
@@ -128,6 +128,7 @@ public:
             Flags _mask{0};
         };
         Filter(const Filter::Builder& builder) : Filter(builder._flags, builder._mask) {}
+        ShapeKey key() const { return ShapeKey(_flags); }
     protected:
         friend class ShapePlumber;
         Flags _flags{0};

From a98824f48300179684c5e3817545cbf67de1bfc6 Mon Sep 17 00:00:00 2001
From: David Rowe <david@ctrlaltstudio.com>
Date: Sat, 25 Feb 2017 08:13:15 +1300
Subject: [PATCH 15/64] Code review

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

diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js
index 5064705f78..fe0fe19ae3 100644
--- a/scripts/system/controllers/handControllerGrab.js
+++ b/scripts/system/controllers/handControllerGrab.js
@@ -417,9 +417,11 @@ function entityIsGrabbedByOther(entityID) {
             // we see a grab-*uuid* shaped tag, but it's our tag, so that's okay.
             continue;
         }
-        if (tag.slice(0, 5) == "grab-") {
+        var GRAB_PREFIX_LENGTH = 5;
+        var UUID_LENGTH = 38;
+        if (tag.slice(0, GRAB_PREFIX_LENGTH) == "grab-") {
             // we see a grab-*uuid* shaped tag and it's not ours, so someone else is grabbing it.
-            return tag.slice(5, 42);
+            return tag.slice(GRAB_PREFIX_LENGTH, GRAB_PREFIX_LENGTH + UUID_LENGTH - 1);
         }
     }
     return null;

From 1b501487fd9eb019cd2894aeb8caba9c49ab623e Mon Sep 17 00:00:00 2001
From: samcake <samuel.gateau@gmail.com>
Date: Fri, 24 Feb 2017 16:03:28 -0800
Subject: [PATCH 16/64] simple shader checks

---
 libraries/render-utils/src/overlay3D_model.slf | 6 ++----
 scripts/system/pal.js                          | 3 ++-
 2 files changed, 4 insertions(+), 5 deletions(-)

diff --git a/libraries/render-utils/src/overlay3D_model.slf b/libraries/render-utils/src/overlay3D_model.slf
index 979a6f2201..75198cdfe4 100644
--- a/libraries/render-utils/src/overlay3D_model.slf
+++ b/libraries/render-utils/src/overlay3D_model.slf
@@ -112,10 +112,8 @@ void main(void) {
         opacity);
 
     color.rgb += emissive;
-   // color.rgb = vec3(0.5, 0.5, 1.0);
-    // Apply standard tone mapping
-    _fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), 1.0);
-    //_fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), color.w);
+
+    _fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), color.w);
 
     /*_fragColor = vec4(evalGlobalLightingAlphaBlended(
         cam._viewInverse,
diff --git a/scripts/system/pal.js b/scripts/system/pal.js
index 36ecc1f084..962923eeef 100644
--- a/scripts/system/pal.js
+++ b/scripts/system/pal.js
@@ -175,7 +175,8 @@ function HighlightedEntity(id, entityProperties) {
         },
         lineWidth: 1.0,
         ignoreRayIntersection: true,
-        drawInFront: false // Arguable. For now, let's not distract with mysterious wires around the scene.
+        //drawInFront: false // Arguable. For now, let's not distract with mysterious wires around the scene.
+        drawInFront: true // Arguable. For now, let's not distract with mysterious wires around the scene.
     });
     HighlightedEntity.overlays.push(this);
 }

From 9ffdc03dc9eae75a71df822d1ce6f0cc8ef67d1c Mon Sep 17 00:00:00 2001
From: samcake <samuel.gateau@gmail.com>
Date: Fri, 24 Feb 2017 18:25:24 -0800
Subject: [PATCH 17/64] setting the shaeKeys for the overly properly because
 most of them are CustomPipleline

---
 interface/src/ui/overlays/Circle3DOverlay.cpp        | 2 +-
 interface/src/ui/overlays/Cube3DOverlay.cpp          | 2 +-
 interface/src/ui/overlays/Image3DOverlay.cpp         | 2 +-
 interface/src/ui/overlays/Shape3DOverlay.cpp         | 2 +-
 interface/src/ui/overlays/Sphere3DOverlay.cpp        | 2 +-
 interface/src/ui/overlays/Text3DOverlay.cpp          | 2 +-
 interface/src/ui/overlays/Web3DOverlay.cpp           | 2 +-
 libraries/render-utils/src/MeshPartPayload.h         | 3 ++-
 libraries/render-utils/src/overlay3D_model.slf       | 7 +++++--
 libraries/render-utils/src/overlay3D_translucent.slf | 4 ++--
 10 files changed, 16 insertions(+), 12 deletions(-)

diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp
index ae0173f054..b51f83ec87 100644
--- a/interface/src/ui/overlays/Circle3DOverlay.cpp
+++ b/interface/src/ui/overlays/Circle3DOverlay.cpp
@@ -263,7 +263,7 @@ void Circle3DOverlay::render(RenderArgs* args) {
 }
 
 const render::ShapeKey Circle3DOverlay::getShapeKey() {
-    auto builder = render::ShapeKey::Builder().withoutCullFace().withUnlit();
+    auto builder = render::ShapeKey::Builder().withoutCullFace().withUnlit().withOwnPipeline();
     if (getAlpha() != 1.0f) {
         builder.withTranslucent();
     }
diff --git a/interface/src/ui/overlays/Cube3DOverlay.cpp b/interface/src/ui/overlays/Cube3DOverlay.cpp
index 8af4c1d908..3cb3c6115d 100644
--- a/interface/src/ui/overlays/Cube3DOverlay.cpp
+++ b/interface/src/ui/overlays/Cube3DOverlay.cpp
@@ -116,7 +116,7 @@ void Cube3DOverlay::render(RenderArgs* args) {
 }
 
 const render::ShapeKey Cube3DOverlay::getShapeKey() {
-    auto builder = render::ShapeKey::Builder();
+    auto builder = render::ShapeKey::Builder().withOwnPipeline();
     if (getAlpha() != 1.0f) {
         builder.withTranslucent();
     }
diff --git a/interface/src/ui/overlays/Image3DOverlay.cpp b/interface/src/ui/overlays/Image3DOverlay.cpp
index 45d63d9cf1..2350ec36ab 100644
--- a/interface/src/ui/overlays/Image3DOverlay.cpp
+++ b/interface/src/ui/overlays/Image3DOverlay.cpp
@@ -116,7 +116,7 @@ void Image3DOverlay::render(RenderArgs* args) {
 }
 
 const render::ShapeKey Image3DOverlay::getShapeKey() {
-    auto builder = render::ShapeKey::Builder().withoutCullFace().withDepthBias();
+    auto builder = render::ShapeKey::Builder().withoutCullFace().withDepthBias().withOwnPipeline();
     if (_emissive) {
         builder.withUnlit();
     }
diff --git a/interface/src/ui/overlays/Shape3DOverlay.cpp b/interface/src/ui/overlays/Shape3DOverlay.cpp
index 2556bc84aa..d64f6db9df 100644
--- a/interface/src/ui/overlays/Shape3DOverlay.cpp
+++ b/interface/src/ui/overlays/Shape3DOverlay.cpp
@@ -61,7 +61,7 @@ void Shape3DOverlay::render(RenderArgs* args) {
 }
 
 const render::ShapeKey Shape3DOverlay::getShapeKey() {
-    auto builder = render::ShapeKey::Builder();
+    auto builder = render::ShapeKey::Builder().withOwnPipeline();
     if (getAlpha() != 1.0f) {
         builder.withTranslucent();
     }
diff --git a/interface/src/ui/overlays/Sphere3DOverlay.cpp b/interface/src/ui/overlays/Sphere3DOverlay.cpp
index 07c2861f16..0c3dd48094 100644
--- a/interface/src/ui/overlays/Sphere3DOverlay.cpp
+++ b/interface/src/ui/overlays/Sphere3DOverlay.cpp
@@ -58,7 +58,7 @@ void Sphere3DOverlay::render(RenderArgs* args) {
 }
 
 const render::ShapeKey Sphere3DOverlay::getShapeKey() {
-    auto builder = render::ShapeKey::Builder();
+    auto builder = render::ShapeKey::Builder().withOwnPipeline();
     if (getAlpha() != 1.0f) {
         builder.withTranslucent();
     }
diff --git a/interface/src/ui/overlays/Text3DOverlay.cpp b/interface/src/ui/overlays/Text3DOverlay.cpp
index 2e2d586abc..41dabe83fd 100644
--- a/interface/src/ui/overlays/Text3DOverlay.cpp
+++ b/interface/src/ui/overlays/Text3DOverlay.cpp
@@ -132,7 +132,7 @@ void Text3DOverlay::render(RenderArgs* args) {
 }
 
 const render::ShapeKey Text3DOverlay::getShapeKey() {
-    auto builder = render::ShapeKey::Builder();
+    auto builder = render::ShapeKey::Builder().withOwnPipeline();
     if (getAlpha() != 1.0f) {
         builder.withTranslucent();
     }
diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp
index bfc37ccf60..96051beb2b 100644
--- a/interface/src/ui/overlays/Web3DOverlay.cpp
+++ b/interface/src/ui/overlays/Web3DOverlay.cpp
@@ -275,7 +275,7 @@ void Web3DOverlay::render(RenderArgs* args) {
 }
 
 const render::ShapeKey Web3DOverlay::getShapeKey() {
-    auto builder = render::ShapeKey::Builder().withoutCullFace().withDepthBias();
+    auto builder = render::ShapeKey::Builder().withoutCullFace().withDepthBias().withOwnPipeline();
     if (getAlpha() != 1.0f) {
         builder.withTranslucent();
     }
diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h
index c585c95025..fa8742c40c 100644
--- a/libraries/render-utils/src/MeshPartPayload.h
+++ b/libraries/render-utils/src/MeshPartPayload.h
@@ -118,7 +118,8 @@ public:
 
 private:
     mutable quint64 _fadeStartTime { 0 };
-    mutable uint8_t _fadeState { FADE_WAITING_TO_START };
+    //mutable uint8_t _fadeState { FADE_WAITING_TO_START };
+    mutable uint8_t _fadeState { FADE_COMPLETE };
 };
 
 namespace render {
diff --git a/libraries/render-utils/src/overlay3D_model.slf b/libraries/render-utils/src/overlay3D_model.slf
index 75198cdfe4..6c21f95a91 100644
--- a/libraries/render-utils/src/overlay3D_model.slf
+++ b/libraries/render-utils/src/overlay3D_model.slf
@@ -112,8 +112,11 @@ void main(void) {
         opacity);
 
     color.rgb += emissive;
-
-    _fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), color.w);
+    color.rgb += vec3(1, 0.0, 0.0);
+    // Apply standard tone mapping
+   // _fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), 1.0);
+    _fragColor = vec4(albedo, 0.5);
+    //_fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), color.w);
 
     /*_fragColor = vec4(evalGlobalLightingAlphaBlended(
         cam._viewInverse,
diff --git a/libraries/render-utils/src/overlay3D_translucent.slf b/libraries/render-utils/src/overlay3D_translucent.slf
index 9f00cdf982..3604fd88c5 100644
--- a/libraries/render-utils/src/overlay3D_translucent.slf
+++ b/libraries/render-utils/src/overlay3D_translucent.slf
@@ -82,8 +82,8 @@ void main(void) {
         fragRoughness,
         fragOpacity);
 
-    color.xyz += vec3(0.0, 1.0, 0.0) * fragOpacity;
-    color.w = 1.0;
+  //  color.xyz += vec3(0.0, 1.0, 0.0) * fragOpacity;
+  //  color.w = 1.0;
 
     // Apply standard tone mapping
     _fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), color.w);

From a53d5e8fbddd2ea03d30528fea17e6914e0b9b83 Mon Sep 17 00:00:00 2001
From: sam <sam>
Date: Sat, 25 Feb 2017 13:00:37 -0800
Subject: [PATCH 18/64] adjusted the PLumber shape keys to coever non material
 shapes in main pass

---
 interface/src/ui/overlays/Circle3DOverlay.cpp        | 2 +-
 interface/src/ui/overlays/Cube3DOverlay.cpp          | 2 +-
 interface/src/ui/overlays/Image3DOverlay.cpp         | 2 +-
 interface/src/ui/overlays/Shape3DOverlay.cpp         | 2 +-
 interface/src/ui/overlays/Sphere3DOverlay.cpp        | 2 +-
 interface/src/ui/overlays/Text3DOverlay.cpp          | 2 +-
 interface/src/ui/overlays/Web3DOverlay.cpp           | 2 +-
 libraries/render-utils/src/MeshPartPayload.h         | 4 ++--
 libraries/render-utils/src/RenderDeferredTask.cpp    | 6 ++----
 libraries/render-utils/src/RenderPipelines.cpp       | 9 +++++++++
 libraries/render-utils/src/overlay3D_model.slf       | 6 +++---
 libraries/render-utils/src/overlay3D_translucent.slf | 3 ---
 12 files changed, 23 insertions(+), 19 deletions(-)

diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp
index b51f83ec87..ae0173f054 100644
--- a/interface/src/ui/overlays/Circle3DOverlay.cpp
+++ b/interface/src/ui/overlays/Circle3DOverlay.cpp
@@ -263,7 +263,7 @@ void Circle3DOverlay::render(RenderArgs* args) {
 }
 
 const render::ShapeKey Circle3DOverlay::getShapeKey() {
-    auto builder = render::ShapeKey::Builder().withoutCullFace().withUnlit().withOwnPipeline();
+    auto builder = render::ShapeKey::Builder().withoutCullFace().withUnlit();
     if (getAlpha() != 1.0f) {
         builder.withTranslucent();
     }
diff --git a/interface/src/ui/overlays/Cube3DOverlay.cpp b/interface/src/ui/overlays/Cube3DOverlay.cpp
index 3cb3c6115d..8af4c1d908 100644
--- a/interface/src/ui/overlays/Cube3DOverlay.cpp
+++ b/interface/src/ui/overlays/Cube3DOverlay.cpp
@@ -116,7 +116,7 @@ void Cube3DOverlay::render(RenderArgs* args) {
 }
 
 const render::ShapeKey Cube3DOverlay::getShapeKey() {
-    auto builder = render::ShapeKey::Builder().withOwnPipeline();
+    auto builder = render::ShapeKey::Builder();
     if (getAlpha() != 1.0f) {
         builder.withTranslucent();
     }
diff --git a/interface/src/ui/overlays/Image3DOverlay.cpp b/interface/src/ui/overlays/Image3DOverlay.cpp
index 2350ec36ab..45d63d9cf1 100644
--- a/interface/src/ui/overlays/Image3DOverlay.cpp
+++ b/interface/src/ui/overlays/Image3DOverlay.cpp
@@ -116,7 +116,7 @@ void Image3DOverlay::render(RenderArgs* args) {
 }
 
 const render::ShapeKey Image3DOverlay::getShapeKey() {
-    auto builder = render::ShapeKey::Builder().withoutCullFace().withDepthBias().withOwnPipeline();
+    auto builder = render::ShapeKey::Builder().withoutCullFace().withDepthBias();
     if (_emissive) {
         builder.withUnlit();
     }
diff --git a/interface/src/ui/overlays/Shape3DOverlay.cpp b/interface/src/ui/overlays/Shape3DOverlay.cpp
index d64f6db9df..2556bc84aa 100644
--- a/interface/src/ui/overlays/Shape3DOverlay.cpp
+++ b/interface/src/ui/overlays/Shape3DOverlay.cpp
@@ -61,7 +61,7 @@ void Shape3DOverlay::render(RenderArgs* args) {
 }
 
 const render::ShapeKey Shape3DOverlay::getShapeKey() {
-    auto builder = render::ShapeKey::Builder().withOwnPipeline();
+    auto builder = render::ShapeKey::Builder();
     if (getAlpha() != 1.0f) {
         builder.withTranslucent();
     }
diff --git a/interface/src/ui/overlays/Sphere3DOverlay.cpp b/interface/src/ui/overlays/Sphere3DOverlay.cpp
index 0c3dd48094..07c2861f16 100644
--- a/interface/src/ui/overlays/Sphere3DOverlay.cpp
+++ b/interface/src/ui/overlays/Sphere3DOverlay.cpp
@@ -58,7 +58,7 @@ void Sphere3DOverlay::render(RenderArgs* args) {
 }
 
 const render::ShapeKey Sphere3DOverlay::getShapeKey() {
-    auto builder = render::ShapeKey::Builder().withOwnPipeline();
+    auto builder = render::ShapeKey::Builder();
     if (getAlpha() != 1.0f) {
         builder.withTranslucent();
     }
diff --git a/interface/src/ui/overlays/Text3DOverlay.cpp b/interface/src/ui/overlays/Text3DOverlay.cpp
index 41dabe83fd..2e2d586abc 100644
--- a/interface/src/ui/overlays/Text3DOverlay.cpp
+++ b/interface/src/ui/overlays/Text3DOverlay.cpp
@@ -132,7 +132,7 @@ void Text3DOverlay::render(RenderArgs* args) {
 }
 
 const render::ShapeKey Text3DOverlay::getShapeKey() {
-    auto builder = render::ShapeKey::Builder().withOwnPipeline();
+    auto builder = render::ShapeKey::Builder();
     if (getAlpha() != 1.0f) {
         builder.withTranslucent();
     }
diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp
index 96051beb2b..bfc37ccf60 100644
--- a/interface/src/ui/overlays/Web3DOverlay.cpp
+++ b/interface/src/ui/overlays/Web3DOverlay.cpp
@@ -275,7 +275,7 @@ void Web3DOverlay::render(RenderArgs* args) {
 }
 
 const render::ShapeKey Web3DOverlay::getShapeKey() {
-    auto builder = render::ShapeKey::Builder().withoutCullFace().withDepthBias().withOwnPipeline();
+    auto builder = render::ShapeKey::Builder().withoutCullFace().withDepthBias();
     if (getAlpha() != 1.0f) {
         builder.withTranslucent();
     }
diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h
index fa8742c40c..9d021f4f34 100644
--- a/libraries/render-utils/src/MeshPartPayload.h
+++ b/libraries/render-utils/src/MeshPartPayload.h
@@ -118,8 +118,8 @@ public:
 
 private:
     mutable quint64 _fadeStartTime { 0 };
-    //mutable uint8_t _fadeState { FADE_WAITING_TO_START };
-    mutable uint8_t _fadeState { FADE_COMPLETE };
+    mutable uint8_t _fadeState { FADE_WAITING_TO_START };
+   // mutable uint8_t _fadeState { FADE_COMPLETE };
 };
 
 namespace render {
diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp
index 55a9c8b9e4..5d03cac58f 100644
--- a/libraries/render-utils/src/RenderDeferredTask.cpp
+++ b/libraries/render-utils/src/RenderDeferredTask.cpp
@@ -75,7 +75,6 @@ RenderDeferredTask::RenderDeferredTask(RenderFetchCullSortTask::Output items) {
     // GPU jobs: Start preparing the primary, deferred and lighting buffer
     const auto primaryFramebuffer = addJob<PreparePrimaryFramebuffer>("PreparePrimaryBuffer");
 
-   // const auto fullFrameRangeTimer = addJob<BeginGPURangeTimer>("BeginRangeTimer");
     const auto opaqueRangeTimer = addJob<BeginGPURangeTimer>("BeginOpaqueRangeTimer", "DrawOpaques");
 
     const auto prepareDeferredInputs = PrepareDeferred::Inputs(primaryFramebuffer, lightingModel).hasVarying();
@@ -167,6 +166,8 @@ RenderDeferredTask::RenderDeferredTask(RenderFetchCullSortTask::Output items) {
 
         // Bounds do not draw on stencil buffer, so they must come last
         addJob<DrawBounds>("DrawMetaBounds", metas);
+        addJob<DrawBounds>("DrawOverlaysOpaques", overlayOpaques);
+        addJob<DrawBounds>("DrawOverlaysTransparents", overlayTransparents);
 
         // Debugging Deferred buffer job
         const auto debugFramebuffers = render::Varying(DebugDeferredBuffer::Inputs(deferredFramebuffer, linearDepthTarget, surfaceGeometryFramebuffer, ambientOcclusionFramebuffer));
@@ -207,9 +208,6 @@ RenderDeferredTask::RenderDeferredTask(RenderFetchCullSortTask::Output items) {
 
     // Blit!
     addJob<Blit>("Blit", primaryFramebuffer);
-
- //   addJob<EndGPURangeTimer>("RangeTimer", fullFrameRangeTimer);
-
 }
 
 void BeginGPURangeTimer::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, gpu::RangeTimerPointer& timer) {
diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp
index 508b035a5a..93bc7b77df 100644
--- a/libraries/render-utils/src/RenderPipelines.cpp
+++ b/libraries/render-utils/src/RenderPipelines.cpp
@@ -156,6 +156,9 @@ void initDeferredPipelines(render::ShapePlumber& plumber) {
     addPipeline(
         Key::Builder().withMaterial().withUnlit(),
         modelVertex, modelUnlitPixel);
+    addPipeline(
+        Key::Builder().withUnlit(),
+        modelVertex, modelUnlitPixel);
     addPipeline(
         Key::Builder().withMaterial().withTangents(),
         modelNormalMapVertex, modelNormalMapPixel);
@@ -169,9 +172,15 @@ void initDeferredPipelines(render::ShapePlumber& plumber) {
     addPipeline(
         Key::Builder().withMaterial().withTranslucent(),
         modelVertex, modelTranslucentPixel);
+    addPipeline(
+        Key::Builder().withTranslucent(),
+        modelVertex, modelTranslucentPixel);
     addPipeline(
         Key::Builder().withMaterial().withTranslucent().withUnlit(),
         modelVertex, modelTranslucentUnlitPixel);
+    addPipeline(
+        Key::Builder().withTranslucent().withUnlit(),
+        modelVertex, modelTranslucentUnlitPixel);
     addPipeline(
         Key::Builder().withMaterial().withTranslucent().withTangents(),
         modelNormalMapVertex, modelTranslucentPixel);
diff --git a/libraries/render-utils/src/overlay3D_model.slf b/libraries/render-utils/src/overlay3D_model.slf
index 6c21f95a91..42c08d21bc 100644
--- a/libraries/render-utils/src/overlay3D_model.slf
+++ b/libraries/render-utils/src/overlay3D_model.slf
@@ -114,9 +114,9 @@ void main(void) {
     color.rgb += emissive;
     color.rgb += vec3(1, 0.0, 0.0);
     // Apply standard tone mapping
-   // _fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), 1.0);
-    _fragColor = vec4(albedo, 0.5);
-    //_fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), color.w);
+    _fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), 1.0);
+    //_fragColor = vec4(albedo, 0.5);
+   // _fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), color.w);
 
     /*_fragColor = vec4(evalGlobalLightingAlphaBlended(
         cam._viewInverse,
diff --git a/libraries/render-utils/src/overlay3D_translucent.slf b/libraries/render-utils/src/overlay3D_translucent.slf
index 3604fd88c5..9bdac2d21f 100644
--- a/libraries/render-utils/src/overlay3D_translucent.slf
+++ b/libraries/render-utils/src/overlay3D_translucent.slf
@@ -82,9 +82,6 @@ void main(void) {
         fragRoughness,
         fragOpacity);
 
-  //  color.xyz += vec3(0.0, 1.0, 0.0) * fragOpacity;
-  //  color.w = 1.0;
-
     // Apply standard tone mapping
     _fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), color.w);
 }

From 709c2e4240b038ecc109b4e189957569d8876a5c Mon Sep 17 00:00:00 2001
From: sam <sam>
Date: Mon, 27 Feb 2017 02:52:00 -0800
Subject: [PATCH 19/64] Fixing the emissive on overlayfor models

---
 .../render-utils/src/RenderDeferredTask.cpp   | 19 +++--
 .../render-utils/src/RenderPipelines.cpp      | 12 ++-
 .../render-utils/src/overlay3D_model.slf      | 70 +++-------------
 .../src/overlay3D_model_translucent.slf       | 83 +++++++++++++++++++
 .../utilities/render/deferredLighting.qml     | 24 +++++-
 5 files changed, 135 insertions(+), 73 deletions(-)
 create mode 100644 libraries/render-utils/src/overlay3D_model_translucent.slf

diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp
index 5d03cac58f..676d176cca 100644
--- a/libraries/render-utils/src/RenderDeferredTask.cpp
+++ b/libraries/render-utils/src/RenderDeferredTask.cpp
@@ -153,22 +153,25 @@ RenderDeferredTask::RenderDeferredTask(RenderFetchCullSortTask::Output items) {
     const auto toneMappingInputs = render::Varying(ToneMappingDeferred::Inputs(lightingFramebuffer, primaryFramebuffer));
     addJob<ToneMappingDeferred>("ToneMapping", toneMappingInputs);
 
+    { // DEbug the bounds of the rendered items, still look at the zbuffer
+        addJob<DrawBounds>("DrawMetaBounds", metas);
+        addJob<DrawBounds>("DrawOpaqueBounds", opaques);
+        addJob<DrawBounds>("DrawTransparentBounds", transparents);
+    }
+
     // Overlays
     const auto overlayOpaquesInputs = DrawOverlay3D::Inputs(overlayOpaques, lightingModel).hasVarying();
     const auto overlayTransparentsInputs = DrawOverlay3D::Inputs(overlayTransparents, lightingModel).hasVarying();
     addJob<DrawOverlay3D>("DrawOverlay3DOpaque", overlayOpaquesInputs, true);
     addJob<DrawOverlay3D>("DrawOverlay3DTransparent", overlayTransparentsInputs, false);
 
+    { // DEbug the bounds of the rendered OVERLAY items, still look at the zbuffer
+        addJob<DrawBounds>("DrawOverlayOpaqueBounds", overlayOpaques);
+        addJob<DrawBounds>("DrawOverlayTransparentBounds", overlayTransparents);
+    }
     
-    // Debugging stages
+     // Debugging stages
     {
-
-
-        // Bounds do not draw on stencil buffer, so they must come last
-        addJob<DrawBounds>("DrawMetaBounds", metas);
-        addJob<DrawBounds>("DrawOverlaysOpaques", overlayOpaques);
-        addJob<DrawBounds>("DrawOverlaysTransparents", overlayTransparents);
-
         // Debugging Deferred buffer job
         const auto debugFramebuffers = render::Varying(DebugDeferredBuffer::Inputs(deferredFramebuffer, linearDepthTarget, surfaceGeometryFramebuffer, ambientOcclusionFramebuffer));
         addJob<DebugDeferredBuffer>("DebugDeferredBuffer", debugFramebuffers);
diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp
index 93bc7b77df..d753c679c6 100644
--- a/libraries/render-utils/src/RenderPipelines.cpp
+++ b/libraries/render-utils/src/RenderPipelines.cpp
@@ -51,6 +51,7 @@
 #include "overlay3D_vert.h"
 #include "overlay3D_frag.h"
 #include "overlay3D_model_frag.h"
+#include "overlay3D_model_translucent_frag.h"
 #include "overlay3D_translucent_frag.h"
 #include "overlay3D_unlit_frag.h"
 #include "overlay3D_translucent_unlit_frag.h"
@@ -72,17 +73,19 @@ void lightBatchSetter(const ShapePipeline& pipeline, gpu::Batch& batch);
 void initOverlay3DPipelines(ShapePlumber& plumber) {
     auto vertex = gpu::Shader::createVertex(std::string(overlay3D_vert));
     auto vertexModel = gpu::Shader::createVertex(std::string(model_vert));
-    auto pixel = gpu::Shader::createPixel(std::string(overlay3D_model_frag));
+    auto pixel = gpu::Shader::createPixel(std::string(overlay3D_frag));
     auto pixelTranslucent = gpu::Shader::createPixel(std::string(overlay3D_translucent_frag));
     auto pixelUnlit = gpu::Shader::createPixel(std::string(overlay3D_unlit_frag));
     auto pixelTranslucentUnlit = gpu::Shader::createPixel(std::string(overlay3D_translucent_unlit_frag));
-    auto pixelMaterial = gpu::Shader::createPixel(std::string(overlay3D_model_frag));
+    auto pixelModel = gpu::Shader::createPixel(std::string(overlay3D_model_frag));
+    auto pixelModelTranslucent = gpu::Shader::createPixel(std::string(overlay3D_model_translucent_frag));
 
     auto opaqueProgram = gpu::Shader::createProgram(vertex, pixel);
     auto translucentProgram = gpu::Shader::createProgram(vertex, pixelTranslucent);
     auto unlitOpaqueProgram = gpu::Shader::createProgram(vertex, pixelUnlit);
     auto unlitTranslucentProgram = gpu::Shader::createProgram(vertex, pixelTranslucentUnlit);
-    auto opaqueMaterialProgram = gpu::Shader::createProgram(vertexModel, pixelMaterial);
+    auto materialOpaqueProgram = gpu::Shader::createProgram(vertexModel, pixelModel);
+    auto materialTranslucentProgram = gpu::Shader::createProgram(vertexModel, pixelModelTranslucent);
 
     for (int i = 0; i < 8; i++) {
         bool isCulled = (i & 1);
@@ -114,8 +117,9 @@ void initOverlay3DPipelines(ShapePlumber& plumber) {
 
         auto simpleProgram = isOpaque ? opaqueProgram : translucentProgram;
         auto unlitProgram = isOpaque ? unlitOpaqueProgram : unlitTranslucentProgram;
+        auto materialProgram = isOpaque ? materialOpaqueProgram : materialTranslucentProgram;
 
-        plumber.addPipeline(builder.withoutUnlit().withMaterial().build().key(), opaqueMaterialProgram, state, &lightBatchSetter);
+        plumber.addPipeline(builder.withMaterial().build().key(), materialProgram, state, &lightBatchSetter);
         plumber.addPipeline(builder.withoutUnlit().withoutMaterial().build().key(), simpleProgram, state, &lightBatchSetter);
         plumber.addPipeline(builder.withUnlit().withoutMaterial().build().key(), unlitProgram, state, &batchSetter);
     }
diff --git a/libraries/render-utils/src/overlay3D_model.slf b/libraries/render-utils/src/overlay3D_model.slf
index 42c08d21bc..bb0d84a513 100644
--- a/libraries/render-utils/src/overlay3D_model.slf
+++ b/libraries/render-utils/src/overlay3D_model.slf
@@ -11,15 +11,8 @@
 //  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
 //
 
-
-<@include model/Light.slh@>
-<$declareLightBuffer()$>
-<$declareLightAmbientBuffer()$>
-
-<@include LightingModel.slh@>
-
-<@include LightDirectional.slh@>
-<$declareLightingDirectional()$>
+<@include DeferredGlobalLight.slh@>
+<$declareEvalSkyboxGlobalColor()$>
 
 <@include model/Material.slh@>
 
@@ -29,35 +22,6 @@
 <@include MaterialTextures.slh@>
 <$declareMaterialTextures(ALBEDO, ROUGHNESS, _SCRIBE_NULL, _SCRIBE_NULL, EMISSIVE, OCCLUSION)$>
 
-vec4 evalGlobalColor(float shadowAttenuation, vec3 position, vec3 normal, vec3 albedo, float metallic, vec3 fresnel, float roughness, float opacity) {
-
-    // Need the light now
-    Light light = getLight();
-    vec3 lightDirection = getLightDirection(light);
-    vec3 lightIrradiance = getLightIrradiance(light);
-
-    LightAmbient ambient = getLightAmbient();
-
-    TransformCamera cam = getTransformCamera();
-    vec3 fragNormal;
-    <$transformEyeToWorldDir(cam, normal, fragNormal)$>
-    vec3 fragEyeVectorView = normalize(-position);
-    vec3 fragEyeDir;
-    <$transformEyeToWorldDir(cam, fragEyeVectorView, fragEyeDir)$>
-
-    vec3 color = opacity * albedo * getLightColor(light) * getLightAmbientIntensity(ambient);
-
-    // Directional
-    vec3 directionalDiffuse;
-    vec3 directionalSpecular;
-    evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, fragEyeDir, fragNormal, roughness, metallic, fresnel, albedo, shadowAttenuation);
-    color += directionalDiffuse * isDiffuseEnabled() * isDirectionalEnabled();
-    color += directionalSpecular * isSpecularEnabled() * isDirectionalEnabled();
-
-    return vec4(color, opacity);
-}
-
-
 in vec2 _texCoord0;
 in vec2 _texCoord1;
 in vec4 _position;
@@ -98,27 +62,13 @@ void main(void) {
 
 
     vec3 fragPosition = _position.xyz;
-    vec3 fragNormal = normalize(_normal);
+    //vec3 fragNormal = normalize(_normal);
 
     TransformCamera cam = getTransformCamera();
+    vec3 fragNormal;
+    <$transformEyeToWorldDir(cam, _normal, fragNormal)$>;
 
-    vec4 color = evalGlobalColor(occlusionTex,
-        fragPosition,
-        fragNormal,
-        albedo,
-        metallic,
-        fresnel,
-        roughness,
-        opacity);
-
-    color.rgb += emissive;
-    color.rgb += vec3(1, 0.0, 0.0);
-    // Apply standard tone mapping
-    _fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), 1.0);
-    //_fragColor = vec4(albedo, 0.5);
-   // _fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), color.w);
-
-    /*_fragColor = vec4(evalGlobalLightingAlphaBlended(
+    vec4 color = vec4(evalSkyboxGlobalColor(
         cam._viewInverse,
         1.0,
         occlusionTex,
@@ -127,10 +77,12 @@ void main(void) {
         albedo,
         fresnel,
         metallic,
-        emissive,
-        roughness, opacity),
+        roughness),
         opacity);
 
+    // And emissive
+    color.rgb += emissive * isEmissiveEnabled();
+
     // Apply standard tone mapping
-    _fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), color.w);*/
+    _fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), color.w);
 }
\ No newline at end of file
diff --git a/libraries/render-utils/src/overlay3D_model_translucent.slf b/libraries/render-utils/src/overlay3D_model_translucent.slf
new file mode 100644
index 0000000000..748eea329c
--- /dev/null
+++ b/libraries/render-utils/src/overlay3D_model_translucent.slf
@@ -0,0 +1,83 @@
+<@include gpu/Config.slh@>
+<$VERSION_HEADER$>
+//  Generated on <$_SCRIBE_DATE$>
+//  overlay3D_model_transparent.slf
+//
+//  Created by Sam Gateau on 2/27/2017.
+//  Copyright 2017 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 DeferredGlobalLight.slh@>
+<$declareEvalGlobalLightingAlphaBlended()$>
+
+<@include model/Material.slh@>
+
+<@include gpu/Transform.slh@>
+<$declareStandardCameraTransform()$>
+
+<@include MaterialTextures.slh@>
+<$declareMaterialTextures(ALBEDO, ROUGHNESS, _SCRIBE_NULL, _SCRIBE_NULL, EMISSIVE, OCCLUSION)$>
+
+in vec2 _texCoord0;
+in vec2 _texCoord1;
+in vec4 _position;
+in vec3 _normal;
+in vec3 _color;
+in float _alpha;
+
+out vec4 _fragColor;
+
+void main(void) {
+    Material mat = getMaterial();
+    int matKey = getMaterialKey(mat);
+    <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, _SCRIBE_NULL, emissiveTex)$>
+    <$fetchMaterialTexturesCoord1(matKey, _texCoord1, occlusionTex)$>
+
+    float opacity = 1.0;
+    <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>;
+
+    vec3 albedo = getMaterialAlbedo(mat);
+    <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>;
+    albedo *= _color;
+
+    float metallic = getMaterialMetallic(mat);
+    vec3 fresnel = vec3(0.03); // Default Di-electric fresnel value
+    if (metallic <= 0.5) {
+        metallic = 0.0;
+    } else {
+        fresnel = albedo;
+        metallic = 1.0;
+    }
+
+    float roughness = getMaterialRoughness(mat);
+    <$evalMaterialRoughness(roughnessTex, roughness, matKey, roughness)$>;
+
+    vec3 emissive = getMaterialEmissive(mat);
+    <$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>;
+
+
+    vec3 fragPosition = _position.xyz;
+
+    TransformCamera cam = getTransformCamera();
+    vec3 fragNormal;
+    <$transformEyeToWorldDir(cam, _normal, fragNormal)$>
+
+    vec4 color = vec4(evalGlobalLightingAlphaBlended(
+        cam._viewInverse,
+        1.0,
+        occlusionTex,
+        fragPosition,
+        fragNormal,
+        albedo,
+        fresnel,
+        metallic,
+        emissive,
+        roughness, opacity),
+        opacity);
+
+    // Apply standard tone mapping
+    _fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), color.w);
+}
\ No newline at end of file
diff --git a/scripts/developer/utilities/render/deferredLighting.qml b/scripts/developer/utilities/render/deferredLighting.qml
index 0ac4cbc5b5..99a9f258e3 100644
--- a/scripts/developer/utilities/render/deferredLighting.qml
+++ b/scripts/developer/utilities/render/deferredLighting.qml
@@ -159,13 +159,33 @@ Column {
         }
     }
 
-    Row {
+    Column {
         id: metas
         CheckBox {
-            text: "Draw Meta Bounds"
+            text: "Metas"
             checked: Render.getConfig("DrawMetaBounds")["enabled"]
             onCheckedChanged: { Render.getConfig("DrawMetaBounds")["enabled"] = checked }
         }
+        CheckBox {
+            text: "Opaques"
+            checked: Render.getConfig("DrawOpaqueBounds")["enabled"]
+            onCheckedChanged: { Render.getConfig("DrawOpaqueBounds")["enabled"] = checked }
+        }
+        CheckBox {
+            text: "Transparents"
+            checked: Render.getConfig("DrawTransparentBounds")["enabled"]
+            onCheckedChanged: { Render.getConfig("DrawTransparentBounds")["enabled"] = checked }
+        }
+        CheckBox {
+            text: "Overlay Opaques"
+            checked: Render.getConfig("DrawOverlayOpaqueBounds")["enabled"]
+            onCheckedChanged: { Render.getConfig("DrawOverlayOpaqueBounds")["enabled"] = checked }
+        }
+        CheckBox {
+            text: "Overlay Transparents"
+            checked: Render.getConfig("DrawOverlayTransparentBounds")["enabled"]
+            onCheckedChanged: { Render.getConfig("DrawOverlayTransparentBounds")["enabled"] = checked }
+        }
     }
 }
 

From f4a3627b767294133ef060ad5e74630721d29093 Mon Sep 17 00:00:00 2001
From: David Rowe <david@ctrlaltstudio.com>
Date: Tue, 28 Feb 2017 09:47:36 +1300
Subject: [PATCH 20/64] Make other avatar's grab beam start at more natural
 position

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

diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js
index fe0fe19ae3..1ab06927ac 100644
--- a/scripts/system/controllers/handControllerGrab.js
+++ b/scripts/system/controllers/handControllerGrab.js
@@ -1729,7 +1729,7 @@ function MyController(hand) {
             } else if (this.otherGrabbingUUID !== null) {
                 if (this.triggerSmoothedGrab() && !isEditing() && farGrabEnabled && farSearching) {
                     var avatar = AvatarList.getAvatar(this.otherGrabbingUUID);
-                    var IN_FRONT_OF_AVATAR = { x: 0, y: 0, z: 0.2 };
+                    var IN_FRONT_OF_AVATAR = { x: 0, y: 0.2, z: 0.4 };  // Up from hips and in front of avatar.
                     var startPosition = Vec3.sum(avatar.position, Vec3.multiplyQbyV(avatar.rotation, IN_FRONT_OF_AVATAR));
                     this.otherGrabbingLineOn(startPosition, rayPickInfo.properties.position, COLORS_GRAB_DISTANCE_HOLD);
                 } else {

From 08cae1d3f23c74b34a4bcb9349b0a8b68a3fc61e Mon Sep 17 00:00:00 2001
From: David Rowe <david@ctrlaltstudio.com>
Date: Tue, 28 Feb 2017 10:02:06 +1300
Subject: [PATCH 21/64] Make other avatar's grab beam finish at entity's
 centroid

---
 scripts/system/controllers/handControllerGrab.js | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js
index 1ab06927ac..cdfb33ee06 100644
--- a/scripts/system/controllers/handControllerGrab.js
+++ b/scripts/system/controllers/handControllerGrab.js
@@ -1731,7 +1731,11 @@ function MyController(hand) {
                     var avatar = AvatarList.getAvatar(this.otherGrabbingUUID);
                     var IN_FRONT_OF_AVATAR = { x: 0, y: 0.2, z: 0.4 };  // Up from hips and in front of avatar.
                     var startPosition = Vec3.sum(avatar.position, Vec3.multiplyQbyV(avatar.rotation, IN_FRONT_OF_AVATAR));
-                    this.otherGrabbingLineOn(startPosition, rayPickInfo.properties.position, COLORS_GRAB_DISTANCE_HOLD);
+                    var finishPisition = Vec3.sum(rayPickInfo.properties.position,  // Entity's centroid.
+                        Vec3.multiplyQbyV(rayPickInfo.properties.rotation ,
+                        Vec3.multiplyVbyV(rayPickInfo.properties.dimensions,
+                        Vec3.subtract(DEFAULT_REGISTRATION_POINT, rayPickInfo.properties.registrationPoint))));
+                    this.otherGrabbingLineOn(startPosition, finishPisition, COLORS_GRAB_DISTANCE_HOLD);
                 } else {
                     this.otherGrabbingLineOff();
                 }

From 6fcc096bcf57cd2d3429906e3ceec3e16dd5e140 Mon Sep 17 00:00:00 2001
From: David Rowe <david@ctrlaltstudio.com>
Date: Tue, 28 Feb 2017 10:14:38 +1300
Subject: [PATCH 22/64] Fix JavaScript error

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

diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js
index cdfb33ee06..3df7b91b6a 100644
--- a/scripts/system/controllers/handControllerGrab.js
+++ b/scripts/system/controllers/handControllerGrab.js
@@ -413,13 +413,13 @@ function entityIsGrabbedByOther(entityID) {
         var actionID = actionIDs[actionIndex];
         var actionArguments = Entities.getActionArguments(entityID, actionID);
         var tag = actionArguments.tag;
-        if (tag == getTag()) {
+        if (tag === getTag()) {
             // we see a grab-*uuid* shaped tag, but it's our tag, so that's okay.
             continue;
         }
         var GRAB_PREFIX_LENGTH = 5;
         var UUID_LENGTH = 38;
-        if (tag.slice(0, GRAB_PREFIX_LENGTH) == "grab-") {
+        if (tag && tag.slice(0, GRAB_PREFIX_LENGTH) == "grab-") {
             // we see a grab-*uuid* shaped tag and it's not ours, so someone else is grabbing it.
             return tag.slice(GRAB_PREFIX_LENGTH, GRAB_PREFIX_LENGTH + UUID_LENGTH - 1);
         }

From 2635657456a097bfc99d28eb44711f3b7a6b0797 Mon Sep 17 00:00:00 2001
From: David Kelly <david@highfidelity.io>
Date: Tue, 28 Feb 2017 13:24:25 -0700
Subject: [PATCH 23/64] zappoman's feedback, plus added const corrrectness to
 computeLoudness

---
 assignment-client/src/Agent.cpp |  4 +++-
 assignment-client/src/Agent.h   | 10 +++++-----
 2 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp
index 5211c3beaa..9c830ef391 100644
--- a/assignment-client/src/Agent.cpp
+++ b/assignment-client/src/Agent.cpp
@@ -380,6 +380,8 @@ void Agent::executeScript() {
         audioTransform.setTranslation(scriptedAvatar->getPosition());
         audioTransform.setRotation(headOrientation);
 
+        computeLoudness(&audio);
+
         QByteArray encodedBuffer;
         if (_encoder) {
             _encoder->encode(audio, encodedBuffer);
@@ -571,7 +573,7 @@ void Agent::encodeFrameOfZeros(QByteArray& encodedZeros) {
     }
 }
 
-void Agent::computeLoudness(QByteArray* decodedBuffer) {
+void Agent::computeLoudness(const QByteArray* decodedBuffer) {
     float loudness = 0.0f;
     auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
     if (decodedBuffer) {
diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h
index 7f04b4746f..ce7393011f 100644
--- a/assignment-client/src/Agent.h
+++ b/assignment-client/src/Agent.h
@@ -68,10 +68,10 @@ private slots:
     void handleAudioPacket(QSharedPointer<ReceivedMessage> message);
     void handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
     void handleJurisdictionPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
-    void handleSelectedAudioFormat(QSharedPointer<ReceivedMessage> message); 
+    void handleSelectedAudioFormat(QSharedPointer<ReceivedMessage> message);
 
     void nodeActivated(SharedNodePointer activatedNode);
-    
+
     void processAgentAvatar();
     void processAgentAvatarAudio();
 
@@ -82,7 +82,7 @@ private:
     void negotiateAudioFormat();
     void selectAudioFormat(const QString& selectedCodecName);
     void encodeFrameOfZeros(QByteArray& encodedZeros);
-    void computeLoudness(QByteArray* decodedBuffer);
+    void computeLoudness(const QByteArray* decodedBuffer);
 
     std::unique_ptr<ScriptEngine> _scriptEngine;
     EntityEditPacketSender _entityEditSender;
@@ -104,10 +104,10 @@ private:
     bool _isAvatar = false;
     QTimer* _avatarIdentityTimer = nullptr;
     QHash<QUuid, quint16> _outgoingScriptAudioSequenceNumbers;
-    
+
     CodecPluginPointer _codec;
     QString _selectedCodecName;
-    Encoder* _encoder { nullptr }; 
+    Encoder* _encoder { nullptr };
     QThread _avatarAudioTimerThread;
     bool _flushEncoder { false };
 };

From 6ac85aee7e22f1fd29b0ae257f9b0a033d0213f4 Mon Sep 17 00:00:00 2001
From: samcake <samuel.gateau@gmail.com>
Date: Tue, 28 Feb 2017 18:12:35 -0800
Subject: [PATCH 24/64] Adding support for the unliti materials too for
 overlay's

---
 .../render-utils/src/RenderPipelines.cpp      |  8 ++++
 .../src/overlay3D_model_translucent_unlit.slf | 43 ++++++++++++++++++
 .../src/overlay3D_model_unlit.slf             | 44 +++++++++++++++++++
 3 files changed, 95 insertions(+)
 create mode 100644 libraries/render-utils/src/overlay3D_model_translucent_unlit.slf
 create mode 100644 libraries/render-utils/src/overlay3D_model_unlit.slf

diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp
index d753c679c6..3b279ff6d9 100644
--- a/libraries/render-utils/src/RenderPipelines.cpp
+++ b/libraries/render-utils/src/RenderPipelines.cpp
@@ -55,6 +55,8 @@
 #include "overlay3D_translucent_frag.h"
 #include "overlay3D_unlit_frag.h"
 #include "overlay3D_translucent_unlit_frag.h"
+#include "overlay3D_model_unlit_frag.h"
+#include "overlay3D_model_translucent_unlit_frag.h"
 
 
 using namespace render;
@@ -79,6 +81,8 @@ void initOverlay3DPipelines(ShapePlumber& plumber) {
     auto pixelTranslucentUnlit = gpu::Shader::createPixel(std::string(overlay3D_translucent_unlit_frag));
     auto pixelModel = gpu::Shader::createPixel(std::string(overlay3D_model_frag));
     auto pixelModelTranslucent = gpu::Shader::createPixel(std::string(overlay3D_model_translucent_frag));
+    auto pixelModelUnlit = gpu::Shader::createPixel(std::string(overlay3D_model_unlit_frag));
+    auto pixelModelTranslucentUnlit = gpu::Shader::createPixel(std::string(overlay3D_model_translucent_unlit_frag));
 
     auto opaqueProgram = gpu::Shader::createProgram(vertex, pixel);
     auto translucentProgram = gpu::Shader::createProgram(vertex, pixelTranslucent);
@@ -86,6 +90,8 @@ void initOverlay3DPipelines(ShapePlumber& plumber) {
     auto unlitTranslucentProgram = gpu::Shader::createProgram(vertex, pixelTranslucentUnlit);
     auto materialOpaqueProgram = gpu::Shader::createProgram(vertexModel, pixelModel);
     auto materialTranslucentProgram = gpu::Shader::createProgram(vertexModel, pixelModelTranslucent);
+    auto materialUnlitOpaqueProgram = gpu::Shader::createProgram(vertexModel, pixelModel);
+    auto materialUnlitTranslucentProgram = gpu::Shader::createProgram(vertexModel, pixelModelTranslucent);
 
     for (int i = 0; i < 8; i++) {
         bool isCulled = (i & 1);
@@ -118,8 +124,10 @@ void initOverlay3DPipelines(ShapePlumber& plumber) {
         auto simpleProgram = isOpaque ? opaqueProgram : translucentProgram;
         auto unlitProgram = isOpaque ? unlitOpaqueProgram : unlitTranslucentProgram;
         auto materialProgram = isOpaque ? materialOpaqueProgram : materialTranslucentProgram;
+        auto materialUnlitProgram = isOpaque ? materialUnlitOpaqueProgram : materialUnlitTranslucentProgram;
 
         plumber.addPipeline(builder.withMaterial().build().key(), materialProgram, state, &lightBatchSetter);
+        plumber.addPipeline(builder.withMaterial().withUnlit().build().key(), materialUnlitProgram, state, &batchSetter);
         plumber.addPipeline(builder.withoutUnlit().withoutMaterial().build().key(), simpleProgram, state, &lightBatchSetter);
         plumber.addPipeline(builder.withUnlit().withoutMaterial().build().key(), unlitProgram, state, &batchSetter);
     }
diff --git a/libraries/render-utils/src/overlay3D_model_translucent_unlit.slf b/libraries/render-utils/src/overlay3D_model_translucent_unlit.slf
new file mode 100644
index 0000000000..3dd8138272
--- /dev/null
+++ b/libraries/render-utils/src/overlay3D_model_translucent_unlit.slf
@@ -0,0 +1,43 @@
+<@include gpu/Config.slh@>
+<$VERSION_HEADER$>
+//  Generated on <$_SCRIBE_DATE$>
+//  overlay3D-model_transparent_unlit.slf
+//  fragment shader
+//
+//  Created by Sam Gateau on 2/28/2017.
+//  Copyright 2015 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+<@include LightingModel.slh@>
+<@include model/Material.slh@>
+
+<@include MaterialTextures.slh@>
+<$declareMaterialTextures(ALBEDO)$>
+
+in vec2 _texCoord0;
+in vec3 _normal;
+in vec3 _color;
+in float _alpha;
+
+out vec4 _fragColor;
+
+void main(void) {
+
+    Material mat = getMaterial();
+    int matKey = getMaterialKey(mat);
+    <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex)$>
+
+    float opacity = 1.0;
+    <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>;
+ 
+    vec3 albedo = getMaterialAlbedo(mat);
+    <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>;
+    albedo *= _color;
+
+    vec4 color = vec4(albedo * isUnlitEnabled(), opacity);
+
+    _fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), color.w);
+}
\ No newline at end of file
diff --git a/libraries/render-utils/src/overlay3D_model_unlit.slf b/libraries/render-utils/src/overlay3D_model_unlit.slf
new file mode 100644
index 0000000000..80c2bb971e
--- /dev/null
+++ b/libraries/render-utils/src/overlay3D_model_unlit.slf
@@ -0,0 +1,44 @@
+<@include gpu/Config.slh@>
+<$VERSION_HEADER$>
+//  Generated on <$_SCRIBE_DATE$>
+//  overlay3D-model_unlit.slf
+//  fragment shader
+//
+//  Created by Sam Gateau on 2/28/2017.
+//  Copyright 2015 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+<@include LightingModel.slh@>
+<@include model/Material.slh@>
+
+<@include MaterialTextures.slh@>
+<$declareMaterialTextures(ALBEDO)$>
+
+in vec2 _texCoord0;
+in vec3 _normal;
+in vec3 _color;
+in float _alpha;
+
+out vec4 _fragColor;
+
+void main(void) {
+
+    Material mat = getMaterial();
+    int matKey = getMaterialKey(mat);
+    <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex)$>
+
+        float opacity = 1.0;
+    <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>;
+    <$discardTransparent(opacity)$>;
+
+    vec3 albedo = getMaterialAlbedo(mat);
+    <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>;
+    albedo *= _color;
+
+    vec4 color = vec4(albedo * isUnlitEnabled(), opacity);
+
+    _fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), color.w);
+}

From 01abb4bdb61ed9e6eff63da995cd74400f4cbad1 Mon Sep 17 00:00:00 2001
From: "Anthony J. Thibault" <tony@highfidelity.io>
Date: Fri, 24 Feb 2017 15:34:20 -0800
Subject: [PATCH 25/64] Exposed DebugDraw interface to Java Script

---
 libraries/script-engine/src/Quat.h           | 10 ++++
 libraries/script-engine/src/ScriptEngine.cpp |  3 +
 libraries/script-engine/src/Vec3.h           |  9 +++
 libraries/shared/src/DebugDraw.cpp           |  8 +--
 libraries/shared/src/DebugDraw.h             | 63 ++++++++++++++++----
 tools/jsdoc/plugins/hifi.js                  |  1 +
 6 files changed, 80 insertions(+), 14 deletions(-)

diff --git a/libraries/script-engine/src/Quat.h b/libraries/script-engine/src/Quat.h
index bb81f24586..b51f1cb47e 100644
--- a/libraries/script-engine/src/Quat.h
+++ b/libraries/script-engine/src/Quat.h
@@ -19,6 +19,16 @@
 #include <QObject>
 #include <QString>
 
+/**jsdoc
+ * A Quaternion
+ *
+ * @typedef Quat
+ * @property {float} x imaginary component i.
+ * @property {float} y imaginary component j.
+ * @property {float} z imaginary component k.
+ * @property {float} w real component.
+ */
+
 /// Scriptable interface a Quaternion helper class object. Used exclusively in the JavaScript API
 class Quat : public QObject {
     Q_OBJECT
diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp
index 83f2f5ccc0..2147374367 100644
--- a/libraries/script-engine/src/ScriptEngine.cpp
+++ b/libraries/script-engine/src/ScriptEngine.cpp
@@ -34,6 +34,7 @@
 #include <AudioConstants.h>
 #include <AudioEffectOptions.h>
 #include <AvatarData.h>
+#include <DebugDraw.h>
 #include <EntityScriptingInterface.h>
 #include <MessagesClient.h>
 #include <NetworkAccessManager.h>
@@ -630,6 +631,8 @@ void ScriptEngine::init() {
     registerGlobalObject("Tablet", DependencyManager::get<TabletScriptingInterface>().data());
     registerGlobalObject("Assets", &_assetScriptingInterface);
     registerGlobalObject("Resources", DependencyManager::get<ResourceScriptingInterface>().data());
+
+    registerGlobalObject("DebugDraw", &DebugDraw::getInstance());
 }
 
 void ScriptEngine::registerValue(const QString& valueName, QScriptValue value) {
diff --git a/libraries/script-engine/src/Vec3.h b/libraries/script-engine/src/Vec3.h
index 5f524eaf74..b3a3dc3035 100644
--- a/libraries/script-engine/src/Vec3.h
+++ b/libraries/script-engine/src/Vec3.h
@@ -37,6 +37,15 @@
  * @property {float} z Z-coordinate of the vector.
  */
 
+/**jsdoc
+ * A 4-dimensional vector.
+ *
+ * @typedef Vec4
+ * @property {float} x X-coordinate of the vector.
+ * @property {float} y Y-coordinate of the vector.
+ * @property {float} z Z-coordinate of the vector.
+ * @property {float} w W-coordinate of the vector.
+ */
 
 /// Scriptable interface a Vec3ernion helper class object. Used exclusively in the JavaScript API
 class Vec3 : public QObject {
diff --git a/libraries/shared/src/DebugDraw.cpp b/libraries/shared/src/DebugDraw.cpp
index 04759e6187..549dbb7293 100644
--- a/libraries/shared/src/DebugDraw.cpp
+++ b/libraries/shared/src/DebugDraw.cpp
@@ -28,19 +28,19 @@ void DebugDraw::drawRay(const glm::vec3& start, const glm::vec3& end, const glm:
     _rays.push_back(Ray(start, end, color));
 }
 
-void DebugDraw::addMarker(const std::string& key, const glm::quat& rotation, const glm::vec3& position, const glm::vec4& color) {
+void DebugDraw::addMarker(const QString& key, const glm::quat& rotation, const glm::vec3& position, const glm::vec4& color) {
     _markers[key] = MarkerInfo(rotation, position, color);
 }
 
-void DebugDraw::removeMarker(const std::string& key) {
+void DebugDraw::removeMarker(const QString& key) {
     _markers.erase(key);
 }
 
-void DebugDraw::addMyAvatarMarker(const std::string& key, const glm::quat& rotation, const glm::vec3& position, const glm::vec4& color) {
+void DebugDraw::addMyAvatarMarker(const QString& key, const glm::quat& rotation, const glm::vec3& position, const glm::vec4& color) {
     _myAvatarMarkers[key] = MarkerInfo(rotation, position, color);
 }
 
-void DebugDraw::removeMyAvatarMarker(const std::string& key) {
+void DebugDraw::removeMyAvatarMarker(const QString& key) {
     _myAvatarMarkers.erase(key);
 }
 
diff --git a/libraries/shared/src/DebugDraw.h b/libraries/shared/src/DebugDraw.h
index f77e281e06..ac7e8b3cbc 100644
--- a/libraries/shared/src/DebugDraw.h
+++ b/libraries/shared/src/DebugDraw.h
@@ -17,26 +17,69 @@
 #include <glm/glm.hpp>
 #include <glm/gtc/quaternion.hpp>
 
-class DebugDraw {
+#include <QObject>
+#include <QString>
+
+/**jsdoc
+ * Helper functions to render ephemeral debug markers and lines.
+ * DebugDraw markers and lines are only visible locally, they are not visible by other users.
+ * @namespace DebugDraw
+ */
+class DebugDraw : public QObject {
+    Q_OBJECT
 public:
     static DebugDraw& getInstance();
 
     DebugDraw();
     ~DebugDraw();
 
-    // world space line, drawn only once
-    void drawRay(const glm::vec3& start, const glm::vec3& end, const glm::vec4& color);
+    /**jsdoc
+     * Draws a line in world space, but it will only be visible for a single frame.
+     * @function DebugDraw.drawRay
+     * @param {Vec3} start - start position of line in world space.
+     * @param {Vec3} end - end position of line in world space.
+     * @param {Vec4} color - color of line, each component should be in the zero to one range.  x = red, y = blue, z = green, w = alpha.
+     */
+    Q_INVOKABLE void drawRay(const glm::vec3& start, const glm::vec3& end, const glm::vec4& color);
 
-    // world space maker, marker drawn every frame until it is removed.
-    void addMarker(const std::string& key, const glm::quat& rotation, const glm::vec3& position, const glm::vec4& color);
-    void removeMarker(const std::string& key);
+    /**jsdoc
+     * Adds a debug marker to the world. This marker will be drawn every frame until it is removed with DebugDraw.removeMarker.
+     * This can be called repeatedly to change the position of the marker.
+     * @function DebugDraw.addMarker
+     * @param {string} key - name to uniquely identify this marker, later used for DebugDraw.removeMarker.
+     * @param {Quat} rotation - start position of line in world space.
+     * @param {Vec3} position - position of the marker in world space.
+     * @param {Vec4} color - color of the marker.
+     */
+    Q_INVOKABLE void addMarker(const QString& key, const glm::quat& rotation, const glm::vec3& position, const glm::vec4& color);
 
-    // myAvatar relative marker, maker is drawn every frame until it is removed.
-    void addMyAvatarMarker(const std::string& key, const glm::quat& rotation, const glm::vec3& position, const glm::vec4& color);
-    void removeMyAvatarMarker(const std::string& key);
+    /**jsdoc
+     * Removes debug marker from the world.  Once a marker is removed, it will no longer be visible.
+     * @function DebugDraw.removeMarker
+     * @param {string} key - name of marker to remove.
+     */
+    Q_INVOKABLE void removeMarker(const QString& key);
+
+    /**jsdoc
+     * Adds a debug marker to the world, this marker will be drawn every frame until it is removed with DebugDraw.removeMyAvatarMarker.
+     * This can be called repeatedly to change the position of the marker.
+     * @function DebugDraw.addMyAvatarMarker
+     * @param {string} key - name to uniquely identify this marker, later used for DebugDraw.removeMyAvatarMarker.
+     * @param {Quat} rotation - start position of line in avatar space.
+     * @param {Vec3} position - position of the marker in avatar space.
+     * @param {Vec4} color - color of the marker.
+     */
+    Q_INVOKABLE void addMyAvatarMarker(const QString& key, const glm::quat& rotation, const glm::vec3& position, const glm::vec4& color);
+
+    /**jsdoc
+     * Removes debug marker from the world.  Once a marker is removed, it will no longer be visible
+     * @function DebugDraw.removeMyAvatarMarker
+     * @param {string} key - name of marker to remove.
+     */
+    Q_INVOKABLE void removeMyAvatarMarker(const QString& key);
 
     using MarkerInfo = std::tuple<glm::quat, glm::vec3, glm::vec4>;
-    using MarkerMap = std::unordered_map<std::string, MarkerInfo>;
+    using MarkerMap = std::map<QString, MarkerInfo>;
     using Ray = std::tuple<glm::vec3, glm::vec3, glm::vec4>;
     using Rays = std::vector<Ray>;
 
diff --git a/tools/jsdoc/plugins/hifi.js b/tools/jsdoc/plugins/hifi.js
index 8a6d2bf0f2..8be15c4103 100644
--- a/tools/jsdoc/plugins/hifi.js
+++ b/tools/jsdoc/plugins/hifi.js
@@ -21,6 +21,7 @@ exports.handlers = {
             '../../libraries/networking/src',
             '../../libraries/animation/src',
             '../../libraries/entities/src',
+            '../../libraries/shared/src'
         ];
         var exts = ['.h', '.cpp'];
 

From f41845710042c935d078ef0d04111ca99480053b Mon Sep 17 00:00:00 2001
From: "Anthony J. Thibault" <tony@highfidelity.io>
Date: Fri, 24 Feb 2017 15:50:41 -0800
Subject: [PATCH 26/64] Index finger touch support for the tablet-ui.

---
 .../system/controllers/handControllerGrab.js  | 215 ++++++++++++++----
 1 file changed, 169 insertions(+), 46 deletions(-)

diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js
index d313d1cfa1..5f854cd5ab 100644
--- a/scripts/system/controllers/handControllerGrab.js
+++ b/scripts/system/controllers/handControllerGrab.js
@@ -28,7 +28,7 @@ Script.include("/~/system/libraries/controllers.js");
 //
 
 var WANT_DEBUG = false;
-var WANT_DEBUG_STATE = false;
+var WANT_DEBUG_STATE = true;
 var WANT_DEBUG_SEARCH_NAME = null;
 
 var FORCE_IGNORE_IK = false;
@@ -46,6 +46,8 @@ var BUMPER_ON_VALUE = 0.5;
 
 var THUMB_ON_VALUE = 0.5;
 
+var USE_FINGER_AS_STYLUS = true;
+
 var HAPTIC_PULSE_STRENGTH = 1.0;
 var HAPTIC_PULSE_DURATION = 13.0;
 var HAPTIC_TEXTURE_STRENGTH = 0.1;
@@ -74,6 +76,10 @@ var WEB_TOUCH_Y_OFFSET = 0.05; // how far forward (or back with a negative numbe
 var WEB_TOUCH_TOO_CLOSE = 0.03; // if the stylus is pushed far though the web surface, don't consider it touching
 var WEB_TOUCH_Y_TOUCH_DEADZONE_SIZE = 0.01;
 
+var FINGER_TOUCH_Y_OFFSET = -0.02;
+var FINGER_TOUCH_MIN = -0.01 - FINGER_TOUCH_Y_OFFSET;
+var FINGER_TOUCH_MAX = 0.01 - FINGER_TOUCH_Y_OFFSET;
+
 //
 // distant manipulation
 //
@@ -252,20 +258,51 @@ CONTROLLER_STATE_MACHINE[STATE_FAR_TRIGGER] = {
     updateMethod: "farTrigger"
 };
 CONTROLLER_STATE_MACHINE[STATE_ENTITY_STYLUS_TOUCHING] = {
-    name: "entityTouching",
+    name: "entityStylusTouching",
+    enterMethod: "entityTouchingEnter",
+    exitMethod: "entityTouchingExit",
+    updateMethod: "entityTouching"
+};
+CONTROLLER_STATE_MACHINE[STATE_ENTITY_LASER_TOUCHING] = {
+    name: "entityLaserTouching",
     enterMethod: "entityTouchingEnter",
     exitMethod: "entityTouchingExit",
     updateMethod: "entityTouching"
 };
-CONTROLLER_STATE_MACHINE[STATE_ENTITY_LASER_TOUCHING] = CONTROLLER_STATE_MACHINE[STATE_ENTITY_STYLUS_TOUCHING];
 CONTROLLER_STATE_MACHINE[STATE_OVERLAY_STYLUS_TOUCHING] = {
-    name: "overlayTouching",
+    name: "overlayStylusTouching",
+    enterMethod: "overlayTouchingEnter",
+    exitMethod: "overlayTouchingExit",
+    updateMethod: "overlayTouching"
+};
+CONTROLLER_STATE_MACHINE[STATE_OVERLAY_LASER_TOUCHING] = {
+    name: "overlayLaserTouching",
     enterMethod: "overlayTouchingEnter",
     exitMethod: "overlayTouchingExit",
     updateMethod: "overlayTouching"
 };
-CONTROLLER_STATE_MACHINE[STATE_OVERLAY_LASER_TOUCHING] = CONTROLLER_STATE_MACHINE[STATE_OVERLAY_STYLUS_TOUCHING];
 
+function getFingerWorldLocation(hand) {
+    var fingerJointName = (hand === RIGHT_HAND) ? "RightHandIndex4" : "LeftHandIndex4";
+
+    var fingerJointIndex = MyAvatar.getJointIndex(fingerJointName);
+    var fingerPosition = MyAvatar.getAbsoluteJointTranslationInObjectFrame(fingerJointIndex);
+    var fingerRotation = MyAvatar.getAbsoluteJointRotationInObjectFrame(fingerJointIndex);
+    var worldFingerRotation = Quat.multiply(MyAvatar.orientation, fingerRotation);
+    var worldFingerPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, fingerPosition));
+
+    // local y offset.
+    var localYOffset = Vec3.multiplyQbyV(worldFingerRotation, {x: 0, y: FINGER_TOUCH_Y_OFFSET, z: 0});
+
+    var offsetWorldFingerPosition = Vec3.sum(worldFingerPosition, localYOffset);
+
+    return {
+        position: offsetWorldFingerPosition,
+        orientation: worldFingerRotation,
+        rotation: worldFingerRotation,
+        valid: true
+    };
+}
 
 function distanceBetweenPointAndEntityBoundingBox(point, entityProps) {
     var entityXform = new Xform(entityProps.rotation, entityProps.position);
@@ -347,6 +384,7 @@ function handLaserIntersectItem(position, rotation, start) {
             direction: rayDirection,
             length: PICK_MAX_DISTANCE
         };
+
         return intersectionInfo;
     } else {
         // entity has been destroyed? or is no longer in cache
@@ -816,6 +854,8 @@ function MyController(hand) {
     this.tabletStabbedPos2D = null;
     this.tabletStabbedPos3D = null;
 
+    this.useFingerInsteadOfStylus = false;
+
     var _this = this;
 
     var suppressedIn2D = [STATE_OFF, STATE_SEARCHING];
@@ -829,10 +869,17 @@ function MyController(hand) {
         this.updateSmoothedTrigger();
         this.maybeScaleMyAvatar();
 
+        if (USE_FINGER_AS_STYLUS && MyAvatar.getJointIndex("LeftHandIndex4") !== -1) {
+            this.useFingerInsteadOfStylus = true;
+        } else {
+            this.useFingerInsteadOfStylus = false;
+        }
+
         if (this.ignoreInput()) {
 
             // Most hand input is disabled, because we are interacting with the 2d hud.
             // However, we still should check for collisions of the stylus with the web overlay.
+
             var controllerLocation = getControllerWorldLocation(this.handToController(), true);
             this.processStylus(controllerLocation.position);
 
@@ -1174,30 +1221,54 @@ function MyController(hand) {
     };
 
     this.processStylus = function(worldHandPosition) {
-        // see if the hand is near a tablet or web-entity
-        var candidateEntities = Entities.findEntities(worldHandPosition, WEB_DISPLAY_STYLUS_DISTANCE);
-        entityPropertiesCache.addEntities(candidateEntities);
-        var nearWeb = false;
-        for (var i = 0; i < candidateEntities.length; i++) {
-            var props = entityPropertiesCache.getProps(candidateEntities[i]);
-            if (props && (props.type == "Web" || this.isTablet(candidateEntities[i]))) {
-                nearWeb = true;
-                break;
+
+        var performRayTest = false;
+        if (this.useFingerInsteadOfStylus) {
+            this.hideStylus();
+            performRayTest = true;
+        } else {
+            var i;
+
+            // see if the hand is near a tablet or web-entity
+            var candidateEntities = Entities.findEntities(worldHandPosition, WEB_DISPLAY_STYLUS_DISTANCE);
+            entityPropertiesCache.addEntities(candidateEntities);
+            for (i = 0; i < candidateEntities.length; i++) {
+                var props = entityPropertiesCache.getProps(candidateEntities[i]);
+                if (props && (props.type == "Web" || this.isTablet(candidateEntities[i]))) {
+                    performRayTest = true;
+                    break;
+                }
+            }
+
+            if (!performRayTest) {
+                var candidateOverlays = Overlays.findOverlays(worldHandPosition, WEB_DISPLAY_STYLUS_DISTANCE);
+                for (i = 0; i < candidateOverlays.length; i++) {
+                    if (this.isTablet(candidateOverlays[i])) {
+                        performRayTest = true;
+                        break;
+                    }
+                }
+            }
+
+            if (performRayTest) {
+                this.showStylus();
+            } else {
+                this.hideStylus();
             }
         }
 
-        var candidateOverlays = Overlays.findOverlays(worldHandPosition, WEB_DISPLAY_STYLUS_DISTANCE);
-        for (var j = 0; j < candidateOverlays.length; j++) {
-            if (this.isTablet(candidateOverlays[j])) {
-                nearWeb = true;
+        if (performRayTest) {
+            var rayPickInfo = this.calcRayPickInfo(this.hand, this.useFingerInsteadOfStylus);
+            var max, min;
+            if (this.useFingerInsteadOfStylus) {
+                max = FINGER_TOUCH_MAX;
+                min = FINGER_TOUCH_MIN;
+            } else {
+                max = WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_Y_OFFSET;
+                min = WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_TOO_CLOSE;
             }
-        }
 
-        if (nearWeb) {
-            this.showStylus();
-            var rayPickInfo = this.calcRayPickInfo(this.hand);
-            if (rayPickInfo.distance < WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_Y_OFFSET &&
-                rayPickInfo.distance > WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_TOO_CLOSE) {
+            if (rayPickInfo.distance < max && rayPickInfo.distance > min) {
                 this.handleStylusOnHomeButton(rayPickInfo);
                 if (this.handleStylusOnWebEntity(rayPickInfo)) {
                     return;
@@ -1206,10 +1277,8 @@ function MyController(hand) {
                     return;
                 }
             } else {
-        this.homeButtonTouched = false;
-        }
-        } else {
-            this.hideStylus();
+                this.homeButtonTouched = false;
+            }
         }
     };
 
@@ -1324,10 +1393,17 @@ function MyController(hand) {
 
     // Performs ray pick test from the hand controller into the world
     // @param {number} which hand to use, RIGHT_HAND or LEFT_HAND
+    // @param {bool} if true use the world position/orientation of the index finger to cast the ray from.
     // @returns {object} returns object with two keys entityID and distance
     //
-    this.calcRayPickInfo = function(hand) {
-        var controllerLocation = getControllerWorldLocation(this.handToController(), true);
+    this.calcRayPickInfo = function(hand, useFingerInsteadOfController) {
+
+        var controllerLocation;
+        if (useFingerInsteadOfController) {
+            controllerLocation = getFingerWorldLocation(hand);
+        } else {
+            controllerLocation = getControllerWorldLocation(this.handToController(), true);
+        }
         var worldHandPosition = controllerLocation.position;
         var worldHandRotation = controllerLocation.orientation;
 
@@ -2783,8 +2859,13 @@ function MyController(hand) {
 
     this.entityTouchingEnter = function() {
         // test for intersection between controller laser and web entity plane.
-        var intersectInfo = handLaserIntersectEntity(this.grabbedThingID,
-                                                     getControllerWorldLocation(this.handToController(), true));
+        var controllerLocation;
+        if (this.useFingerInsteadOfStylus && this.state === STATE_ENTITY_STYLUS_TOUCHING) {
+            controllerLocation = getFingerWorldLocation(this.hand);
+        } else {
+            controllerLocation = getControllerWorldLocation(this.handToController(), true);
+        }
+        var intersectInfo = handLaserIntersectEntity(this.grabbedThingID, controllerLocation);
         if (intersectInfo) {
             var pointerEvent = {
                 type: "Press",
@@ -2820,8 +2901,13 @@ function MyController(hand) {
 
     this.entityTouchingExit = function() {
         // test for intersection between controller laser and web entity plane.
-        var intersectInfo = handLaserIntersectEntity(this.grabbedThingID,
-                                                     getControllerWorldLocation(this.handToController(), true));
+        var controllerLocation;
+        if (this.useFingerInsteadOfStylus && this.state === STATE_ENTITY_STYLUS_TOUCHING) {
+            controllerLocation = getFingerWorldLocation(this.hand);
+        } else {
+            controllerLocation = getControllerWorldLocation(this.handToController(), true);
+        }
+        var intersectInfo = handLaserIntersectEntity(this.grabbedThingID, controllerLocation);
         if (intersectInfo) {
             var pointerEvent;
             if (this.deadspotExpired) {
@@ -2861,12 +2947,26 @@ function MyController(hand) {
         }
 
         // test for intersection between controller laser and web entity plane.
-        var intersectInfo = handLaserIntersectEntity(this.grabbedThingID,
-                                                     getControllerWorldLocation(this.handToController(), true));
+        var controllerLocation;
+        if (this.useFingerInsteadOfStylus && this.state === STATE_ENTITY_STYLUS_TOUCHING) {
+            controllerLocation = getFingerWorldLocation(this.hand);
+        } else {
+            controllerLocation = getControllerWorldLocation(this.handToController(), true);
+        }
+        var intersectInfo = handLaserIntersectEntity(this.grabbedThingID, controllerLocation);
         if (intersectInfo) {
 
+            var max, min;
+            if (this.useFingerInsteadOfStylus && this.state === STATE_ENTITY_STYLUS_TOUCHING) {
+                max = FINGER_TOUCH_MAX;
+                min = FINGER_TOUCH_MIN;
+            } else {
+                max = WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_Y_OFFSET;
+                min = WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_TOO_CLOSE;
+            }
+
             if (this.state == STATE_ENTITY_STYLUS_TOUCHING &&
-                intersectInfo.distance > WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_Y_OFFSET) {
+                intersectInfo.distance > max) {
                 this.setState(STATE_OFF, "pulled away from web entity");
                 return;
             }
@@ -2909,8 +3009,13 @@ function MyController(hand) {
 
     this.overlayTouchingEnter = function () {
         // Test for intersection between controller laser and Web overlay plane.
-        var intersectInfo =
-            handLaserIntersectOverlay(this.grabbedOverlay, getControllerWorldLocation(this.handToController(), true));
+        var controllerLocation;
+        if (this.useFingerInsteadOfStylus && this.state === STATE_OVERLAY_STYLUS_TOUCHING) {
+            controllerLocation = getFingerWorldLocation(this.hand);
+        } else {
+            controllerLocation = getControllerWorldLocation(this.handToController(), true);
+        }
+        var intersectInfo = handLaserIntersectOverlay(this.grabbedOverlay, controllerLocation);
         if (intersectInfo) {
             var pointerEvent = {
                 type: "Press",
@@ -2945,8 +3050,13 @@ function MyController(hand) {
 
     this.overlayTouchingExit = function () {
         // Test for intersection between controller laser and Web overlay plane.
-        var intersectInfo =
-            handLaserIntersectOverlay(this.grabbedOverlay, getControllerWorldLocation(this.handToController(), true));
+        var controllerLocation;
+        if (this.useFingerInsteadOfStylus && this.state === STATE_OVERLAY_STYLUS_TOUCHING) {
+            controllerLocation = getFingerWorldLocation(this.hand);
+        } else {
+            controllerLocation = getControllerWorldLocation(this.handToController(), true);
+        }
+        var intersectInfo = handLaserIntersectOverlay(this.grabbedOverlay, controllerLocation);
         if (intersectInfo) {
             var pointerEvent;
 
@@ -3003,12 +3113,25 @@ function MyController(hand) {
         }
 
         // Test for intersection between controller laser and Web overlay plane.
-        var intersectInfo =
-            handLaserIntersectOverlay(this.grabbedOverlay, getControllerWorldLocation(this.handToController(), true));
+        var controllerLocation;
+        if (this.useFingerInsteadOfStylus && this.state === STATE_OVERLAY_STYLUS_TOUCHING) {
+            controllerLocation = getFingerWorldLocation(this.hand);
+        } else {
+            controllerLocation = getControllerWorldLocation(this.handToController(), true);
+        }
+        var intersectInfo = handLaserIntersectOverlay(this.grabbedOverlay, controllerLocation);
         if (intersectInfo) {
 
-            if (this.state == STATE_OVERLAY_STYLUS_TOUCHING &&
-                intersectInfo.distance > WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_Y_OFFSET + WEB_TOUCH_Y_TOUCH_DEADZONE_SIZE) {
+            var max, min;
+            if (this.useFingerInsteadOfStylus && this.state === STATE_OVERLAY_STYLUS_TOUCHING) {
+                max = FINGER_TOUCH_MAX;
+                min = FINGER_TOUCH_MIN;
+            } else {
+                max = WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_Y_OFFSET + WEB_TOUCH_Y_TOUCH_DEADZONE_SIZE;
+                min = WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_TOO_CLOSE;
+            }
+
+            if (this.state == STATE_OVERLAY_STYLUS_TOUCHING && intersectInfo.distance > max) {
                 this.grabbedThingID = null;
                 this.setState(STATE_OFF, "pulled away from overlay");
                 return;
@@ -3019,7 +3142,7 @@ function MyController(hand) {
 
             if (this.state == STATE_OVERLAY_STYLUS_TOUCHING &&
                 !this.tabletStabbed &&
-                intersectInfo.distance < WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_TOO_CLOSE) {
+                intersectInfo.distance < min) {
                 // they've stabbed the tablet, don't send events until they pull back
                 this.tabletStabbed = true;
                 this.tabletStabbedPos2D = pos2D;

From d142c3d69bfd851b57f6901bf24440b3c0ec4697 Mon Sep 17 00:00:00 2001
From: "Anthony J. Thibault" <tony@highfidelity.io>
Date: Wed, 1 Mar 2017 13:29:24 -0800
Subject: [PATCH 27/64] eslint fix

---
 scripts/system/controllers/handControllerGrab.js | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js
index 5f854cd5ab..dbc2bebd31 100644
--- a/scripts/system/controllers/handControllerGrab.js
+++ b/scripts/system/controllers/handControllerGrab.js
@@ -2956,13 +2956,11 @@ function MyController(hand) {
         var intersectInfo = handLaserIntersectEntity(this.grabbedThingID, controllerLocation);
         if (intersectInfo) {
 
-            var max, min;
+            var max;
             if (this.useFingerInsteadOfStylus && this.state === STATE_ENTITY_STYLUS_TOUCHING) {
                 max = FINGER_TOUCH_MAX;
-                min = FINGER_TOUCH_MIN;
             } else {
                 max = WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_Y_OFFSET;
-                min = WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_TOO_CLOSE;
             }
 
             if (this.state == STATE_ENTITY_STYLUS_TOUCHING &&

From 465a9e2008f18e893b27e33e87e4c58c5c475bd2 Mon Sep 17 00:00:00 2001
From: "Anthony J. Thibault" <tony@highfidelity.io>
Date: Wed, 1 Mar 2017 14:31:04 -0800
Subject: [PATCH 28/64] Responsiveness improvement to tablet

PointerEvents sent from handControllerGrab.js to the Qml window are now direct connections, instead of queued.
This should reduce latency of laser and finger pressed onto the tablet.
---
 interface/src/ui/overlays/Web3DOverlay.cpp | 33 ++++++++++++++--------
 1 file changed, 21 insertions(+), 12 deletions(-)

diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp
index bfc37ccf60..7b9e075d64 100644
--- a/interface/src/ui/overlays/Web3DOverlay.cpp
+++ b/interface/src/ui/overlays/Web3DOverlay.cpp
@@ -198,18 +198,27 @@ void Web3DOverlay::render(RenderArgs* args) {
         _webSurface->getRootItem()->setProperty("scriptURL", _scriptURL);
         currentContext->makeCurrent(currentSurface);
 
+        auto selfOverlayID = getOverlayID();
+        std::weak_ptr<Web3DOverlay> weakSelf = std::dynamic_pointer_cast<Web3DOverlay>(qApp->getOverlays().getOverlay(selfOverlayID));
         auto forwardPointerEvent = [=](OverlayID overlayID, const PointerEvent& event) {
-            if (overlayID == getOverlayID()) {
-                handlePointerEvent(event);
+            auto self = weakSelf.lock();
+            if (!self) {
+                return;
+            }
+            if (overlayID == selfOverlayID) {
+                self->handlePointerEvent(event);
             }
         };
 
-        _mousePressConnection = connect(&(qApp->getOverlays()), &Overlays::mousePressOnOverlay, forwardPointerEvent);
-        _mouseReleaseConnection = connect(&(qApp->getOverlays()), &Overlays::mouseReleaseOnOverlay, forwardPointerEvent);
-        _mouseMoveConnection = connect(&(qApp->getOverlays()), &Overlays::mouseMoveOnOverlay, forwardPointerEvent);
-        _hoverLeaveConnection = connect(&(qApp->getOverlays()), &Overlays::hoverLeaveOverlay,
-            [=](OverlayID overlayID, const PointerEvent& event) {
-            if (this->_pressed && this->getOverlayID() == overlayID) {
+        _mousePressConnection = connect(&(qApp->getOverlays()), &Overlays::mousePressOnOverlay, this, forwardPointerEvent, Qt::DirectConnection);
+        _mouseReleaseConnection = connect(&(qApp->getOverlays()), &Overlays::mouseReleaseOnOverlay, this, forwardPointerEvent, Qt::DirectConnection);
+        _mouseMoveConnection = connect(&(qApp->getOverlays()), &Overlays::mouseMoveOnOverlay, this, forwardPointerEvent, Qt::DirectConnection);
+        _hoverLeaveConnection = connect(&(qApp->getOverlays()), &Overlays::hoverLeaveOverlay, this, [=](OverlayID overlayID, const PointerEvent& event) {
+            auto self = weakSelf.lock();
+            if (!self) {
+                return;
+            }
+            if (self->_pressed && overlayID == selfOverlayID) {
                 // If the user mouses off the overlay while the button is down, simulate a touch end.
                 QTouchEvent::TouchPoint point;
                 point.setId(event.getID());
@@ -222,12 +231,12 @@ void Web3DOverlay::render(RenderArgs* args) {
                 touchPoints.push_back(point);
                 QTouchEvent* touchEvent = new QTouchEvent(QEvent::TouchEnd, nullptr, Qt::NoModifier, Qt::TouchPointReleased,
                     touchPoints);
-                touchEvent->setWindow(_webSurface->getWindow());
+                touchEvent->setWindow(self->_webSurface->getWindow());
                 touchEvent->setDevice(&_touchDevice);
-                touchEvent->setTarget(_webSurface->getRootItem());
-                QCoreApplication::postEvent(_webSurface->getWindow(), touchEvent);
+                touchEvent->setTarget(self->_webSurface->getRootItem());
+                QCoreApplication::postEvent(self->_webSurface->getWindow(), touchEvent);
             }
-        });
+        }, Qt::DirectConnection);
 
         _emitScriptEventConnection = connect(this, &Web3DOverlay::scriptEventReceived, _webSurface.data(), &OffscreenQmlSurface::emitScriptEvent);
         _webEventReceivedConnection = connect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, &Web3DOverlay::webEventReceived);

From 81451191c1fef266e2b78b9549b1bb855710caea Mon Sep 17 00:00:00 2001
From: David Kelly <david@highfidelity.io>
Date: Wed, 1 Mar 2017 18:06:03 -0700
Subject: [PATCH 29/64] no need to get the ScriptableAvatar from
 DependencyManager every time

---
 assignment-client/src/Agent.cpp | 14 ++++++--------
 assignment-client/src/Agent.h   |  3 ++-
 2 files changed, 8 insertions(+), 9 deletions(-)

diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp
index 9c830ef391..d20ef2e687 100644
--- a/assignment-client/src/Agent.cpp
+++ b/assignment-client/src/Agent.cpp
@@ -43,7 +43,6 @@
 #include <WebSocketServerClass.h>
 #include <EntityScriptingInterface.h> // TODO: consider moving to scriptengine.h
 
-#include "avatars/ScriptableAvatar.h"
 #include "entities/AssignmentParentFinder.h"
 #include "RecordingScriptingInterface.h"
 #include "AbstractAudioInterface.h"
@@ -380,7 +379,7 @@ void Agent::executeScript() {
         audioTransform.setTranslation(scriptedAvatar->getPosition());
         audioTransform.setRotation(headOrientation);
 
-        computeLoudness(&audio);
+        computeLoudness(&audio, scriptedAvatar);
 
         QByteArray encodedBuffer;
         if (_encoder) {
@@ -573,9 +572,8 @@ void Agent::encodeFrameOfZeros(QByteArray& encodedZeros) {
     }
 }
 
-void Agent::computeLoudness(const QByteArray* decodedBuffer) {
+void Agent::computeLoudness(const QByteArray* decodedBuffer, QSharedPointer<ScriptableAvatar> scriptableAvatar) {
     float loudness = 0.0f;
-    auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
     if (decodedBuffer) {
         auto soundData = reinterpret_cast<const int16_t*>(decodedBuffer->constData());
         int numFrames = decodedBuffer->size() / sizeof(int16_t);
@@ -587,7 +585,7 @@ void Agent::computeLoudness(const QByteArray* decodedBuffer) {
             loudness /= numFrames;
         }
     }
-    scriptedAvatar->setAudioLoudness(loudness);
+    scriptableAvatar->setAudioLoudness(loudness);
 }
 
 void Agent::processAgentAvatarAudio() {
@@ -640,7 +638,7 @@ void Agent::processAgentAvatarAudio() {
 
         if (silentFrame) {
             // no matter what, the loudness should be set to 0
-            computeLoudness(nullptr);
+            computeLoudness(nullptr, scriptedAvatar);
 
             if (!_isListeningToAudioStream) {
                 // if we have a silent frame and we're not listening then just send nothing and break out of here
@@ -679,7 +677,7 @@ void Agent::processAgentAvatarAudio() {
             if (_flushEncoder) {
                 encodeFrameOfZeros(encodedBuffer);
                 // loudness is 0
-                computeLoudness(nullptr);
+                computeLoudness(nullptr, scriptedAvatar);
             } else {
                 QByteArray decodedBuffer(reinterpret_cast<const char*>(nextSoundOutput), numAvailableSamples*sizeof(int16_t));
                 if (_encoder) {
@@ -688,7 +686,7 @@ void Agent::processAgentAvatarAudio() {
                 } else {
                     encodedBuffer = decodedBuffer;
                 }
-                computeLoudness(&decodedBuffer);
+                computeLoudness(&decodedBuffer, scriptedAvatar);
             }
             audioPacket->write(encodedBuffer.constData(), encodedBuffer.size());
         }
diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h
index ce7393011f..0ce7b71d5d 100644
--- a/assignment-client/src/Agent.h
+++ b/assignment-client/src/Agent.h
@@ -30,6 +30,7 @@
 #include <plugins/CodecPlugin.h>
 
 #include "MixedAudioStream.h"
+#include "avatars/ScriptableAvatar.h"
 
 class Agent : public ThreadedAssignment {
     Q_OBJECT
@@ -82,7 +83,7 @@ private:
     void negotiateAudioFormat();
     void selectAudioFormat(const QString& selectedCodecName);
     void encodeFrameOfZeros(QByteArray& encodedZeros);
-    void computeLoudness(const QByteArray* decodedBuffer);
+    void computeLoudness(const QByteArray* decodedBuffer, QSharedPointer<ScriptableAvatar>);
 
     std::unique_ptr<ScriptEngine> _scriptEngine;
     EntityEditPacketSender _entityEditSender;

From 4f03c06a948ce37283f07247e73ffc793fa24f2e Mon Sep 17 00:00:00 2001
From: "Anthony J. Thibault" <tony@highfidelity.io>
Date: Wed, 1 Mar 2017 18:07:53 -0800
Subject: [PATCH 30/64] Added General Preference to control stylus vs finger
 usage

By default the finger is preferred over the stylus.
---
 interface/src/Application.cpp                    | 6 ++++++
 interface/src/Application.h                      | 3 +++
 interface/src/ui/PreferencesDialog.cpp           | 6 ++++++
 scripts/system/controllers/handControllerGrab.js | 7 +++++--
 4 files changed, 20 insertions(+), 2 deletions(-)

diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index d48fe19a99..d37c1a259e 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -549,6 +549,7 @@ const float DEFAULT_DESKTOP_TABLET_SCALE_PERCENT = 75.0f;
 const bool DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR = true;
 const bool DEFAULT_HMD_TABLET_BECOMES_TOOLBAR = false;
 const bool DEFAULT_TABLET_VISIBLE_TO_OTHERS = false;
+const bool DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS = true;
 
 Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bool runServer, QString runServerPathOption) :
     QApplication(argc, argv),
@@ -572,6 +573,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
     _desktopTabletBecomesToolbarSetting("desktopTabletBecomesToolbar", DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR),
     _hmdTabletBecomesToolbarSetting("hmdTabletBecomesToolbar", DEFAULT_HMD_TABLET_BECOMES_TOOLBAR),
     _tabletVisibleToOthersSetting("tabletVisibleToOthers", DEFAULT_TABLET_VISIBLE_TO_OTHERS),
+    _preferAvatarFingerOverStylusSetting("preferAvatarFingerOverStylus", DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS),
     _constrainToolbarPosition("toolbar/constrainToolbarToCenterX", true),
     _scaleMirror(1.0f),
     _rotateMirror(0.0f),
@@ -2362,6 +2364,10 @@ void Application::setTabletVisibleToOthersSetting(bool value) {
     updateSystemTabletMode();
 }
 
+void Application::setPreferAvatarFingerOverStylus(bool value) {
+    _preferAvatarFingerOverStylusSetting.set(value);
+}
+
 void Application::setSettingConstrainToolbarPosition(bool setting) {
     _constrainToolbarPosition.set(setting);
     DependencyManager::get<OffscreenUi>()->setConstrainToolbarToCenterX(setting);
diff --git a/interface/src/Application.h b/interface/src/Application.h
index 13c1458aee..ec6d9b19f7 100644
--- a/interface/src/Application.h
+++ b/interface/src/Application.h
@@ -220,6 +220,8 @@ public:
     void setHmdTabletBecomesToolbarSetting(bool value);
     bool getTabletVisibleToOthersSetting() { return _tabletVisibleToOthersSetting.get(); }
     void setTabletVisibleToOthersSetting(bool value);
+    bool getPreferAvatarFingerOverStylus() { return _preferAvatarFingerOverStylusSetting.get(); }
+    void setPreferAvatarFingerOverStylus(bool value);
 
     float getSettingConstrainToolbarPosition() { return _constrainToolbarPosition.get(); }
     void setSettingConstrainToolbarPosition(bool setting);
@@ -565,6 +567,7 @@ private:
     Setting::Handle<bool> _desktopTabletBecomesToolbarSetting;
     Setting::Handle<bool> _hmdTabletBecomesToolbarSetting;
     Setting::Handle<bool> _tabletVisibleToOthersSetting;
+    Setting::Handle<bool> _preferAvatarFingerOverStylusSetting;
     Setting::Handle<bool> _constrainToolbarPosition;
 
     float _scaleMirror;
diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp
index d291510556..c2caf91045 100644
--- a/interface/src/ui/PreferencesDialog.cpp
+++ b/interface/src/ui/PreferencesDialog.cpp
@@ -107,6 +107,12 @@ void setupPreferences() {
         auto setter = [](bool value) { qApp->setTabletVisibleToOthersSetting(value); };
         preferences->addPreference(new CheckPreference(UI_CATEGORY, "Tablet Is Visible To Others", getter, setter));
     }
+    {
+        auto getter = []()->bool { return qApp->getPreferAvatarFingerOverStylus(); };
+        auto setter = [](bool value) { qApp->setPreferAvatarFingerOverStylus(value); };
+        preferences->addPreference(new CheckPreference(UI_CATEGORY, "Prefer Avatar Finger Over Stylus", getter, setter));
+    }
+
     // Snapshots
     static const QString SNAPSHOTS { "Snapshots" };
     {
diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js
index dbc2bebd31..e52c3344e6 100644
--- a/scripts/system/controllers/handControllerGrab.js
+++ b/scripts/system/controllers/handControllerGrab.js
@@ -46,8 +46,6 @@ var BUMPER_ON_VALUE = 0.5;
 
 var THUMB_ON_VALUE = 0.5;
 
-var USE_FINGER_AS_STYLUS = true;
-
 var HAPTIC_PULSE_STRENGTH = 1.0;
 var HAPTIC_PULSE_DURATION = 13.0;
 var HAPTIC_TEXTURE_STRENGTH = 0.1;
@@ -869,6 +867,11 @@ function MyController(hand) {
         this.updateSmoothedTrigger();
         this.maybeScaleMyAvatar();
 
+        var DEFAULT_USE_FINGER_AS_STYLUS = true;
+        var USE_FINGER_AS_STYLUS = Settings.getValue("preferAvatarFingerOverStylus");
+        if (USE_FINGER_AS_STYLUS === "") {
+            USE_FINGER_AS_STYLUS = DEFAULT_USE_FINGER_AS_STYLUS;
+        }
         if (USE_FINGER_AS_STYLUS && MyAvatar.getJointIndex("LeftHandIndex4") !== -1) {
             this.useFingerInsteadOfStylus = true;
         } else {

From 29f263a29668e5901865c8d60aadf772183b4026 Mon Sep 17 00:00:00 2001
From: David Kelly <david@highfidelity.io>
Date: Thu, 2 Mar 2017 12:43:41 -0700
Subject: [PATCH 31/64] oh man, this was hard to find

---
 assignment-client/src/Agent.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp
index d20ef2e687..be23dcfa25 100644
--- a/assignment-client/src/Agent.cpp
+++ b/assignment-client/src/Agent.cpp
@@ -637,8 +637,6 @@ void Agent::processAgentAvatarAudio() {
         audioPacket->seek(sizeof(quint16));
 
         if (silentFrame) {
-            // no matter what, the loudness should be set to 0
-            computeLoudness(nullptr, scriptedAvatar);
 
             if (!_isListeningToAudioStream) {
                 // if we have a silent frame and we're not listening then just send nothing and break out of here
@@ -658,6 +656,8 @@ void Agent::processAgentAvatarAudio() {
             audioPacket->writePrimitive(scriptedAvatar->getPosition());
             audioPacket->writePrimitive(glm::vec3(0));
 
+            // no matter what, the loudness should be set to 0
+            computeLoudness(nullptr, scriptedAvatar);
         } else if (nextSoundOutput) {
 
             // write the codec

From 644e29a43d63bf8029e4f71bf4d6d576d693eaab Mon Sep 17 00:00:00 2001
From: "Anthony J. Thibault" <tony@highfidelity.io>
Date: Thu, 2 Mar 2017 14:15:05 -0800
Subject: [PATCH 32/64] disable WANT_STATE_DEBUG in handControllerGrab.js

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

diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js
index e52c3344e6..33254e9da2 100644
--- a/scripts/system/controllers/handControllerGrab.js
+++ b/scripts/system/controllers/handControllerGrab.js
@@ -28,7 +28,7 @@ Script.include("/~/system/libraries/controllers.js");
 //
 
 var WANT_DEBUG = false;
-var WANT_DEBUG_STATE = true;
+var WANT_DEBUG_STATE = false;
 var WANT_DEBUG_SEARCH_NAME = null;
 
 var FORCE_IGNORE_IK = false;

From bc256f3e8f01ce07b5740f00099e7dac59c1b4ef Mon Sep 17 00:00:00 2001
From: "Anthony J. Thibault" <tony@highfidelity.io>
Date: Thu, 2 Mar 2017 14:42:06 -0800
Subject: [PATCH 33/64] Fix for multi-threaded access to maps in DebugDraw.

---
 libraries/shared/src/DebugDraw.cpp | 30 ++++++++++++++++++++++++++++++
 libraries/shared/src/DebugDraw.h   | 10 ++++++----
 2 files changed, 36 insertions(+), 4 deletions(-)

diff --git a/libraries/shared/src/DebugDraw.cpp b/libraries/shared/src/DebugDraw.cpp
index 549dbb7293..f17671da4d 100644
--- a/libraries/shared/src/DebugDraw.cpp
+++ b/libraries/shared/src/DebugDraw.cpp
@@ -10,6 +10,8 @@
 #include "DebugDraw.h"
 #include "SharedUtil.h"
 
+using Lock = std::unique_lock<std::mutex>;
+
 DebugDraw& DebugDraw::getInstance() {
     static DebugDraw* instance = globalInstance<DebugDraw>("com.highfidelity.DebugDraw");
     return *instance;
@@ -25,22 +27,50 @@ DebugDraw::~DebugDraw() {
 
 // world space line, drawn only once
 void DebugDraw::drawRay(const glm::vec3& start, const glm::vec3& end, const glm::vec4& color) {
+    Lock lock(_mapMutex);
     _rays.push_back(Ray(start, end, color));
 }
 
 void DebugDraw::addMarker(const QString& key, const glm::quat& rotation, const glm::vec3& position, const glm::vec4& color) {
+    Lock lock(_mapMutex);
     _markers[key] = MarkerInfo(rotation, position, color);
 }
 
 void DebugDraw::removeMarker(const QString& key) {
+    Lock lock(_mapMutex);
     _markers.erase(key);
 }
 
 void DebugDraw::addMyAvatarMarker(const QString& key, const glm::quat& rotation, const glm::vec3& position, const glm::vec4& color) {
+    Lock lock(_mapMutex);
     _myAvatarMarkers[key] = MarkerInfo(rotation, position, color);
 }
 
 void DebugDraw::removeMyAvatarMarker(const QString& key) {
+    Lock lock(_mapMutex);
     _myAvatarMarkers.erase(key);
 }
 
+//
+// accessors used by renderer
+//
+
+DebugDraw::MarkerMap DebugDraw::getMarkerMap() const {
+    Lock lock(_mapMutex);
+    return _markers;
+}
+
+DebugDraw::MarkerMap DebugDraw::getMyAvatarMarkerMap() const {
+    Lock lock(_mapMutex);
+    return _myAvatarMarkers;
+}
+
+DebugDraw::Rays DebugDraw::getRays() const {
+    Lock lock(_mapMutex);
+    return _rays;
+}
+
+void DebugDraw::clearRays() {
+    Lock lock(_mapMutex);
+    _rays.clear();
+}
diff --git a/libraries/shared/src/DebugDraw.h b/libraries/shared/src/DebugDraw.h
index ac7e8b3cbc..64327585fb 100644
--- a/libraries/shared/src/DebugDraw.h
+++ b/libraries/shared/src/DebugDraw.h
@@ -10,6 +10,7 @@
 #ifndef hifi_DebugDraw_h
 #define hifi_DebugDraw_h
 
+#include <mutex>
 #include <unordered_map>
 #include <tuple>
 #include <string>
@@ -87,16 +88,17 @@ public:
     // accessors used by renderer
     //
 
-    const MarkerMap& getMarkerMap() const { return _markers; }
-    const MarkerMap& getMyAvatarMarkerMap() const { return _myAvatarMarkers; }
+    MarkerMap getMarkerMap() const;
+    MarkerMap getMyAvatarMarkerMap() const;
     void updateMyAvatarPos(const glm::vec3& pos) { _myAvatarPos = pos; }
     const glm::vec3& getMyAvatarPos() const { return _myAvatarPos; }
     void updateMyAvatarRot(const glm::quat& rot) { _myAvatarRot = rot; }
     const glm::quat& getMyAvatarRot() const { return _myAvatarRot; }
-    const Rays getRays() const { return _rays; }
-    void clearRays() { _rays.clear(); }
+    Rays getRays() const;
+    void clearRays();
 
 protected:
+    mutable std::mutex _mapMutex;
     MarkerMap _markers;
     MarkerMap _myAvatarMarkers;
     glm::quat _myAvatarRot;

From e56f02d94f8c499acb50b099830d4dce05be0875 Mon Sep 17 00:00:00 2001
From: "Anthony J. Thibault" <tony@highfidelity.io>
Date: Fri, 3 Mar 2017 14:12:04 -0800
Subject: [PATCH 34/64] Changed default for preferFingerOverStylus to false.

---
 interface/src/Application.cpp                    | 2 +-
 scripts/system/controllers/handControllerGrab.js | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 0335743360..56b93e57e4 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -549,7 +549,7 @@ const float DEFAULT_DESKTOP_TABLET_SCALE_PERCENT = 75.0f;
 const bool DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR = true;
 const bool DEFAULT_HMD_TABLET_BECOMES_TOOLBAR = false;
 const bool DEFAULT_TABLET_VISIBLE_TO_OTHERS = false;
-const bool DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS = true;
+const bool DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS = false;
 
 Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bool runServer, QString runServerPathOption) :
     QApplication(argc, argv),
diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js
index db678044b5..f53512b7a7 100644
--- a/scripts/system/controllers/handControllerGrab.js
+++ b/scripts/system/controllers/handControllerGrab.js
@@ -896,7 +896,7 @@ function MyController(hand) {
         this.updateSmoothedTrigger();
         this.maybeScaleMyAvatar();
 
-        var DEFAULT_USE_FINGER_AS_STYLUS = true;
+        var DEFAULT_USE_FINGER_AS_STYLUS = false;
         var USE_FINGER_AS_STYLUS = Settings.getValue("preferAvatarFingerOverStylus");
         if (USE_FINGER_AS_STYLUS === "") {
             USE_FINGER_AS_STYLUS = DEFAULT_USE_FINGER_AS_STYLUS;

From 15d680d4f9fd1ed08ccddd7396ad4a18051fe5b1 Mon Sep 17 00:00:00 2001
From: howard-stearns <howard.stearns@gmail.com>
Date: Fri, 3 Mar 2017 16:56:12 -0800
Subject: [PATCH 35/64] preserve audio data during pal refresh

---
 scripts/system/pal.js | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/scripts/system/pal.js b/scripts/system/pal.js
index 70b2739c96..4914cbe34c 100644
--- a/scripts/system/pal.js
+++ b/scripts/system/pal.js
@@ -248,12 +248,16 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
         }
         break;
     case 'refresh':
+        data = {};
+        ExtendedOverlay.some(function (overlay) { // capture the audio data
+            data[overlay.key] = overlay;
+        });
         removeOverlays();
         // If filter is specified from .qml instead of through settings, update the settings.
         if (message.params.filter !== undefined) {
             Settings.setValue('pal/filtered', !!message.params.filter);
         }
-        populateUserList(message.params.selected);
+        populateUserList(message.params.selected, data);
         UserActivityLogger.palAction("refresh", "");
         break;
     case 'displayNameUpdate':
@@ -285,7 +289,7 @@ function addAvatarNode(id) {
 }
 // Each open/refresh will capture a stable set of avatarsOfInterest, within the specified filter.
 var avatarsOfInterest = {};
-function populateUserList(selectData) {
+function populateUserList(selectData, oldAudioData) {
     var filter = Settings.getValue('pal/filtered') && {distance: Settings.getValue('pal/nearDistance')};
     var data = [], avatars = AvatarList.getAvatarIdentifiers();
     avatarsOfInterest = {};
@@ -317,12 +321,13 @@ function populateUserList(selectData) {
         if (id && filter && ((Math.abs(horizontal) > horizontalHalfAngle) || (Math.abs(vertical) > verticalHalfAngle))) {
             return;
         }
+        var oldAudio = oldAudioData && oldAudioData[id];
         var avatarPalDatum = {
             displayName: name,
             userName: '',
             sessionId: id || '',
-            audioLevel: 0.0,
-            avgAudioLevel: 0.0,
+            audioLevel: (oldAudio && oldAudio.audioLevel) || 0.0,
+            avgAudioLevel: (oldAudio && oldAudio.avgAudioLevel) || 0.0,
             admin: false,
             personalMute: !!id && Users.getPersonalMuteStatus(id), // expects proper boolean, not null
             ignore: !!id && Users.getIgnoreStatus(id) // ditto

From 6d4abca0c122d855825fdb2cf3212ab89ba8220f Mon Sep 17 00:00:00 2001
From: Menithal <menithal@norteclabs.com>
Date: Sat, 4 Mar 2017 11:12:19 +0200
Subject: [PATCH 36/64] Base line progress

---
 .../tutorials/entity_scripts/magneticBlock.js | 68 +++++++++++++++++++
 scripts/tutorials/makeBlocks.js               |  0
 2 files changed, 68 insertions(+)
 create mode 100644 scripts/tutorials/entity_scripts/magneticBlock.js
 create mode 100644 scripts/tutorials/makeBlocks.js

diff --git a/scripts/tutorials/entity_scripts/magneticBlock.js b/scripts/tutorials/entity_scripts/magneticBlock.js
new file mode 100644
index 0000000000..6eb0900db5
--- /dev/null
+++ b/scripts/tutorials/entity_scripts/magneticBlock.js
@@ -0,0 +1,68 @@
+(function(){
+
+  // Helper for detecting nearby objects
+  function findEntitiesInRange(releasedProperties) {
+    var dimensions = releasedProperties.dimensions;
+    return Entities.findEntities(releasedProperties.position, ((dimensions.x + dimensions.y + dimensions.z) / 3) *1.5);
+  }
+  function getNearestValidEntityProperties(id) {
+    var releasedProperties = Entities.getEntityProperties(id,["position", "rotation", "dimensions"]);
+    var entities = findEntitiesInRange(releasedProperties);
+    var nearestEntity = null;
+    var nearest = -1;
+    var releaseSize = Vec3.length(releasedProperties.dimensions);
+    entities.forEach(function(entityId) {
+      print('ftest ' + entityId);
+      var entity = Entities.getEntityProperties(entityId, ['position', 'rotation', 'dimensions']);
+      var distance = Vec3.distance(releasedProperties.position, entity.position);
+      var scale = releaseSize/Vec3.length(entity.dimensions);
+      if ((nearest === -1 || distance < nearest && scale >= 0.5 && scale <= 2 ) && entity.id !== entityId) {
+        nearestEntity = entity;
+        dnearest = distance;
+      }
+    })
+    return nearestEntity;
+
+  }
+
+  // Create the 'class'
+  function MagneticBlock () {  }
+  // Bind pre-emptive events
+  MagneticBlock.prototype = {
+    // When script is bound to an entity, preload is the first callback called with the entityID. It will behave as the constructor
+    preload: function (id) {
+      /*
+        We will now override any existing userdata with the grabbable property.
+        Only retrieving userData
+      */
+      var val = Entities.getEntityProperties(id, ['userData'])
+      var userData = {};
+      if (val.userData && val.userData.length > 0 ) {
+        try {
+          userData = JSON.parse(val.userData);
+        } catch (e) {}
+      }
+      // Object must be triggerable inorder to bind events.
+      userData.grabbableKey = {grabbable: true};
+      // Apply the new properties to entity of id
+      Entities.editEntity(id, {userData: JSON.stringify(userData)});
+      this.held = false;
+
+      // We will now create a custom binding, to keep the 'current' context as these are callbacks called without context
+      var t = this;
+      this.callbacks = {};
+      this.callbacks["releaseGrab"] = function () {
+        var nearest = getNearestValidEntityProperties(id);
+
+        print(JSON.stringify(nearest));
+      }
+
+      this.releaseGrab = this.callbacks["releaseGrab"];
+
+      Script.scriptEnding.connect( function () {
+        Script.removeEventHandler(id, "releaseGrab", this.callbacks["releaseGrab"]); //continueNearGrab
+      })
+    }
+  }
+  return new MagneticBlock();
+})
diff --git a/scripts/tutorials/makeBlocks.js b/scripts/tutorials/makeBlocks.js
new file mode 100644
index 0000000000..e69de29bb2

From ea3f7f02749856c9ca7bde0fc7a77f2464b13ffe Mon Sep 17 00:00:00 2001
From: Menithal <menithal@norteclabs.com>
Date: Sat, 4 Mar 2017 18:50:30 +0200
Subject: [PATCH 37/64] Initial Magnetic block

---
 scripts/system/assets/sounds/entitySnap.wav   | Bin 0 -> 30858 bytes
 .../tutorials/entity_scripts/magneticBlock.js |  79 ++++++++++++++----
 scripts/tutorials/makeBlocks.js               |  15 ++++
 3 files changed, 78 insertions(+), 16 deletions(-)
 create mode 100644 scripts/system/assets/sounds/entitySnap.wav

diff --git a/scripts/system/assets/sounds/entitySnap.wav b/scripts/system/assets/sounds/entitySnap.wav
new file mode 100644
index 0000000000000000000000000000000000000000..4584f3dcaa7469e2efd170c68a30ed306674ca91
GIT binary patch
literal 30858
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
V+^P2ZtnudGiK8ba0Y9qI{{i{Yq~`zt

literal 0
HcmV?d00001

diff --git a/scripts/tutorials/entity_scripts/magneticBlock.js b/scripts/tutorials/entity_scripts/magneticBlock.js
index 6eb0900db5..73f317e3b2 100644
--- a/scripts/tutorials/entity_scripts/magneticBlock.js
+++ b/scripts/tutorials/entity_scripts/magneticBlock.js
@@ -1,30 +1,34 @@
 (function(){
 
+
+  const SNAPSOUND = SoundCache.getSound(Script.resolvePath("../../system/assets/sounds/entitySnap.wav?xrs"));
+  const RANGE_MULTIPLER = 1.5;
+
   // Helper for detecting nearby objects
   function findEntitiesInRange(releasedProperties) {
     var dimensions = releasedProperties.dimensions;
-    return Entities.findEntities(releasedProperties.position, ((dimensions.x + dimensions.y + dimensions.z) / 3) *1.5);
+    return Entities.findEntities(releasedProperties.position, ((dimensions.x + dimensions.y + dimensions.z) / 3) * RANGE_MULTIPLER);
   }
-  function getNearestValidEntityProperties(id) {
-    var releasedProperties = Entities.getEntityProperties(id,["position", "rotation", "dimensions"]);
+
+  function getNearestValidEntityProperties(releasedProperties) {
     var entities = findEntitiesInRange(releasedProperties);
     var nearestEntity = null;
-    var nearest = -1;
+    var nearest = 9999999999999;
     var releaseSize = Vec3.length(releasedProperties.dimensions);
     entities.forEach(function(entityId) {
-      print('ftest ' + entityId);
-      var entity = Entities.getEntityProperties(entityId, ['position', 'rotation', 'dimensions']);
-      var distance = Vec3.distance(releasedProperties.position, entity.position);
-      var scale = releaseSize/Vec3.length(entity.dimensions);
-      if ((nearest === -1 || distance < nearest && scale >= 0.5 && scale <= 2 ) && entity.id !== entityId) {
-        nearestEntity = entity;
-        dnearest = distance;
+      if(entityId !== releasedProperties.id) {
+        var entity = Entities.getEntityProperties(entityId, ['position', 'rotation', 'dimensions']);
+        var distance = Vec3.distance(releasedProperties.position, entity.position);
+        var scale = releaseSize/Vec3.length(entity.dimensions);
+
+        if (distance < nearest && (scale >= 0.5 && scale <= 2)) {
+          nearestEntity = entity;
+          nearest = distance;
+        }
       }
     })
     return nearestEntity;
-
   }
-
   // Create the 'class'
   function MagneticBlock () {  }
   // Bind pre-emptive events
@@ -52,13 +56,56 @@
       var t = this;
       this.callbacks = {};
       this.callbacks["releaseGrab"] = function () {
-        var nearest = getNearestValidEntityProperties(id);
+          var released = Entities.getEntityProperties(id,["position", "rotation", "dimensions"]);
+          var target = getNearestValidEntityProperties(released);
+          if (target !== null) {
+              // We found nearest, now lets do the snap calculations
+              // Plays the snap sound between the two objects.
+              Audio.playSound(SNAPSOUND, {
+                  volume: 1,
+                  position: Vec3.mix(target.position, released.position, 0.5)
+              });
+              // Check Nearest Axis
+              var difference = Vec3.subtract(released.position, target.position);
+              var relativeDifference = Vec3.multiplyQbyV(Quat.inverse(target.rotation), difference);
 
-        print(JSON.stringify(nearest));
+              var abs = {
+                  x: Math.abs(relativeDifference.x),
+                  y: Math.abs(relativeDifference.y),
+                  z: Math.abs(relativeDifference.z)
+              };
+
+              if (abs.x >= abs.y && abs.x >= abs.z) {
+                  relativeDifference.y = 0;
+                  relativeDifference.z = 0;
+                  if (relativeDifference.x > 0) {
+                      relativeDifference.x = target.dimensions.x / 2 + released.dimensions.x / 2;
+                  } else {
+                      relativeDifference.x = -target.dimensions.x / 2 - released.dimensions.x / 2;
+                  }
+              } else if (abs.y >= abs.x && abs.y >= abs.z) {
+                  relativeDifference.x = 0;
+                  relativeDifference.z = 0;
+                  if (relativeDifference.y > 0) {
+                      relativeDifference.y = target.dimensions.y / 2 + released.dimensions.y / 2;
+                  } else {
+                      relativeDifference.y = -target.dimensions.y / 2 - released.dimensions.y / 2;
+                  }
+              } else if (abs.z >= abs.x && abs.z >= abs.y ) {
+                  relativeDifference.x = 0;
+                  relativeDifference.y = 0;
+                  if (relativeDifference.z > 0) {
+                      relativeDifference.z = target.dimensions.z / 2 + released.dimensions.z / 2;
+                  } else {
+                      relativeDifference.z = -target.dimensions.z / 2 - released.dimensions.z / 2;
+                  }
+              }
+              var newPosition = Vec3.multiplyQbyV(target.rotation, relativeDifference);
+              Entities.editEntity(id, {rotation: target.rotation, position: Vec3.sum(target.position, newPosition)})
+          }
       }
 
       this.releaseGrab = this.callbacks["releaseGrab"];
-
       Script.scriptEnding.connect( function () {
         Script.removeEventHandler(id, "releaseGrab", this.callbacks["releaseGrab"]); //continueNearGrab
       })
diff --git a/scripts/tutorials/makeBlocks.js b/scripts/tutorials/makeBlocks.js
index e69de29bb2..6a8b95bb3f 100644
--- a/scripts/tutorials/makeBlocks.js
+++ b/scripts/tutorials/makeBlocks.js
@@ -0,0 +1,15 @@
+// Toy
+const MAX_RGB_COMPONENT_VALUE = 256 / 2; // Limit the values to half the maximum.
+const MIN_COLOR_VALUE = 127;
+function newColor() {
+  color = {
+    red: randomPastelRGBComponent(),
+    green: randomPastelRGBComponent(),
+    blue: randomPastelRGBComponent()
+  };
+  return color;
+  }
+// Helper functions.
+function randomPastelRGBComponent() {
+  return Math.floor(Math.random() * MAX_RGB_COMPONENT_VALUE) + MIN_COLOR_VALUE;
+}

From fe19b5511ce5b1f04c42e6db7369c1c9f075c289 Mon Sep 17 00:00:00 2001
From: Menithal <menithal@norteclabs.com>
Date: Sat, 4 Mar 2017 21:55:21 +0200
Subject: [PATCH 38/64] Fixed up blocks scripts

---
 .../tutorials/entity_scripts/magneticBlock.js | 240 ++++++++++--------
 scripts/tutorials/makeBlocks.js               |  79 ++++--
 2 files changed, 193 insertions(+), 126 deletions(-)

diff --git a/scripts/tutorials/entity_scripts/magneticBlock.js b/scripts/tutorials/entity_scripts/magneticBlock.js
index 73f317e3b2..a375025671 100644
--- a/scripts/tutorials/entity_scripts/magneticBlock.js
+++ b/scripts/tutorials/entity_scripts/magneticBlock.js
@@ -1,115 +1,133 @@
-(function(){
+//
+//  magneticBlock.js
+//
+//  Created by Matti Lahtinen 4/3/2017
+//  Copyright 2017 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
+//
+//  Makes the entity the script is bound to connect to nearby, similarly sized entities, like a magnet.
 
+(function() {
+    const SNAPSOUND_SOURCE = SoundCache.getSound(Script.resolvePath("../../system/assets/sounds/entitySnap.wav?xrs"));
+    // Preload trick for faster playback
+    const RANGE_MULTIPLER = 1.5;
 
-  const SNAPSOUND = SoundCache.getSound(Script.resolvePath("../../system/assets/sounds/entitySnap.wav?xrs"));
-  const RANGE_MULTIPLER = 1.5;
-
-  // Helper for detecting nearby objects
-  function findEntitiesInRange(releasedProperties) {
-    var dimensions = releasedProperties.dimensions;
-    return Entities.findEntities(releasedProperties.position, ((dimensions.x + dimensions.y + dimensions.z) / 3) * RANGE_MULTIPLER);
-  }
-
-  function getNearestValidEntityProperties(releasedProperties) {
-    var entities = findEntitiesInRange(releasedProperties);
-    var nearestEntity = null;
-    var nearest = 9999999999999;
-    var releaseSize = Vec3.length(releasedProperties.dimensions);
-    entities.forEach(function(entityId) {
-      if(entityId !== releasedProperties.id) {
-        var entity = Entities.getEntityProperties(entityId, ['position', 'rotation', 'dimensions']);
-        var distance = Vec3.distance(releasedProperties.position, entity.position);
-        var scale = releaseSize/Vec3.length(entity.dimensions);
-
-        if (distance < nearest && (scale >= 0.5 && scale <= 2)) {
-          nearestEntity = entity;
-          nearest = distance;
-        }
-      }
-    })
-    return nearestEntity;
-  }
-  // Create the 'class'
-  function MagneticBlock () {  }
-  // Bind pre-emptive events
-  MagneticBlock.prototype = {
-    // When script is bound to an entity, preload is the first callback called with the entityID. It will behave as the constructor
-    preload: function (id) {
-      /*
-        We will now override any existing userdata with the grabbable property.
-        Only retrieving userData
-      */
-      var val = Entities.getEntityProperties(id, ['userData'])
-      var userData = {};
-      if (val.userData && val.userData.length > 0 ) {
-        try {
-          userData = JSON.parse(val.userData);
-        } catch (e) {}
-      }
-      // Object must be triggerable inorder to bind events.
-      userData.grabbableKey = {grabbable: true};
-      // Apply the new properties to entity of id
-      Entities.editEntity(id, {userData: JSON.stringify(userData)});
-      this.held = false;
-
-      // We will now create a custom binding, to keep the 'current' context as these are callbacks called without context
-      var t = this;
-      this.callbacks = {};
-      this.callbacks["releaseGrab"] = function () {
-          var released = Entities.getEntityProperties(id,["position", "rotation", "dimensions"]);
-          var target = getNearestValidEntityProperties(released);
-          if (target !== null) {
-              // We found nearest, now lets do the snap calculations
-              // Plays the snap sound between the two objects.
-              Audio.playSound(SNAPSOUND, {
-                  volume: 1,
-                  position: Vec3.mix(target.position, released.position, 0.5)
-              });
-              // Check Nearest Axis
-              var difference = Vec3.subtract(released.position, target.position);
-              var relativeDifference = Vec3.multiplyQbyV(Quat.inverse(target.rotation), difference);
-
-              var abs = {
-                  x: Math.abs(relativeDifference.x),
-                  y: Math.abs(relativeDifference.y),
-                  z: Math.abs(relativeDifference.z)
-              };
-
-              if (abs.x >= abs.y && abs.x >= abs.z) {
-                  relativeDifference.y = 0;
-                  relativeDifference.z = 0;
-                  if (relativeDifference.x > 0) {
-                      relativeDifference.x = target.dimensions.x / 2 + released.dimensions.x / 2;
-                  } else {
-                      relativeDifference.x = -target.dimensions.x / 2 - released.dimensions.x / 2;
-                  }
-              } else if (abs.y >= abs.x && abs.y >= abs.z) {
-                  relativeDifference.x = 0;
-                  relativeDifference.z = 0;
-                  if (relativeDifference.y > 0) {
-                      relativeDifference.y = target.dimensions.y / 2 + released.dimensions.y / 2;
-                  } else {
-                      relativeDifference.y = -target.dimensions.y / 2 - released.dimensions.y / 2;
-                  }
-              } else if (abs.z >= abs.x && abs.z >= abs.y ) {
-                  relativeDifference.x = 0;
-                  relativeDifference.y = 0;
-                  if (relativeDifference.z > 0) {
-                      relativeDifference.z = target.dimensions.z / 2 + released.dimensions.z / 2;
-                  } else {
-                      relativeDifference.z = -target.dimensions.z / 2 - released.dimensions.z / 2;
-                  }
-              }
-              var newPosition = Vec3.multiplyQbyV(target.rotation, relativeDifference);
-              Entities.editEntity(id, {rotation: target.rotation, position: Vec3.sum(target.position, newPosition)})
-          }
-      }
-
-      this.releaseGrab = this.callbacks["releaseGrab"];
-      Script.scriptEnding.connect( function () {
-        Script.removeEventHandler(id, "releaseGrab", this.callbacks["releaseGrab"]); //continueNearGrab
-      })
+    // Helper for detecting nearby objects
+    function findEntitiesInRange(releasedProperties) {
+        var dimensions = releasedProperties.dimensions;
+        return Entities.findEntities(releasedProperties.position, ((dimensions.x + dimensions.y + dimensions.z) / 3) * RANGE_MULTIPLER);
     }
-  }
-  return new MagneticBlock();
+
+    function getNearestValidEntityProperties(releasedProperties) {
+        var entities = findEntitiesInRange(releasedProperties);
+        var nearestEntity = null;
+        var nearest = 9999999999999;
+        var releaseSize = Vec3.length(releasedProperties.dimensions);
+        entities.forEach(function(entityId) {
+            if (entityId !== releasedProperties.id) {
+                var entity = Entities.getEntityProperties(entityId, ['position', 'rotation', 'dimensions']);
+                var distance = Vec3.distance(releasedProperties.position, entity.position);
+                var scale = releaseSize / Vec3.length(entity.dimensions);
+
+                if (distance < nearest && (scale >= 0.5 && scale <= 2)) {
+                    nearestEntity = entity;
+                    nearest = distance;
+                }
+            }
+        })
+        return nearestEntity;
+    }
+    // Create the 'class'
+    function MagneticBlock() {}
+    // Bind pre-emptive events
+    MagneticBlock.prototype = {
+        // When script is bound to an entity, preload is the first callback called with the entityID. It will behave as the constructor
+        preload: function(id) {
+            /*
+              We will now override any existing userdata with the grabbable property.
+              Only retrieving userData
+            */
+            var val = Entities.getEntityProperties(id, ['userData'])
+            var userData = {grabbableKey: {}};
+
+            if (val.userData && val.userData.length > 0) {
+                try {
+                    userData = JSON.parse(val.userData);
+                } catch (e) {
+                }
+            }
+            // Object must be triggerable inorder to bind events.
+            userData.grabbableKey.grabbable = true;
+
+            // Apply the new properties to entity of id
+            Entities.editEntity(id, {
+                userData: JSON.stringify(userData)
+            });
+            this.held = false;
+            // We will now create a custom binding, to keep the 'current' context as these are callbacks called without context
+            var t = this;
+            this.callbacks = {};
+            this.releaseGrab = function() {
+                var released = Entities.getEntityProperties(id, ["position", "rotation", "dimensions"]);
+                var target = getNearestValidEntityProperties(released);
+                if (target !== null) {
+                    // We found nearest, now lets do the snap calculations
+                    // Plays the snap sound between the two objects.
+                    Audio.playSound(SNAPSOUND_SOURCE, {
+                        volume: 1,
+                        position: Vec3.mix(target.position, released.position, 0.5)
+                    });
+                    // Check Nearest Axis
+                    var difference = Vec3.subtract(released.position, target.position);
+                    var relativeDifference = Vec3.multiplyQbyV(Quat.inverse(target.rotation), difference);
+
+                    var abs = {
+                        x: Math.abs(relativeDifference.x),
+                        y: Math.abs(relativeDifference.y),
+                        z: Math.abs(relativeDifference.z)
+                    };
+                    // Check what value is greater. Simplified.
+                    if (abs.x >= abs.y && abs.x >= abs.z) {
+                        relativeDifference.y = 0;
+                        relativeDifference.z = 0;
+                        if (relativeDifference.x > 0) {
+                            relativeDifference.x = target.dimensions.x / 2 + released.dimensions.x / 2;
+                        } else {
+                            relativeDifference.x = -target.dimensions.x / 2 - released.dimensions.x / 2;
+                        }
+                    } else if (abs.y >= abs.x && abs.y >= abs.z) {
+                        relativeDifference.x = 0;
+                        relativeDifference.z = 0;
+                        if (relativeDifference.y > 0) {
+                            relativeDifference.y = target.dimensions.y / 2 + released.dimensions.y / 2;
+                        } else {
+                            relativeDifference.y = -target.dimensions.y / 2 - released.dimensions.y / 2;
+                        }
+                    } else if (abs.z >= abs.x && abs.z >= abs.y) {
+                        relativeDifference.x = 0;
+                        relativeDifference.y = 0;
+                        if (relativeDifference.z > 0) {
+                            relativeDifference.z = target.dimensions.z / 2 + released.dimensions.z / 2;
+                        } else {
+                            relativeDifference.z = -target.dimensions.z / 2 - released.dimensions.z / 2;
+                        }
+                    }
+                    // Can be expanded upon to work in nearest rotation as well, but was not in spec.
+                    var newPosition = Vec3.multiplyQbyV(target.rotation, relativeDifference);
+                    Entities.editEntity(id, {
+                        rotation: target.rotation,
+                        position: Vec3.sum(target.position, newPosition)
+                    })
+                }
+            }
+
+            Script.scriptEnding.connect(function() {
+                Script.removeEventHandler(id, "releaseGrab", this.releaseGrab);
+            })
+        }
+    }
+    return new MagneticBlock();
 })
diff --git a/scripts/tutorials/makeBlocks.js b/scripts/tutorials/makeBlocks.js
index 6a8b95bb3f..bb4974498c 100644
--- a/scripts/tutorials/makeBlocks.js
+++ b/scripts/tutorials/makeBlocks.js
@@ -1,15 +1,64 @@
-// Toy
-const MAX_RGB_COMPONENT_VALUE = 256 / 2; // Limit the values to half the maximum.
-const MIN_COLOR_VALUE = 127;
-function newColor() {
-  color = {
-    red: randomPastelRGBComponent(),
-    green: randomPastelRGBComponent(),
-    blue: randomPastelRGBComponent()
-  };
-  return color;
-  }
-// Helper functions.
-function randomPastelRGBComponent() {
-  return Math.floor(Math.random() * MAX_RGB_COMPONENT_VALUE) + MIN_COLOR_VALUE;
-}
+//
+//  makeBlocks.js
+//
+//  Created by Matti Lahtinen 4/3/2017
+//  Copyright 2017 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
+//
+//  Creates multiple  "magnetic" blocks with random colors that users clones of and snap together.
+
+
+(function() {
+    const MAX_RGB_COMPONENT_VALUE = 256 / 2; // Limit the values to half the maximum.
+    const MIN_COLOR_VALUE = 127;
+    const SIZE = 0.3;
+    const LIFETIME = 600;
+
+    // Random Pastel Generator based on Piper's script
+    function newColor() {
+        color = {
+            red: randomPastelRGBComponent(),
+            green: randomPastelRGBComponent(),
+            blue: randomPastelRGBComponent()
+        };
+        return color;
+    }
+    // Helper functions.
+    function randomPastelRGBComponent() {
+        return Math.floor(Math.random() * MAX_RGB_COMPONENT_VALUE) + MIN_COLOR_VALUE;
+    }
+
+    var SCRIPT_URL = Script.resolvePath("./entity_scripts/magneticBlock.js");
+
+    var frontVector = Quat.getFront(MyAvatar.orientation);
+    frontVector.y -=.25;
+    for(var x =0; x < 3; x++) {
+        for (var y = 0; y < 3; y++) {
+
+            var frontOffset = {
+                x: 0,
+                y: SIZE * y + SIZE,
+                z: SIZE * x + SIZE
+            };
+
+            Entities.addEntity({
+                type: "Box",
+                name: "MagneticBlock-" + y +'-' + x,
+                dimensions: {
+                    x: SIZE,
+                    y: SIZE,
+                    z: SIZE
+                },
+                userData: JSON.stringify({grabbableKey: { cloneable: true, grabbable: true, cloneLifetime : LIFETIME, cloneLimit: 9999}}),
+                position: Vec3.sum(MyAvatar.position, Vec3.sum(frontOffset, frontVector)),
+                color: newColor(),
+                script: SCRIPT_URL
+            });
+        }
+    }
+
+    Script.stop();
+})();

From 0bababf1f5866dff5f73c4650a87648cb427b5a1 Mon Sep 17 00:00:00 2001
From: Ken Cooke <ken@highfidelity.io>
Date: Sat, 4 Mar 2017 14:53:07 -0800
Subject: [PATCH 39/64] Safe replacement of glm_mat4_mul() for unaligned
 arguments instead of __m128

---
 libraries/shared/src/GLMHelpers.h | 49 +++++++++++++++++++++++++++++++
 1 file changed, 49 insertions(+)

diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h
index 4aac913768..609c3ab08b 100644
--- a/libraries/shared/src/GLMHelpers.h
+++ b/libraries/shared/src/GLMHelpers.h
@@ -245,4 +245,53 @@ inline bool isNaN(const glm::quat& value) { return isNaN(value.w) || isNaN(value
 
 glm::mat4 orthoInverse(const glm::mat4& m);
 
+//
+// Safe replacement of glm_mat4_mul() for unaligned arguments instead of __m128
+//
+inline void glm_mat4u_mul(const glm::mat4& m1, const glm::mat4& m2, glm::mat4& r) {
+
+#if GLM_ARCH & GLM_ARCH_SSE2_BIT
+    __m128 u0 = _mm_loadu_ps((float*)&m1[0][0]);
+    __m128 u1 = _mm_loadu_ps((float*)&m1[1][0]);
+    __m128 u2 = _mm_loadu_ps((float*)&m1[2][0]);
+    __m128 u3 = _mm_loadu_ps((float*)&m1[3][0]);
+
+    __m128 v0 = _mm_loadu_ps((float*)&m2[0][0]);
+    __m128 v1 = _mm_loadu_ps((float*)&m2[1][0]);
+    __m128 v2 = _mm_loadu_ps((float*)&m2[2][0]);
+    __m128 v3 = _mm_loadu_ps((float*)&m2[3][0]);
+
+    __m128 t0 = _mm_mul_ps(_mm_shuffle_ps(v0, v0, _MM_SHUFFLE(0,0,0,0)), u0);
+    __m128 t1 = _mm_mul_ps(_mm_shuffle_ps(v0, v0, _MM_SHUFFLE(1,1,1,1)), u1);
+    __m128 t2 = _mm_mul_ps(_mm_shuffle_ps(v0, v0, _MM_SHUFFLE(2,2,2,2)), u2);
+    __m128 t3 = _mm_mul_ps(_mm_shuffle_ps(v0, v0, _MM_SHUFFLE(3,3,3,3)), u3);
+    v0 = _mm_add_ps(_mm_add_ps(t0, t1), _mm_add_ps(t2, t3));
+
+    t0 = _mm_mul_ps(_mm_shuffle_ps(v1, v1, _MM_SHUFFLE(0,0,0,0)), u0);
+    t1 = _mm_mul_ps(_mm_shuffle_ps(v1, v1, _MM_SHUFFLE(1,1,1,1)), u1);
+    t2 = _mm_mul_ps(_mm_shuffle_ps(v1, v1, _MM_SHUFFLE(2,2,2,2)), u2);
+    t3 = _mm_mul_ps(_mm_shuffle_ps(v1, v1, _MM_SHUFFLE(3,3,3,3)), u3);
+    v1 = _mm_add_ps(_mm_add_ps(t0, t1), _mm_add_ps(t2, t3));
+
+    t0 = _mm_mul_ps(_mm_shuffle_ps(v2, v2, _MM_SHUFFLE(0,0,0,0)), u0);
+    t1 = _mm_mul_ps(_mm_shuffle_ps(v2, v2, _MM_SHUFFLE(1,1,1,1)), u1);
+    t2 = _mm_mul_ps(_mm_shuffle_ps(v2, v2, _MM_SHUFFLE(2,2,2,2)), u2);
+    t3 = _mm_mul_ps(_mm_shuffle_ps(v2, v2, _MM_SHUFFLE(3,3,3,3)), u3);
+    v2 = _mm_add_ps(_mm_add_ps(t0, t1), _mm_add_ps(t2, t3));
+
+    t0 = _mm_mul_ps(_mm_shuffle_ps(v3, v3, _MM_SHUFFLE(0,0,0,0)), u0);
+    t1 = _mm_mul_ps(_mm_shuffle_ps(v3, v3, _MM_SHUFFLE(1,1,1,1)), u1);
+    t2 = _mm_mul_ps(_mm_shuffle_ps(v3, v3, _MM_SHUFFLE(2,2,2,2)), u2);
+    t3 = _mm_mul_ps(_mm_shuffle_ps(v3, v3, _MM_SHUFFLE(3,3,3,3)), u3);
+    v3 = _mm_add_ps(_mm_add_ps(t0, t1), _mm_add_ps(t2, t3));
+
+    _mm_storeu_ps((float*)&r[0][0], v0);
+    _mm_storeu_ps((float*)&r[1][0], v1);
+    _mm_storeu_ps((float*)&r[2][0], v2);
+    _mm_storeu_ps((float*)&r[3][0], v3);
+#else
+    r = m1 * m2;
+#endif
+}
+
 #endif // hifi_GLMHelpers_h

From 117bba8b6a79473190220e15b77617f32eb7171b Mon Sep 17 00:00:00 2001
From: Ken Cooke <ken@highfidelity.io>
Date: Sat, 4 Mar 2017 15:17:09 -0800
Subject: [PATCH 40/64] redo unsafe optimization

---
 interface/src/avatar/CauterizedModel.cpp | 8 +-------
 1 file changed, 1 insertion(+), 7 deletions(-)

diff --git a/interface/src/avatar/CauterizedModel.cpp b/interface/src/avatar/CauterizedModel.cpp
index 0c3d863649..7faf89dec5 100644
--- a/interface/src/avatar/CauterizedModel.cpp
+++ b/interface/src/avatar/CauterizedModel.cpp
@@ -110,13 +110,7 @@ void CauterizedModel::updateClusterMatrices() {
         for (int j = 0; j < mesh.clusters.size(); j++) {
             const FBXCluster& cluster = mesh.clusters.at(j);
             auto jointMatrix = _rig->getJointTransform(cluster.jointIndex);
-#if (GLM_ARCH & GLM_ARCH_SSE2) && !(defined Q_OS_MAC)
-            glm::mat4 out, inverseBindMatrix = cluster.inverseBindMatrix;
-            glm_mat4_mul((glm_vec4*)&jointMatrix, (glm_vec4*)&inverseBindMatrix, (glm_vec4*)&out);
-            state.clusterMatrices[j] = out;
-#else
-            state.clusterMatrices[j] = jointMatrix * cluster.inverseBindMatrix;
-#endif
+            glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]);
         }
 
         // Once computed the cluster matrices, update the buffer(s)

From 46c5f961130a42e0434927f33d75470ce7b8323e Mon Sep 17 00:00:00 2001
From: Ken Cooke <ken@highfidelity.io>
Date: Sat, 4 Mar 2017 15:24:03 -0800
Subject: [PATCH 41/64] redo unsafe optimization

---
 interface/src/avatar/CauterizedModel.cpp | 8 +-------
 1 file changed, 1 insertion(+), 7 deletions(-)

diff --git a/interface/src/avatar/CauterizedModel.cpp b/interface/src/avatar/CauterizedModel.cpp
index 7faf89dec5..1ca87a498a 100644
--- a/interface/src/avatar/CauterizedModel.cpp
+++ b/interface/src/avatar/CauterizedModel.cpp
@@ -143,13 +143,7 @@ void CauterizedModel::updateClusterMatrices() {
                 if (_cauterizeBoneSet.find(cluster.jointIndex) != _cauterizeBoneSet.end()) {
                     jointMatrix = cauterizeMatrix;
                 }
-#if (GLM_ARCH & GLM_ARCH_SSE2) && !(defined Q_OS_MAC)
-                glm::mat4 out, inverseBindMatrix = cluster.inverseBindMatrix;
-                glm_mat4_mul((glm_vec4*)&jointMatrix, (glm_vec4*)&inverseBindMatrix, (glm_vec4*)&out);
-                state.clusterMatrices[j] = out;
-#else
-                state.clusterMatrices[j] = jointMatrix * cluster.inverseBindMatrix;
-#endif
+                glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]);
             }
 
             if (!_cauterizeBoneSet.empty() && (state.clusterMatrices.size() > 1)) {

From 50f92cb934034767416cc5946e886b1cdb4048e8 Mon Sep 17 00:00:00 2001
From: Ken Cooke <ken@highfidelity.io>
Date: Sat, 4 Mar 2017 15:29:23 -0800
Subject: [PATCH 42/64] redo unsafe optimization

---
 interface/src/avatar/SoftAttachmentModel.cpp | 8 +-------
 1 file changed, 1 insertion(+), 7 deletions(-)

diff --git a/interface/src/avatar/SoftAttachmentModel.cpp b/interface/src/avatar/SoftAttachmentModel.cpp
index 6ed54afb27..0521f7a893 100644
--- a/interface/src/avatar/SoftAttachmentModel.cpp
+++ b/interface/src/avatar/SoftAttachmentModel.cpp
@@ -60,13 +60,7 @@ void SoftAttachmentModel::updateClusterMatrices() {
             } else {
                 jointMatrix = _rig->getJointTransform(cluster.jointIndex);
             }
-#if (GLM_ARCH & GLM_ARCH_SSE2) && !(defined Q_OS_MAC)
-            glm::mat4 out, inverseBindMatrix = cluster.inverseBindMatrix;
-            glm_mat4_mul((glm_vec4*)&jointMatrix, (glm_vec4*)&inverseBindMatrix, (glm_vec4*)&out);
-            state.clusterMatrices[j] = out;
-#else
-            state.clusterMatrices[j] = jointMatrix * cluster.inverseBindMatrix;
-#endif
+            glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]);
         }
 
         // Once computed the cluster matrices, update the buffer(s)

From 44c1f8500dfafc8de6ee0338baaeee40790f1451 Mon Sep 17 00:00:00 2001
From: Ken Cooke <ken@highfidelity.io>
Date: Sat, 4 Mar 2017 15:55:53 -0800
Subject: [PATCH 43/64] redo unsafe optimization

---
 libraries/animation/src/AnimPose.cpp | 8 +-------
 1 file changed, 1 insertion(+), 7 deletions(-)

diff --git a/libraries/animation/src/AnimPose.cpp b/libraries/animation/src/AnimPose.cpp
index 5638cacabc..e1c8528e0b 100644
--- a/libraries/animation/src/AnimPose.cpp
+++ b/libraries/animation/src/AnimPose.cpp
@@ -50,15 +50,9 @@ glm::vec3 AnimPose::xformVector(const glm::vec3& rhs) const {
 }
 
 AnimPose AnimPose::operator*(const AnimPose& rhs) const {
-#if (GLM_ARCH & GLM_ARCH_SSE2) && !(defined Q_OS_MAC)
     glm::mat4 result;
-    glm::mat4 lhsMat = *this;
-    glm::mat4 rhsMat = rhs;
-    glm_mat4_mul((glm_vec4*)&lhsMat, (glm_vec4*)&rhsMat, (glm_vec4*)&result);
+    glm_mat4u_mul(*this, rhs, result);
     return AnimPose(result);
-#else 
-    return AnimPose(static_cast<glm::mat4>(*this) * static_cast<glm::mat4>(rhs));
-#endif
 }
 
 AnimPose AnimPose::inverse() const {

From a5571bd49dbd4e71f236e95d8bf637aa9574f67c Mon Sep 17 00:00:00 2001
From: Ken Cooke <ken@highfidelity.io>
Date: Sat, 4 Mar 2017 16:02:39 -0800
Subject: [PATCH 44/64] redo unsafe optimization

---
 libraries/render-utils/src/Model.cpp | 8 +-------
 1 file changed, 1 insertion(+), 7 deletions(-)

diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp
index adfffe2614..d4de05c84d 100644
--- a/libraries/render-utils/src/Model.cpp
+++ b/libraries/render-utils/src/Model.cpp
@@ -1178,13 +1178,7 @@ void Model::updateClusterMatrices() {
         for (int j = 0; j < mesh.clusters.size(); j++) {
             const FBXCluster& cluster = mesh.clusters.at(j);
             auto jointMatrix = _rig->getJointTransform(cluster.jointIndex);
-#if (GLM_ARCH & GLM_ARCH_SSE2) && !(defined Q_OS_MAC)
-            glm::mat4 out, inverseBindMatrix = cluster.inverseBindMatrix;
-            glm_mat4_mul((glm_vec4*)&jointMatrix, (glm_vec4*)&inverseBindMatrix, (glm_vec4*)&out);
-            state.clusterMatrices[j] = out;
-#else
-            state.clusterMatrices[j] = jointMatrix * cluster.inverseBindMatrix;
-#endif
+            glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]);
         }
 
         // Once computed the cluster matrices, update the buffer(s)

From 818425707b182343ecbd0739fcc2d9df5b5cd689 Mon Sep 17 00:00:00 2001
From: Ken Cooke <ken@highfidelity.io>
Date: Sat, 4 Mar 2017 16:14:31 -0800
Subject: [PATCH 45/64] update unit tests

---
 tests/shared/src/GLMHelpersTests.cpp | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/tests/shared/src/GLMHelpersTests.cpp b/tests/shared/src/GLMHelpersTests.cpp
index 8d26d35c69..b4af4729a3 100644
--- a/tests/shared/src/GLMHelpersTests.cpp
+++ b/tests/shared/src/GLMHelpersTests.cpp
@@ -115,8 +115,8 @@ void GLMHelpersTests::testSimd() {
 
     a1 = a * b;
     b1 = b * a;
-    glm_mat4_mul((glm_vec4*)&a, (glm_vec4*)&b, (glm_vec4*)&a2);
-    glm_mat4_mul((glm_vec4*)&b, (glm_vec4*)&a, (glm_vec4*)&b2);
+    glm_mat4u_mul(a, b, a2);
+    glm_mat4u_mul(b, a, b2);
 
 
     {
@@ -133,8 +133,8 @@ void GLMHelpersTests::testSimd() {
         QElapsedTimer timer;
         timer.start();
         for (size_t i = 0; i < LOOPS; ++i) {
-            glm_mat4_mul((glm_vec4*)&a, (glm_vec4*)&b, (glm_vec4*)&a2);
-            glm_mat4_mul((glm_vec4*)&b, (glm_vec4*)&a, (glm_vec4*)&b2);
+            glm_mat4u_mul(a, b, a2);
+            glm_mat4u_mul(b, a, b2);
         }
         qDebug() << "SIMD " << timer.elapsed();
     }

From 04d3bf0c3802e1a77dee971eca94a915ca6c1dc6 Mon Sep 17 00:00:00 2001
From: Menithal <menithal@norteclabs.com>
Date: Mon, 6 Mar 2017 10:55:47 +0200
Subject: [PATCH 46/64] Cleanup and safeguards

- RegistrationPoint can no longer be set for magnetic blocks, will set it to
0.5 on snap.
- Simplified axis lock logic
---
 .../tutorials/entity_scripts/magneticBlock.js | 147 ++++++++++--------
 1 file changed, 80 insertions(+), 67 deletions(-)

diff --git a/scripts/tutorials/entity_scripts/magneticBlock.js b/scripts/tutorials/entity_scripts/magneticBlock.js
index a375025671..911e9c0eb5 100644
--- a/scripts/tutorials/entity_scripts/magneticBlock.js
+++ b/scripts/tutorials/entity_scripts/magneticBlock.js
@@ -12,13 +12,14 @@
 
 (function() {
     const SNAPSOUND_SOURCE = SoundCache.getSound(Script.resolvePath("../../system/assets/sounds/entitySnap.wav?xrs"));
-    // Preload trick for faster playback
     const RANGE_MULTIPLER = 1.5;
+    const MAX_SCALE = 2;
+    const MIN_SCALE = 0.5;
 
-    // Helper for detecting nearby objects
-    function findEntitiesInRange(releasedProperties) {
-        var dimensions = releasedProperties.dimensions;
-        return Entities.findEntities(releasedProperties.position, ((dimensions.x + dimensions.y + dimensions.z) / 3) * RANGE_MULTIPLER);
+    // Helper for detecting nearby objects near entityProperties, with the scale calculated by the dimensions of the object.
+    function findEntitiesInRange(entityProperties) {
+        var dimensions = entityProperties.dimensions;
+        return Entities.findEntities(entityProperties.position, ((dimensions.x + dimensions.y + dimensions.z) / 3) * RANGE_MULTIPLER);
     }
 
     function getNearestValidEntityProperties(releasedProperties) {
@@ -32,7 +33,7 @@
                 var distance = Vec3.distance(releasedProperties.position, entity.position);
                 var scale = releaseSize / Vec3.length(entity.dimensions);
 
-                if (distance < nearest && (scale >= 0.5 && scale <= 2)) {
+                if (distance < nearest && (scale >= MIN_SCALE && scale <= MAX_SCALE)) {
                     nearestEntity = entity;
                     nearest = distance;
                 }
@@ -51,82 +52,94 @@
               Only retrieving userData
             */
             var val = Entities.getEntityProperties(id, ['userData'])
-            var userData = {grabbableKey: {}};
-
+            var userData = {
+                grabbableKey: {}
+            };
+            // Check if existing userData field exists.
             if (val.userData && val.userData.length > 0) {
                 try {
                     userData = JSON.parse(val.userData);
+                    if (!userData.grabbableKey) {
+                        userData.grabbableKey = {}; // If by random change there is no grabbableKey in the userData.
+                    }
                 } catch (e) {
+                    // if user data is not valid json, we will simply overwrite it.
                 }
             }
-            // Object must be triggerable inorder to bind events.
+            // Object must be triggerable inorder to bind releaseGrabEvent
             userData.grabbableKey.grabbable = true;
 
             // Apply the new properties to entity of id
             Entities.editEntity(id, {
                 userData: JSON.stringify(userData)
             });
-            this.held = false;
-            // We will now create a custom binding, to keep the 'current' context as these are callbacks called without context
-            var t = this;
-            this.callbacks = {};
-            this.releaseGrab = function() {
-                var released = Entities.getEntityProperties(id, ["position", "rotation", "dimensions"]);
-                var target = getNearestValidEntityProperties(released);
-                if (target !== null) {
-                    // We found nearest, now lets do the snap calculations
-                    // Plays the snap sound between the two objects.
-                    Audio.playSound(SNAPSOUND_SOURCE, {
-                        volume: 1,
-                        position: Vec3.mix(target.position, released.position, 0.5)
-                    });
-                    // Check Nearest Axis
-                    var difference = Vec3.subtract(released.position, target.position);
-                    var relativeDifference = Vec3.multiplyQbyV(Quat.inverse(target.rotation), difference);
-
-                    var abs = {
-                        x: Math.abs(relativeDifference.x),
-                        y: Math.abs(relativeDifference.y),
-                        z: Math.abs(relativeDifference.z)
-                    };
-                    // Check what value is greater. Simplified.
-                    if (abs.x >= abs.y && abs.x >= abs.z) {
-                        relativeDifference.y = 0;
-                        relativeDifference.z = 0;
-                        if (relativeDifference.x > 0) {
-                            relativeDifference.x = target.dimensions.x / 2 + released.dimensions.x / 2;
-                        } else {
-                            relativeDifference.x = -target.dimensions.x / 2 - released.dimensions.x / 2;
-                        }
-                    } else if (abs.y >= abs.x && abs.y >= abs.z) {
-                        relativeDifference.x = 0;
-                        relativeDifference.z = 0;
-                        if (relativeDifference.y > 0) {
-                            relativeDifference.y = target.dimensions.y / 2 + released.dimensions.y / 2;
-                        } else {
-                            relativeDifference.y = -target.dimensions.y / 2 - released.dimensions.y / 2;
-                        }
-                    } else if (abs.z >= abs.x && abs.z >= abs.y) {
-                        relativeDifference.x = 0;
-                        relativeDifference.y = 0;
-                        if (relativeDifference.z > 0) {
-                            relativeDifference.z = target.dimensions.z / 2 + released.dimensions.z / 2;
-                        } else {
-                            relativeDifference.z = -target.dimensions.z / 2 - released.dimensions.z / 2;
-                        }
-                    }
-                    // Can be expanded upon to work in nearest rotation as well, but was not in spec.
-                    var newPosition = Vec3.multiplyQbyV(target.rotation, relativeDifference);
-                    Entities.editEntity(id, {
-                        rotation: target.rotation,
-                        position: Vec3.sum(target.position, newPosition)
-                    })
-                }
-            }
-
             Script.scriptEnding.connect(function() {
                 Script.removeEventHandler(id, "releaseGrab", this.releaseGrab);
             })
+        },
+        releaseGrab: function(entityId) {
+            // Release grab is called with entityId,
+            var released = Entities.getEntityProperties(entityId, ["position", "rotation", "dimensions"]);
+            var target = getNearestValidEntityProperties(released);
+            if (target !== null) {
+                // We found nearest, now lets do the snap calculations
+                // Plays the snap sound between the two objects.
+                Audio.playSound(SNAPSOUND_SOURCE, {
+                    volume: 1,
+                    position: Vec3.mix(target.position, released.position, 0.5)
+                });
+                // Check Nearest Axis
+                var difference = Vec3.subtract(released.position, target.position);
+                var relativeDifference = Vec3.multiplyQbyV(Quat.inverse(target.rotation), difference);
+
+                var abs = {
+                    x: Math.abs(relativeDifference.x),
+                    y: Math.abs(relativeDifference.y),
+                    z: Math.abs(relativeDifference.z)
+                };
+                // Check what value is greater. and lock down to that axis.
+                var newRelative = {
+                    x: 0,
+                    y: 0,
+                    z: 0
+                }
+                if (abs.x >= abs.y && abs.x >= abs.z) {
+                    newRelative.x = target.dimensions.x / 2 + released.dimensions.x / 2;
+                    if (relativeDifference.x < 0) {
+                        newRelative.x = -newRelative.x;
+                    }
+                } else if (abs.y >= abs.x && abs.y >= abs.z) {
+                    newRelative.y = target.dimensions.y / 2 + released.dimensions.y / 2;
+                    if (relativeDifference.y < 0) {
+                        newRelative.y = -newRelative.y;
+                    }
+                } else if (abs.z >= abs.x && abs.z >= abs.y) {
+                    newRelative.z = target.dimensions.z / 2 + released.dimensions.z / 2;
+                    if (relativeDifference.z < 0) {
+                        newRelative.z = -newRelative.z;
+                    }
+                }
+                // Can be expanded upon to work in nearest 90 degree rotation as well, but was not in spec.
+                var newPosition = Vec3.multiplyQbyV(target.rotation, newRelative);
+                Entities.editEntity(entityId, {
+                    // Script relies on the registrationPoint being at the very center of the object. Thus override.
+                    registrationPoint: {
+                        x: 0.5,
+                        y: 0.5,
+                        z: 0.5
+                    },
+                    rotation: target.rotation,
+                    position: Vec3.sum(target.position, newPosition)
+                });
+                // Script relies on the registrationPoint being at the very center of the object. Thus override.
+                Entities.editEntity(target.id, {
+                    registrationPoint: {
+                        x: 0.5,
+                        y: 0.5,
+                        z: 0.5
+                    }
+                })
+            }
         }
     }
     return new MagneticBlock();

From c9c07c4269bdd2a742b0ece1e1647bec1ce30d33 Mon Sep 17 00:00:00 2001
From: Zach Fox <fox@highfidelity.io>
Date: Fri, 24 Feb 2017 09:38:23 -0800
Subject: [PATCH 47/64] Basically rebase and squash

---
 .../src/avatars/AvatarMixerSlave.cpp          | 24 +++++----
 interface/src/avatar/AvatarManager.cpp        |  2 +-
 libraries/avatars/src/AvatarData.cpp          | 48 ++++++++++++-----
 libraries/avatars/src/AvatarData.h            |  3 +-
 scripts/system/pal.js                         | 52 +++++++------------
 5 files changed, 71 insertions(+), 58 deletions(-)

diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp
index 49b4b1ced4..1c386caab7 100644
--- a/assignment-client/src/avatars/AvatarMixerSlave.cpp
+++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp
@@ -137,14 +137,18 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
         // keep track of the number of other avatar frames skipped
         int numAvatarsWithSkippedFrames = 0;
 
-        // When this is true, the AvatarMixer will send Avatar data to a client about avatars that are not in the view frustrum
-        bool getsOutOfView = nodeData->getRequestsDomainListData();
-
-        // When this is true, the AvatarMixer will send Avatar data to a client about avatars that they've ignored
-        bool getsIgnoredByMe = getsOutOfView;
+        // When this is true, the AvatarMixer will send Avatar data to a client
+        // about avatars they've ignored or that are out of view
+        bool PALIsOpen = nodeData->getRequestsDomainListData();
 
         // When this is true, the AvatarMixer will send Avatar data to a client about avatars that have ignored them
-        bool getsAnyIgnored = getsIgnoredByMe && node->getCanKick();
+        bool getsAnyIgnored = PALIsOpen && node->getCanKick();
+
+        // Increase minimumBytesPerAvatar if the PAL is open or we're gettingAnyIgnored
+        if (PALIsOpen || getsAnyIgnored) {
+            minimumBytesPerAvatar += sizeof(AvatarDataPacket::AvatarGlobalPosition) +
+                sizeof(AvatarDataPacket::AudioLoudness);
+        }
 
         // setup a PacketList for the avatarPackets
         auto avatarPacketList = NLPacketList::create(PacketType::BulkAvatarData);
@@ -222,7 +226,7 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
                     // or that has ignored the viewing node
                     if (!avatarNode->getLinkedData()
                         || avatarNode->getUUID() == node->getUUID()
-                        || (node->isIgnoringNodeWithID(avatarNode->getUUID()) && !getsIgnoredByMe)
+                        || (node->isIgnoringNodeWithID(avatarNode->getUUID()) && !PALIsOpen)
                         || (avatarNode->isIgnoringNodeWithID(node->getUUID()) && !getsAnyIgnored)) {
                         shouldIgnore = true;
                     } else {
@@ -335,9 +339,9 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
             if (overBudget) {
                 overBudgetAvatars++;
                 _stats.overBudgetAvatars++;
-                detail = AvatarData::NoData;
-            } else  if (!isInView && !getsOutOfView) {
-                detail = AvatarData::NoData;
+                detail = (PALIsOpen || getsAnyIgnored) ? AvatarData::PALMinimum : AvatarData::NoData;
+            } else if (!isInView) {
+                detail = (PALIsOpen || getsAnyIgnored) ? AvatarData::PALMinimum : AvatarData::NoData;
                 nodeData->incrementAvatarOutOfView();
             } else {
                 detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO
diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp
index 7417f73102..94ce444416 100644
--- a/interface/src/avatar/AvatarManager.cpp
+++ b/interface/src/avatar/AvatarManager.cpp
@@ -329,7 +329,7 @@ void AvatarManager::removeAvatar(const QUuid& sessionUUID, KillAvatarReason remo
 }
 
 void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) {
-    AvatarHashMap::handleRemovedAvatar(removedAvatar);
+    AvatarHashMap::handleRemovedAvatar(removedAvatar, removalReason);
 
     // removedAvatar is a shared pointer to an AvatarData but we need to get to the derived Avatar
     // class in this context so we can call methods that don't exist at the base class.
diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp
index 8025c680ca..7b33ada89c 100644
--- a/libraries/avatars/src/AvatarData.cpp
+++ b/libraries/avatars/src/AvatarData.cpp
@@ -186,6 +186,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
     bool cullSmallChanges = (dataDetail == CullSmallData);
     bool sendAll = (dataDetail == SendAllData);
     bool sendMinimum = (dataDetail == MinimumData);
+    bool sendPALMinimum = (dataDetail == PALMinimum);
 
     lazyInitHeadData();
 
@@ -222,24 +223,45 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
     auto parentID = getParentID();
 
     bool hasAvatarGlobalPosition = true; // always include global position
-    bool hasAvatarOrientation = sendAll || rotationChangedSince(lastSentTime);
-    bool hasAvatarBoundingBox = sendAll || avatarBoundingBoxChangedSince(lastSentTime);
-    bool hasAvatarScale = sendAll || avatarScaleChangedSince(lastSentTime);
-    bool hasLookAtPosition = sendAll || lookAtPositionChangedSince(lastSentTime);
-    bool hasAudioLoudness = sendAll || audioLoudnessChangedSince(lastSentTime);
-    bool hasSensorToWorldMatrix = sendAll || sensorToWorldMatrixChangedSince(lastSentTime);
-    bool hasAdditionalFlags = sendAll || additionalFlagsChangedSince(lastSentTime);
+    bool hasAvatarOrientation = false;
+    bool hasAvatarBoundingBox = false;
+    bool hasAvatarScale = false;
+    bool hasLookAtPosition = false;
+    bool hasAudioLoudness = false;
+    bool hasSensorToWorldMatrix = false;
+    bool hasAdditionalFlags = false;
 
     // local position, and parent info only apply to avatars that are parented. The local position
     // and the parent info can change independently though, so we track their "changed since"
     // separately
-    bool hasParentInfo = sendAll || parentInfoChangedSince(lastSentTime);
-    bool hasAvatarLocalPosition = hasParent() && (sendAll ||
-        tranlationChangedSince(lastSentTime) ||
-        parentInfoChangedSince(lastSentTime));
+    bool hasParentInfo = false;
+    bool hasAvatarLocalPosition = false;
 
-    bool hasFaceTrackerInfo = !dropFaceTracking && hasFaceTracker() && (sendAll || faceTrackerInfoChangedSince(lastSentTime));
-    bool hasJointData = sendAll || !sendMinimum;
+    bool hasFaceTrackerInfo = false;
+    bool hasJointData = false;
+
+    if (sendPALMinimum) {
+        hasAudioLoudness = true;
+    } else {
+        hasAvatarOrientation = sendAll || rotationChangedSince(lastSentTime);
+        hasAvatarBoundingBox = sendAll || avatarBoundingBoxChangedSince(lastSentTime);
+        hasAvatarScale = sendAll || avatarScaleChangedSince(lastSentTime);
+        hasLookAtPosition = sendAll || lookAtPositionChangedSince(lastSentTime);
+        hasAudioLoudness = sendAll || audioLoudnessChangedSince(lastSentTime);
+        hasSensorToWorldMatrix = sendAll || sensorToWorldMatrixChangedSince(lastSentTime);
+        hasAdditionalFlags = sendAll || additionalFlagsChangedSince(lastSentTime);
+
+        // local position, and parent info only apply to avatars that are parented. The local position
+        // and the parent info can change independently though, so we track their "changed since"
+        // separately
+        hasParentInfo = sendAll || parentInfoChangedSince(lastSentTime);
+        hasAvatarLocalPosition = hasParent() && (sendAll ||
+            tranlationChangedSince(lastSentTime) ||
+            parentInfoChangedSince(lastSentTime));
+
+        hasFaceTrackerInfo = !dropFaceTracking && hasFaceTracker() && (sendAll || faceTrackerInfoChangedSince(lastSentTime));
+        hasJointData = sendAll || !sendMinimum;
+    }
 
     // Leading flags, to indicate how much data is actually included in the packet...
     AvatarDataPacket::HasFlags packetStateFlags =
diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h
index c2240f400f..ac6c2fcbe0 100644
--- a/libraries/avatars/src/AvatarData.h
+++ b/libraries/avatars/src/AvatarData.h
@@ -379,7 +379,8 @@ public:
         MinimumData, 
         CullSmallData,
         IncludeSmallData,
-        SendAllData
+        SendAllData,
+        PALMinimum
     } AvatarDataDetail;
 
     virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail);
diff --git a/scripts/system/pal.js b/scripts/system/pal.js
index 4914cbe34c..f9d0a60f5a 100644
--- a/scripts/system/pal.js
+++ b/scripts/system/pal.js
@@ -526,17 +526,23 @@ var button;
 var buttonName = "PEOPLE";
 var tablet = null;
 
+function onTabletScreenChanged(type, url) {
+    if (type !== "QML" || url !== "../Pal.qml") {
+        off();
+    }
+}
+
 function startup() {
     tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
     button = tablet.addButton({
         text: buttonName,
         icon: "icons/tablet-icons/people-i.svg",
-        activeIcon: "icons/tablet-icons/people-a.svg",
         sortOrder: 7
     });
     tablet.fromQml.connect(fromQml);
     button.clicked.connect(onTabletButtonClicked);
     tablet.screenChanged.connect(onTabletScreenChanged);
+
     Users.usernameFromIDReply.connect(usernameFromIDReply);
     Window.domainChanged.connect(clearLocalQMLDataAndClosePAL);
     Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL);
@@ -567,39 +573,17 @@ function off() {
     Users.requestsDomainListData = false;
 }
 
-var onPalScreen = false;
-var shouldActivateButton = false;
-
 function onTabletButtonClicked() {
-    if (onPalScreen) {
-        // for toolbar-mode: go back to home screen, this will close the window.
-        tablet.gotoHomeScreen();
-    } else {
-        shouldActivateButton = true;
-        tablet.loadQMLSource("../Pal.qml");
-        onPalScreen = true;
-        Users.requestsDomainListData = true;
-        populateUserList();
-        isWired = true;
-        Script.update.connect(updateOverlays);
-        Controller.mousePressEvent.connect(handleMouseEvent);
-        Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
-        triggerMapping.enable();
-        triggerPressMapping.enable();
-        audioTimer = createAudioInterval(conserveResources ? AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS : AUDIO_LEVEL_UPDATE_INTERVAL_MS);
-    }
-}
-
-function onTabletScreenChanged(type, url) {
-    // for toolbar mode: change button to active when window is first openend, false otherwise.
-    button.editProperties({isActive: shouldActivateButton});
-    shouldActivateButton = false;
-    onPalScreen = false;
-
-    // disable sphere overlays when not on pal screen.
-    if (type !== "QML" || url !== "../Pal.qml") {
-        off();
-    }
+    tablet.loadQMLSource("../Pal.qml");
+    Users.requestsDomainListData = true;
+    populateUserList();
+    isWired = true;
+    Script.update.connect(updateOverlays);
+    Controller.mousePressEvent.connect(handleMouseEvent);
+    Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
+    triggerMapping.enable();
+    triggerPressMapping.enable();
+    audioTimer = createAudioInterval(conserveResources ? AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS : AUDIO_LEVEL_UPDATE_INTERVAL_MS);
 }
 
 //
@@ -699,12 +683,14 @@ function shutdown() {
     button.clicked.disconnect(onTabletButtonClicked);
     tablet.removeButton(button);
     tablet.screenChanged.disconnect(onTabletScreenChanged);
+
     Users.usernameFromIDReply.disconnect(usernameFromIDReply);
     Window.domainChanged.disconnect(clearLocalQMLDataAndClosePAL);
     Window.domainConnectionRefused.disconnect(clearLocalQMLDataAndClosePAL);
     Messages.subscribe(CHANNEL);
     Messages.messageReceived.disconnect(receiveMessage);
     Users.avatarDisconnected.disconnect(avatarDisconnected);
+
     off();
 }
 

From b927472e4e272846c59e8ab0cbc1904a48fa9540 Mon Sep 17 00:00:00 2001
From: Zach Fox <fox@highfidelity.io>
Date: Fri, 24 Feb 2017 11:25:04 -0800
Subject: [PATCH 48/64] Fixup after rebase

---
 scripts/system/pal.js | 52 +++++++++++++++++++++++++++----------------
 1 file changed, 33 insertions(+), 19 deletions(-)

diff --git a/scripts/system/pal.js b/scripts/system/pal.js
index f9d0a60f5a..4914cbe34c 100644
--- a/scripts/system/pal.js
+++ b/scripts/system/pal.js
@@ -526,23 +526,17 @@ var button;
 var buttonName = "PEOPLE";
 var tablet = null;
 
-function onTabletScreenChanged(type, url) {
-    if (type !== "QML" || url !== "../Pal.qml") {
-        off();
-    }
-}
-
 function startup() {
     tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
     button = tablet.addButton({
         text: buttonName,
         icon: "icons/tablet-icons/people-i.svg",
+        activeIcon: "icons/tablet-icons/people-a.svg",
         sortOrder: 7
     });
     tablet.fromQml.connect(fromQml);
     button.clicked.connect(onTabletButtonClicked);
     tablet.screenChanged.connect(onTabletScreenChanged);
-
     Users.usernameFromIDReply.connect(usernameFromIDReply);
     Window.domainChanged.connect(clearLocalQMLDataAndClosePAL);
     Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL);
@@ -573,17 +567,39 @@ function off() {
     Users.requestsDomainListData = false;
 }
 
+var onPalScreen = false;
+var shouldActivateButton = false;
+
 function onTabletButtonClicked() {
-    tablet.loadQMLSource("../Pal.qml");
-    Users.requestsDomainListData = true;
-    populateUserList();
-    isWired = true;
-    Script.update.connect(updateOverlays);
-    Controller.mousePressEvent.connect(handleMouseEvent);
-    Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
-    triggerMapping.enable();
-    triggerPressMapping.enable();
-    audioTimer = createAudioInterval(conserveResources ? AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS : AUDIO_LEVEL_UPDATE_INTERVAL_MS);
+    if (onPalScreen) {
+        // for toolbar-mode: go back to home screen, this will close the window.
+        tablet.gotoHomeScreen();
+    } else {
+        shouldActivateButton = true;
+        tablet.loadQMLSource("../Pal.qml");
+        onPalScreen = true;
+        Users.requestsDomainListData = true;
+        populateUserList();
+        isWired = true;
+        Script.update.connect(updateOverlays);
+        Controller.mousePressEvent.connect(handleMouseEvent);
+        Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
+        triggerMapping.enable();
+        triggerPressMapping.enable();
+        audioTimer = createAudioInterval(conserveResources ? AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS : AUDIO_LEVEL_UPDATE_INTERVAL_MS);
+    }
+}
+
+function onTabletScreenChanged(type, url) {
+    // for toolbar mode: change button to active when window is first openend, false otherwise.
+    button.editProperties({isActive: shouldActivateButton});
+    shouldActivateButton = false;
+    onPalScreen = false;
+
+    // disable sphere overlays when not on pal screen.
+    if (type !== "QML" || url !== "../Pal.qml") {
+        off();
+    }
 }
 
 //
@@ -683,14 +699,12 @@ function shutdown() {
     button.clicked.disconnect(onTabletButtonClicked);
     tablet.removeButton(button);
     tablet.screenChanged.disconnect(onTabletScreenChanged);
-
     Users.usernameFromIDReply.disconnect(usernameFromIDReply);
     Window.domainChanged.disconnect(clearLocalQMLDataAndClosePAL);
     Window.domainConnectionRefused.disconnect(clearLocalQMLDataAndClosePAL);
     Messages.subscribe(CHANNEL);
     Messages.messageReceived.disconnect(receiveMessage);
     Users.avatarDisconnected.disconnect(avatarDisconnected);
-
     off();
 }
 

From 81ce5cffcd6eb53dff514b8a41acca148acb62a0 Mon Sep 17 00:00:00 2001
From: Zach Fox <fox@highfidelity.io>
Date: Fri, 24 Feb 2017 11:47:42 -0800
Subject: [PATCH 49/64] Force-send ID packets when PAL is open

---
 assignment-client/src/avatars/AvatarMixerSlave.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp
index 1c386caab7..e6ca5e49df 100644
--- a/assignment-client/src/avatars/AvatarMixerSlave.cpp
+++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp
@@ -311,7 +311,8 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
             const AvatarMixerClientData* otherNodeData = reinterpret_cast<const AvatarMixerClientData*>(otherNode->getLinkedData());
 
             // make sure we send out identity packets to and from new arrivals.
-            bool forceSend = !nodeData->checkAndSetHasReceivedFirstPacketsFrom(otherNode->getUUID());
+            // Also make sure we send identity packets if the PAL is open.
+            bool forceSend = !nodeData->checkAndSetHasReceivedFirstPacketsFrom(otherNode->getUUID()) || (PALIsOpen || getsAnyIgnored);
 
             // FIXME - this clause seems suspicious "... || otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp ..."
             if (!overBudget

From 4025601c2bfc8e9510585d7fcdb8ede57a80545e Mon Sep 17 00:00:00 2001
From: Zach Fox <fox@highfidelity.io>
Date: Fri, 24 Feb 2017 14:28:29 -0800
Subject: [PATCH 50/64] Force send ID even when 'overBudget'

---
 assignment-client/src/avatars/AvatarMixerSlave.cpp | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp
index e6ca5e49df..37406675b9 100644
--- a/assignment-client/src/avatars/AvatarMixerSlave.cpp
+++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp
@@ -311,15 +311,16 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
             const AvatarMixerClientData* otherNodeData = reinterpret_cast<const AvatarMixerClientData*>(otherNode->getLinkedData());
 
             // make sure we send out identity packets to and from new arrivals.
-            // Also make sure we send identity packets if the PAL is open.
             bool forceSend = !nodeData->checkAndSetHasReceivedFirstPacketsFrom(otherNode->getUUID()) || (PALIsOpen || getsAnyIgnored);
 
             // FIXME - this clause seems suspicious "... || otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp ..."
-            if (!overBudget
+            if ((!overBudget
                 && otherNodeData->getIdentityChangeTimestamp().time_since_epoch().count() > 0
                 && (forceSend
                 || otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp
-                || distribution(generator) < IDENTITY_SEND_PROBABILITY)) {
+                || distribution(generator) < IDENTITY_SEND_PROBABILITY)) ||
+                // Also make sure we send identity packets if the PAL is open.
+                (PALIsOpen || getsAnyIgnored)) {
 
                 identityBytesSent += sendIdentityPacket(otherNodeData, node);
             }

From 9969d422d6d07eeeea29b2c3a925934edfa7dcf4 Mon Sep 17 00:00:00 2001
From: Zach Fox <fox@highfidelity.io>
Date: Fri, 24 Feb 2017 15:38:30 -0800
Subject: [PATCH 51/64] Cleanup

---
 assignment-client/src/avatars/AvatarMixerSlave.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp
index 37406675b9..88b363b73a 100644
--- a/assignment-client/src/avatars/AvatarMixerSlave.cpp
+++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp
@@ -311,7 +311,7 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
             const AvatarMixerClientData* otherNodeData = reinterpret_cast<const AvatarMixerClientData*>(otherNode->getLinkedData());
 
             // make sure we send out identity packets to and from new arrivals.
-            bool forceSend = !nodeData->checkAndSetHasReceivedFirstPacketsFrom(otherNode->getUUID()) || (PALIsOpen || getsAnyIgnored);
+            bool forceSend = !nodeData->checkAndSetHasReceivedFirstPacketsFrom(otherNode->getUUID());
 
             // FIXME - this clause seems suspicious "... || otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp ..."
             if ((!overBudget

From cfb8534d71dd3b1da32a11e02a3ba1374d0add9c Mon Sep 17 00:00:00 2001
From: Zach Fox <fox@highfidelity.io>
Date: Fri, 24 Feb 2017 17:00:57 -0800
Subject: [PATCH 52/64] Comment

---
 libraries/avatars/src/AvatarData.cpp | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp
index 7b33ada89c..06df75d451 100644
--- a/libraries/avatars/src/AvatarData.cpp
+++ b/libraries/avatars/src/AvatarData.cpp
@@ -250,10 +250,6 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
         hasAudioLoudness = sendAll || audioLoudnessChangedSince(lastSentTime);
         hasSensorToWorldMatrix = sendAll || sensorToWorldMatrixChangedSince(lastSentTime);
         hasAdditionalFlags = sendAll || additionalFlagsChangedSince(lastSentTime);
-
-        // local position, and parent info only apply to avatars that are parented. The local position
-        // and the parent info can change independently though, so we track their "changed since"
-        // separately
         hasParentInfo = sendAll || parentInfoChangedSince(lastSentTime);
         hasAvatarLocalPosition = hasParent() && (sendAll ||
             tranlationChangedSince(lastSentTime) ||

From 718ecea404459d9eb779ada40c5b99e1103fc7b2 Mon Sep 17 00:00:00 2001
From: Zach Fox <fox@highfidelity.io>
Date: Mon, 27 Feb 2017 16:10:53 -0800
Subject: [PATCH 53/64] Potential non-spammy solution using pseudo-state

---
 .../src/avatars/AvatarMixerSlave.cpp          | 31 ++++++++++++-------
 .../src/avatars/AvatarMixerSlave.h            | 12 +++++++
 libraries/avatars/src/AvatarData.h            |  4 +--
 3 files changed, 33 insertions(+), 14 deletions(-)

diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp
index 88b363b73a..15cf89754e 100644
--- a/assignment-client/src/avatars/AvatarMixerSlave.cpp
+++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp
@@ -80,16 +80,6 @@ int AvatarMixerSlave::sendIdentityPacket(const AvatarMixerClientData* nodeData,
 
 static const int AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 45;
 
-// FIXME - There is some old logic (unchanged as of 2/17/17) that randomly decides to send an identity
-// packet. That logic had the following comment about the constants it uses...
-//
-//         An 80% chance of sending a identity packet within a 5 second interval.
-//         assuming 60 htz update rate.
-//
-// Assuming the calculation of the constant is in fact correct for 80% and 60hz and 5 seconds (an assumption
-// that I have not verified) then the constant is definitely wrong now, since we send at 45hz.
-const float IDENTITY_SEND_PROBABILITY = 1.0f / 187.0f;
-
 void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
     quint64 start = usecTimestampNow();
 
@@ -150,6 +140,23 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
                 sizeof(AvatarDataPacket::AudioLoudness);
         }
 
+        if (PALIsOpen) {
+            if (_identitySendProbability == DEFAULT_IDENTITY_SEND_PROBABILITY)
+            {
+                // The client has just opened the PAL. Force all identity packets to be sent to
+                // this client.
+                _identitySendProbability = 1.0f;
+            } else {
+                // The user recently opened the PAL, but we've already gone through the above conditional.
+                // We want to receive identity updates more often than default when the PAL is open
+                // to be more confident that the user will see the most up-to-date information in the PAL.
+                _identitySendProbability = DEFAULT_IDENTITY_SEND_PROBABILITY * 2;
+            }
+        } else {
+            // If the PAL is closed, reset the identitySendProbability to the default.
+            _identitySendProbability = DEFAULT_IDENTITY_SEND_PROBABILITY;
+        }
+
         // setup a PacketList for the avatarPackets
         auto avatarPacketList = NLPacketList::create(PacketType::BulkAvatarData);
 
@@ -318,9 +325,9 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
                 && otherNodeData->getIdentityChangeTimestamp().time_since_epoch().count() > 0
                 && (forceSend
                 || otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp
-                || distribution(generator) < IDENTITY_SEND_PROBABILITY)) ||
+                || distribution(generator) < _identitySendProbability)) ||
                 // Also make sure we send identity packets if the PAL is open.
-                (PALIsOpen || getsAnyIgnored)) {
+                ((PALIsOpen || getsAnyIgnored) && distribution(generator) < _identitySendProbability)) {
 
                 identityBytesSent += sendIdentityPacket(otherNodeData, node);
             }
diff --git a/assignment-client/src/avatars/AvatarMixerSlave.h b/assignment-client/src/avatars/AvatarMixerSlave.h
index 04141d9d72..2fa5fa4484 100644
--- a/assignment-client/src/avatars/AvatarMixerSlave.h
+++ b/assignment-client/src/avatars/AvatarMixerSlave.h
@@ -101,6 +101,18 @@ private:
     float _maxKbpsPerNode { 0.0f };
     float _throttlingRatio { 0.0f };
 
+
+    // FIXME - There is some old logic (unchanged as of 2/17/17) that randomly decides to send an identity
+    // packet. That logic had the following comment about the constants it uses...
+    //
+    //         An 80% chance of sending a identity packet within a 5 second interval.
+    //         assuming 60 htz update rate.
+    //
+    // Assuming the calculation of the constant is in fact correct for 80% and 60hz and 5 seconds (an assumption
+    // that I have not verified) then the constant is definitely wrong now, since we send at 45hz.
+    const float DEFAULT_IDENTITY_SEND_PROBABILITY = 1.0f / 187.0f;
+    float _identitySendProbability = DEFAULT_IDENTITY_SEND_PROBABILITY;
+
     AvatarMixerSlaveStats _stats;
 };
 
diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h
index ac6c2fcbe0..f0759aedbd 100644
--- a/libraries/avatars/src/AvatarData.h
+++ b/libraries/avatars/src/AvatarData.h
@@ -376,11 +376,11 @@ public:
 
     typedef enum { 
         NoData,
+        PALMinimum,
         MinimumData, 
         CullSmallData,
         IncludeSmallData,
-        SendAllData,
-        PALMinimum
+        SendAllData
     } AvatarDataDetail;
 
     virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail);

From 8bdbbd4b25aa2cb8d943cacf3724a4bb13a95224 Mon Sep 17 00:00:00 2001
From: Zach Fox <fox@highfidelity.io>
Date: Wed, 1 Mar 2017 09:07:27 -0800
Subject: [PATCH 54/64] CR feedback 1

---
 assignment-client/src/avatars/AvatarMixerSlave.cpp | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)

diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp
index 15cf89754e..49353fd88f 100644
--- a/assignment-client/src/avatars/AvatarMixerSlave.cpp
+++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp
@@ -134,15 +134,14 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
         // When this is true, the AvatarMixer will send Avatar data to a client about avatars that have ignored them
         bool getsAnyIgnored = PALIsOpen && node->getCanKick();
 
-        // Increase minimumBytesPerAvatar if the PAL is open or we're gettingAnyIgnored
-        if (PALIsOpen || getsAnyIgnored) {
+        // Increase minimumBytesPerAvatar if the PAL is open
+        if (PALIsOpen) {
             minimumBytesPerAvatar += sizeof(AvatarDataPacket::AvatarGlobalPosition) +
                 sizeof(AvatarDataPacket::AudioLoudness);
         }
 
         if (PALIsOpen) {
-            if (_identitySendProbability == DEFAULT_IDENTITY_SEND_PROBABILITY)
-            {
+            if (_identitySendProbability == DEFAULT_IDENTITY_SEND_PROBABILITY) {
                 // The client has just opened the PAL. Force all identity packets to be sent to
                 // this client.
                 _identitySendProbability = 1.0f;
@@ -327,7 +326,7 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
                 || otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp
                 || distribution(generator) < _identitySendProbability)) ||
                 // Also make sure we send identity packets if the PAL is open.
-                ((PALIsOpen || getsAnyIgnored) && distribution(generator) < _identitySendProbability)) {
+                (PALIsOpen && distribution(generator) < _identitySendProbability)) {
 
                 identityBytesSent += sendIdentityPacket(otherNodeData, node);
             }
@@ -348,9 +347,9 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
             if (overBudget) {
                 overBudgetAvatars++;
                 _stats.overBudgetAvatars++;
-                detail = (PALIsOpen || getsAnyIgnored) ? AvatarData::PALMinimum : AvatarData::NoData;
+                detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::NoData;
             } else if (!isInView) {
-                detail = (PALIsOpen || getsAnyIgnored) ? AvatarData::PALMinimum : AvatarData::NoData;
+                detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::NoData;
                 nodeData->incrementAvatarOutOfView();
             } else {
                 detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO

From fda87b3a531c95856a949d5df1d9e4032883f20a Mon Sep 17 00:00:00 2001
From: Zach Fox <fox@highfidelity.io>
Date: Wed, 1 Mar 2017 09:09:41 -0800
Subject: [PATCH 55/64] Quick cleanup

---
 assignment-client/src/avatars/AvatarMixerSlave.cpp | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp
index 49353fd88f..47efb39c3c 100644
--- a/assignment-client/src/avatars/AvatarMixerSlave.cpp
+++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp
@@ -134,13 +134,10 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
         // When this is true, the AvatarMixer will send Avatar data to a client about avatars that have ignored them
         bool getsAnyIgnored = PALIsOpen && node->getCanKick();
 
-        // Increase minimumBytesPerAvatar if the PAL is open
         if (PALIsOpen) {
+            // Increase minimumBytesPerAvatar if the PAL is open
             minimumBytesPerAvatar += sizeof(AvatarDataPacket::AvatarGlobalPosition) +
                 sizeof(AvatarDataPacket::AudioLoudness);
-        }
-
-        if (PALIsOpen) {
             if (_identitySendProbability == DEFAULT_IDENTITY_SEND_PROBABILITY) {
                 // The client has just opened the PAL. Force all identity packets to be sent to
                 // this client.

From 5fa5e6e0a74eabcfe1ab99ecd216bde4734044fc Mon Sep 17 00:00:00 2001
From: Zach Fox <fox@highfidelity.io>
Date: Wed, 1 Mar 2017 14:27:50 -0800
Subject: [PATCH 56/64] First pass at implementing Brad's simplification ideas

---
 .../src/avatars/AvatarMixerClientData.cpp     | 10 -------
 .../src/avatars/AvatarMixerClientData.h       |  9 ++----
 .../src/avatars/AvatarMixerSlave.cpp          | 28 ++-----------------
 .../src/avatars/AvatarMixerSlave.h            | 12 --------
 4 files changed, 6 insertions(+), 53 deletions(-)

diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp
index 717e14535f..43b8816111 100644
--- a/assignment-client/src/avatars/AvatarMixerClientData.cpp
+++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp
@@ -65,15 +65,6 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message) {
     // compute the offset to the data payload
     return _avatar->parseDataFromBuffer(message.readWithoutCopy(message.getBytesLeftToRead()));
 }
-
-bool AvatarMixerClientData::checkAndSetHasReceivedFirstPacketsFrom(const QUuid& uuid) {
-    if (_hasReceivedFirstPacketsFrom.find(uuid) == _hasReceivedFirstPacketsFrom.end()) {
-        _hasReceivedFirstPacketsFrom.insert(uuid);
-        return false;
-    }
-    return true;
-}
-
 uint64_t AvatarMixerClientData::getLastBroadcastTime(const QUuid& nodeUUID) const {
     // return the matching PacketSequenceNumber, or the default if we don't have it
     auto nodeMatch = _lastBroadcastTimes.find(nodeUUID);
@@ -103,7 +94,6 @@ void AvatarMixerClientData::ignoreOther(SharedNodePointer self, SharedNodePointe
             killPacket->writePrimitive(KillAvatarReason::YourAvatarEnteredTheirBubble);
         }
         DependencyManager::get<NodeList>()->sendUnreliablePacket(*killPacket, *self);
-        _hasReceivedFirstPacketsFrom.erase(other->getUUID());
     }
 }
 
diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h
index 51b8d692e2..1449005246 100644
--- a/assignment-client/src/avatars/AvatarMixerClientData.h
+++ b/assignment-client/src/avatars/AvatarMixerClientData.h
@@ -45,8 +45,6 @@ public:
     const AvatarData* getConstAvatarData() const { return _avatar.get(); }
     AvatarSharedPointer getAvatarSharedPointer() const { return _avatar; }
 
-    bool checkAndSetHasReceivedFirstPacketsFrom(const QUuid& uuid);
-
     uint16_t getLastBroadcastSequenceNumber(const QUuid& nodeUUID) const;
     void setLastBroadcastSequenceNumber(const QUuid& nodeUUID, uint16_t sequenceNumber)
         { _lastBroadcastSequenceNumbers[nodeUUID] = sequenceNumber; }
@@ -63,8 +61,8 @@ public:
 
     uint16_t getLastReceivedSequenceNumber() const { return _lastReceivedSequenceNumber; }
 
-    HRCTime getIdentityChangeTimestamp() const { return _identityChangeTimestamp; }
-    void flagIdentityChange() { _identityChangeTimestamp = p_high_resolution_clock::now(); }
+    uint64_t getIdentityChangeTimestamp() const { return _identityChangeTimestamp; }
+    void flagIdentityChange() { _identityChangeTimestamp = usecTimestampNow(); }
     bool getAvatarSessionDisplayNameMustChange() const { return _avatarSessionDisplayNameMustChange; }
     void setAvatarSessionDisplayNameMustChange(bool set = true) { _avatarSessionDisplayNameMustChange = set; }
 
@@ -139,7 +137,6 @@ private:
 
     uint16_t _lastReceivedSequenceNumber { 0 };
     std::unordered_map<QUuid, uint16_t> _lastBroadcastSequenceNumbers;
-    std::unordered_set<QUuid> _hasReceivedFirstPacketsFrom;
     std::unordered_map<QUuid, uint64_t> _lastBroadcastTimes;
 
     // this is a map of the last time we encoded an "other" avatar for
@@ -147,7 +144,7 @@ private:
     std::unordered_map<QUuid, quint64> _lastOtherAvatarEncodeTime;
     std::unordered_map<QUuid, QVector<JointData>> _lastOtherAvatarSentJoints;
 
-    HRCTime _identityChangeTimestamp;
+    uint64_t _identityChangeTimestamp;
     bool _avatarSessionDisplayNameMustChange{ false };
 
     int _numAvatarsSentLastFrame = 0;
diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp
index 47efb39c3c..60dfad7687 100644
--- a/assignment-client/src/avatars/AvatarMixerSlave.cpp
+++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp
@@ -138,19 +138,6 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
             // Increase minimumBytesPerAvatar if the PAL is open
             minimumBytesPerAvatar += sizeof(AvatarDataPacket::AvatarGlobalPosition) +
                 sizeof(AvatarDataPacket::AudioLoudness);
-            if (_identitySendProbability == DEFAULT_IDENTITY_SEND_PROBABILITY) {
-                // The client has just opened the PAL. Force all identity packets to be sent to
-                // this client.
-                _identitySendProbability = 1.0f;
-            } else {
-                // The user recently opened the PAL, but we've already gone through the above conditional.
-                // We want to receive identity updates more often than default when the PAL is open
-                // to be more confident that the user will see the most up-to-date information in the PAL.
-                _identitySendProbability = DEFAULT_IDENTITY_SEND_PROBABILITY * 2;
-            }
-        } else {
-            // If the PAL is closed, reset the identitySendProbability to the default.
-            _identitySendProbability = DEFAULT_IDENTITY_SEND_PROBABILITY;
         }
 
         // setup a PacketList for the avatarPackets
@@ -313,18 +300,9 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
 
             const AvatarMixerClientData* otherNodeData = reinterpret_cast<const AvatarMixerClientData*>(otherNode->getLinkedData());
 
-            // make sure we send out identity packets to and from new arrivals.
-            bool forceSend = !nodeData->checkAndSetHasReceivedFirstPacketsFrom(otherNode->getUUID());
-
-            // FIXME - this clause seems suspicious "... || otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp ..."
-            if ((!overBudget
-                && otherNodeData->getIdentityChangeTimestamp().time_since_epoch().count() > 0
-                && (forceSend
-                || otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp
-                || distribution(generator) < _identitySendProbability)) ||
-                // Also make sure we send identity packets if the PAL is open.
-                (PALIsOpen && distribution(generator) < _identitySendProbability)) {
-
+            // If the time that the mixer sent AVATAR DATA about Avatar B to Avatar A is BEFORE OR EQUAL TO
+            // the time that Avatar B flagged an IDENTITY DATA change, send IDENTITY DATA about Avatar B to Avatar A.
+            if (nodeData->getLastBroadcastTime(otherNode->getUUID()) <= otherNodeData->getIdentityChangeTimestamp()) {
                 identityBytesSent += sendIdentityPacket(otherNodeData, node);
             }
 
diff --git a/assignment-client/src/avatars/AvatarMixerSlave.h b/assignment-client/src/avatars/AvatarMixerSlave.h
index 2fa5fa4484..04141d9d72 100644
--- a/assignment-client/src/avatars/AvatarMixerSlave.h
+++ b/assignment-client/src/avatars/AvatarMixerSlave.h
@@ -101,18 +101,6 @@ private:
     float _maxKbpsPerNode { 0.0f };
     float _throttlingRatio { 0.0f };
 
-
-    // FIXME - There is some old logic (unchanged as of 2/17/17) that randomly decides to send an identity
-    // packet. That logic had the following comment about the constants it uses...
-    //
-    //         An 80% chance of sending a identity packet within a 5 second interval.
-    //         assuming 60 htz update rate.
-    //
-    // Assuming the calculation of the constant is in fact correct for 80% and 60hz and 5 seconds (an assumption
-    // that I have not verified) then the constant is definitely wrong now, since we send at 45hz.
-    const float DEFAULT_IDENTITY_SEND_PROBABILITY = 1.0f / 187.0f;
-    float _identitySendProbability = DEFAULT_IDENTITY_SEND_PROBABILITY;
-
     AvatarMixerSlaveStats _stats;
 };
 

From 5418a7c2304c2773fb09584070b28e8707db13f3 Mon Sep 17 00:00:00 2001
From: Zach Fox <fox@highfidelity.io>
Date: Wed, 1 Mar 2017 16:54:53 -0800
Subject: [PATCH 57/64] Important addition before rebase

---
 assignment-client/src/avatars/AvatarMixerClientData.cpp | 1 +
 1 file changed, 1 insertion(+)

diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp
index 43b8816111..0df3edbd11 100644
--- a/assignment-client/src/avatars/AvatarMixerClientData.cpp
+++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp
@@ -94,6 +94,7 @@ void AvatarMixerClientData::ignoreOther(SharedNodePointer self, SharedNodePointe
             killPacket->writePrimitive(KillAvatarReason::YourAvatarEnteredTheirBubble);
         }
         DependencyManager::get<NodeList>()->sendUnreliablePacket(*killPacket, *self);
+        setLastBroadcastTime(other->getUUID(), 0);
     }
 }
 

From 685bd95105f96b828ce62292b8b83b3291c6e83a Mon Sep 17 00:00:00 2001
From: Zach Fox <fox@highfidelity.io>
Date: Fri, 3 Mar 2017 11:21:55 -0800
Subject: [PATCH 58/64] I think I'm done. Not straightforward!

---
 assignment-client/src/avatars/AvatarMixer.cpp | 26 ++++++++++++++++++-
 .../src/avatars/AvatarMixerClientData.cpp     |  2 +-
 .../src/avatars/AvatarMixerSlave.cpp          |  3 ++-
 3 files changed, 28 insertions(+), 3 deletions(-)

diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp
index 0f6863f9ae..1d1a1a57a8 100644
--- a/assignment-client/src/avatars/AvatarMixer.cpp
+++ b/assignment-client/src/avatars/AvatarMixer.cpp
@@ -409,7 +409,31 @@ void AvatarMixer::handleKillAvatarPacket(QSharedPointer<ReceivedMessage> message
 
 void AvatarMixer::handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
     auto start = usecTimestampNow();
-    senderNode->parseIgnoreRequestMessage(message);
+    auto nodeList = DependencyManager::get<NodeList>();
+    AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(senderNode->getLinkedData());
+    bool addToIgnore;
+    message->readPrimitive(&addToIgnore);
+    while (message->getBytesLeftToRead()) {
+        // parse out the UUID being ignored from the packet
+        QUuid ignoredUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
+        // Reset the lastBroadcastTime for the ignored avatar to 0
+        // so the AvatarMixer knows it'll have to send identity data about the ignored avatar
+        // to the ignorer if the ignorer unignores.
+        nodeData->setLastBroadcastTime(ignoredUUID, 0);
+
+        // Reset the lastBroadcastTime for the ignorer (FROM THE PERSPECTIVE OF THE IGNORED) to 0
+        // so the AvatarMixer knows it'll have to send identity data about the ignorer
+        // to the ignored if the ignorer unignores.
+        auto ignoredNode = nodeList->nodeWithUUID(ignoredUUID);
+        AvatarMixerClientData* ignoredNodeData = reinterpret_cast<AvatarMixerClientData*>(ignoredNode->getLinkedData());
+        ignoredNodeData->setLastBroadcastTime(senderNode->getUUID(), 0);
+
+        if (addToIgnore) {
+            senderNode->addIgnoredNode(ignoredUUID);
+        } else {
+            senderNode->removeIgnoredNode(ignoredUUID);
+        }
+    }
     auto end = usecTimestampNow();
     _handleNodeIgnoreRequestPacketElapsedTime += (end - start);
 }
diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp
index 0df3edbd11..15a7f50fa3 100644
--- a/assignment-client/src/avatars/AvatarMixerClientData.cpp
+++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp
@@ -93,8 +93,8 @@ void AvatarMixerClientData::ignoreOther(SharedNodePointer self, SharedNodePointe
         } else {
             killPacket->writePrimitive(KillAvatarReason::YourAvatarEnteredTheirBubble);
         }
-        DependencyManager::get<NodeList>()->sendUnreliablePacket(*killPacket, *self);
         setLastBroadcastTime(other->getUUID(), 0);
+        DependencyManager::get<NodeList>()->sendUnreliablePacket(*killPacket, *self);
     }
 }
 
diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp
index 60dfad7687..05de209e81 100644
--- a/assignment-client/src/avatars/AvatarMixerSlave.cpp
+++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp
@@ -222,7 +222,8 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
                     } else {
 
                         // Check to see if the space bubble is enabled
-                        if (node->isIgnoreRadiusEnabled() || avatarNode->isIgnoreRadiusEnabled()) {
+                        // Don't bother with these checks if the other avatar has their bubble enabled and we're gettingAnyIgnored
+                        if (node->isIgnoreRadiusEnabled() || (avatarNode->isIgnoreRadiusEnabled() && !getsAnyIgnored)) {
 
                             // Define the scale of the box for the current other node
                             glm::vec3 otherNodeBoxScale = (avatarNodeData->getPosition() - avatarNodeData->getGlobalBoundingBoxCorner()) * 2.0f;

From 0000b8fb2159a9b489fa61cd56f6b21ed9fa021e Mon Sep 17 00:00:00 2001
From: Zach Fox <fox@highfidelity.io>
Date: Fri, 3 Mar 2017 17:01:57 -0800
Subject: [PATCH 59/64] OMG I think it's actually fully working

---
 assignment-client/src/avatars/AvatarMixer.cpp | 22 +++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp
index 1d1a1a57a8..c10a616818 100644
--- a/assignment-client/src/avatars/AvatarMixer.cpp
+++ b/assignment-client/src/avatars/AvatarMixer.cpp
@@ -365,6 +365,28 @@ void AvatarMixer::handleRequestsDomainListDataPacket(QSharedPointer<ReceivedMess
             message->readPrimitive(&isRequesting);
             nodeData->setRequestsDomainListData(isRequesting);
             qCDebug(avatars) << "node" << nodeData->getNodeID() << "requestsDomainListData" << isRequesting;
+
+            // If we just opened the PAL...
+            if (isRequesting) {
+                // For each node in the NodeList...
+                auto nodeList = DependencyManager::get<NodeList>();
+                nodeList->eachMatchingNode(
+                    // Discover the valid nodes we're ignoring...
+                    [&](const SharedNodePointer& node)->bool {
+                    if (node->getUUID() != senderNode->getUUID() &&
+                        (nodeData->isRadiusIgnoring(node->getUUID()) ||
+                        senderNode->isIgnoringNodeWithID(node->getUUID()))) {
+                        return true;
+                    }
+                    return false;
+                },
+                    // ...For those nodes, reset the lastBroadcastTime to 0
+                    // so that the AvatarMixer will send Identity data to us
+                    [&](const SharedNodePointer& node) {
+                    nodeData->setLastBroadcastTime(node->getUUID(), 0);
+                }
+                );
+            }
         }
     }
     auto end = usecTimestampNow();

From 19a31d76304393f5488196a3c70bf6475eaf410b Mon Sep 17 00:00:00 2001
From: Seth Alves <seth.alves@gmail.com>
Date: Mon, 6 Mar 2017 10:15:53 -0800
Subject: [PATCH 60/64] don't automatically unhook overlays from hands unless
 they were grabbable overlays

---
 scripts/system/controllers/handControllerGrab.js | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)

diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js
index aa0a3d9abd..febeea0c8e 100644
--- a/scripts/system/controllers/handControllerGrab.js
+++ b/scripts/system/controllers/handControllerGrab.js
@@ -3570,13 +3570,21 @@ function MyController(hand) {
                     }
                     _this.previouslyUnhooked[childID] = now;
 
-                    // we don't know if it's an entity or an overlay
+                    if (Overlays.getProperty(childID, "grabbable")) {
+                        // only auto-unhook overlays that were flagged as grabbable.  this avoids unhooking overlays
+                        // used in tutorial.
+                        Overlays.editOverlay(childID, {
+                            parentID: previousParentID,
+                            parentJointIndex: previousParentJointIndex
+                        });
+                    }
                     Entities.editEntity(childID, { parentID: previousParentID, parentJointIndex: previousParentJointIndex });
-                    Overlays.editOverlay(childID, { parentID: previousParentID, parentJointIndex: previousParentJointIndex });
 
                 } else {
                     Entities.editEntity(childID, { parentID: NULL_UUID });
-                    Overlays.editOverlay(childID, { parentID: NULL_UUID });
+                    if (Overlays.getProperty(childID, "grabbable")) {
+                        Overlays.editOverlay(childID, { parentID: NULL_UUID });
+                    }
                 }
             }
         });

From d50a0e33a99b6cb0181186f8fae1dd496dd1f3f2 Mon Sep 17 00:00:00 2001
From: Menithal <menithal@norteclabs.com>
Date: Mon, 6 Mar 2017 21:07:16 +0200
Subject: [PATCH 61/64] CR. Run through Lint ECMAScript 5 standard

Meaning all const are out, non-variable definitions are out, etc
---
 .../tutorials/entity_scripts/magneticBlock.js | 37 +++++++++++--------
 scripts/tutorials/makeBlocks.js               | 32 ++++++++++------
 2 files changed, 41 insertions(+), 28 deletions(-)

diff --git a/scripts/tutorials/entity_scripts/magneticBlock.js b/scripts/tutorials/entity_scripts/magneticBlock.js
index 911e9c0eb5..7771a3668d 100644
--- a/scripts/tutorials/entity_scripts/magneticBlock.js
+++ b/scripts/tutorials/entity_scripts/magneticBlock.js
@@ -11,21 +11,23 @@
 //  Makes the entity the script is bound to connect to nearby, similarly sized entities, like a magnet.
 
 (function() {
-    const SNAPSOUND_SOURCE = SoundCache.getSound(Script.resolvePath("../../system/assets/sounds/entitySnap.wav?xrs"));
-    const RANGE_MULTIPLER = 1.5;
-    const MAX_SCALE = 2;
-    const MIN_SCALE = 0.5;
+    var SNAPSOUND_SOURCE = SoundCache.getSound(Script.resolvePath("../../system/assets/sounds/entitySnap.wav?xrs"));
+    var RANGE_MULTIPLER = 1.5;
+    var MAX_SCALE = 2;
+    var MIN_SCALE = 0.5;
 
     // Helper for detecting nearby objects near entityProperties, with the scale calculated by the dimensions of the object.
     function findEntitiesInRange(entityProperties) {
         var dimensions = entityProperties.dimensions;
-        return Entities.findEntities(entityProperties.position, ((dimensions.x + dimensions.y + dimensions.z) / 3) * RANGE_MULTIPLER);
+        // Average of the dimensions instead of full value.
+        return Entities.findEntities(entityProperties.position,
+            ((dimensions.x + dimensions.y + dimensions.z) / 3) * RANGE_MULTIPLER);
     }
 
     function getNearestValidEntityProperties(releasedProperties) {
         var entities = findEntitiesInRange(releasedProperties);
         var nearestEntity = null;
-        var nearest = 9999999999999;
+        var nearest = Number.MAX_SAFE_INTEGER;
         var releaseSize = Vec3.length(releasedProperties.dimensions);
         entities.forEach(function(entityId) {
             if (entityId !== releasedProperties.id) {
@@ -38,27 +40,30 @@
                     nearest = distance;
                 }
             }
-        })
+        });
         return nearestEntity;
     }
     // Create the 'class'
     function MagneticBlock() {}
     // Bind pre-emptive events
     MagneticBlock.prototype = {
-        // When script is bound to an entity, preload is the first callback called with the entityID. It will behave as the constructor
+        /*
+          When script is bound to an entity, preload is the first callback called with the entityID.
+          It will behave as the constructor
+        */
         preload: function(id) {
             /*
               We will now override any existing userdata with the grabbable property.
               Only retrieving userData
             */
-            var val = Entities.getEntityProperties(id, ['userData'])
+            var entity = Entities.getEntityProperties(id, ['userData']);
             var userData = {
                 grabbableKey: {}
             };
             // Check if existing userData field exists.
-            if (val.userData && val.userData.length > 0) {
+            if (entity.userData && entity.userData.length > 0) {
                 try {
-                    userData = JSON.parse(val.userData);
+                    userData = JSON.parse(entity.userData);
                     if (!userData.grabbableKey) {
                         userData.grabbableKey = {}; // If by random change there is no grabbableKey in the userData.
                     }
@@ -75,7 +80,7 @@
             });
             Script.scriptEnding.connect(function() {
                 Script.removeEventHandler(id, "releaseGrab", this.releaseGrab);
-            })
+            });
         },
         releaseGrab: function(entityId) {
             // Release grab is called with entityId,
@@ -102,7 +107,7 @@
                     x: 0,
                     y: 0,
                     z: 0
-                }
+                };
                 if (abs.x >= abs.y && abs.x >= abs.z) {
                     newRelative.x = target.dimensions.x / 2 + released.dimensions.x / 2;
                     if (relativeDifference.x < 0) {
@@ -138,9 +143,9 @@
                         y: 0.5,
                         z: 0.5
                     }
-                })
+                });
             }
         }
-    }
+    };
     return new MagneticBlock();
-})
+});
diff --git a/scripts/tutorials/makeBlocks.js b/scripts/tutorials/makeBlocks.js
index bb4974498c..54bdead792 100644
--- a/scripts/tutorials/makeBlocks.js
+++ b/scripts/tutorials/makeBlocks.js
@@ -12,19 +12,20 @@
 
 
 (function() {
-    const MAX_RGB_COMPONENT_VALUE = 256 / 2; // Limit the values to half the maximum.
-    const MIN_COLOR_VALUE = 127;
-    const SIZE = 0.3;
-    const LIFETIME = 600;
-
+    var MAX_RGB_COMPONENT_VALUE = 256 / 2; // Limit the values to half the maximum.
+    var MIN_COLOR_VALUE = 127;
+    var SIZE = 0.3;
+    var LIFETIME = 600;
+    var VERTICAL_OFFSET = -0.25;
+    var ROWS = 3;
+    var COLUMNS = 3;
     // Random Pastel Generator based on Piper's script
     function newColor() {
-        color = {
+        return {
             red: randomPastelRGBComponent(),
             green: randomPastelRGBComponent(),
             blue: randomPastelRGBComponent()
         };
-        return color;
     }
     // Helper functions.
     function randomPastelRGBComponent() {
@@ -34,9 +35,9 @@
     var SCRIPT_URL = Script.resolvePath("./entity_scripts/magneticBlock.js");
 
     var frontVector = Quat.getFront(MyAvatar.orientation);
-    frontVector.y -=.25;
-    for(var x =0; x < 3; x++) {
-        for (var y = 0; y < 3; y++) {
+    frontVector.y += VERTICAL_OFFSET;
+    for (var x = 0; x < COLUMNS; x++) {
+        for (var y = 0; y < ROWS; y++) {
 
             var frontOffset = {
                 x: 0,
@@ -46,13 +47,20 @@
 
             Entities.addEntity({
                 type: "Box",
-                name: "MagneticBlock-" + y +'-' + x,
+                name: "MagneticBlock-" + y + '-' + x,
                 dimensions: {
                     x: SIZE,
                     y: SIZE,
                     z: SIZE
                 },
-                userData: JSON.stringify({grabbableKey: { cloneable: true, grabbable: true, cloneLifetime : LIFETIME, cloneLimit: 9999}}),
+                userData: JSON.stringify({
+                    grabbableKey: {
+                        cloneable: true,
+                        grabbable: true,
+                        cloneLifetime: LIFETIME,
+                        cloneLimit: 9999
+                    }
+                }),
                 position: Vec3.sum(MyAvatar.position, Vec3.sum(frontOffset, frontVector)),
                 color: newColor(),
                 script: SCRIPT_URL

From 01ba44c572d66be6c86d79390918bda798576ba6 Mon Sep 17 00:00:00 2001
From: Menithal <menithal@norteclabs.com>
Date: Mon, 6 Mar 2017 21:32:27 +0200
Subject: [PATCH 62/64] Fixed print

---
 scripts/tutorials/entity_scripts/magneticBlock.js | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/scripts/tutorials/entity_scripts/magneticBlock.js b/scripts/tutorials/entity_scripts/magneticBlock.js
index 7771a3668d..1ec5f2a6c6 100644
--- a/scripts/tutorials/entity_scripts/magneticBlock.js
+++ b/scripts/tutorials/entity_scripts/magneticBlock.js
@@ -27,7 +27,7 @@
     function getNearestValidEntityProperties(releasedProperties) {
         var entities = findEntitiesInRange(releasedProperties);
         var nearestEntity = null;
-        var nearest = Number.MAX_SAFE_INTEGER;
+        var nearest = Number.MAX_VALUE - 1;
         var releaseSize = Vec3.length(releasedProperties.dimensions);
         entities.forEach(function(entityId) {
             if (entityId !== releasedProperties.id) {
@@ -56,14 +56,14 @@
               We will now override any existing userdata with the grabbable property.
               Only retrieving userData
             */
-            var entity = Entities.getEntityProperties(id, ['userData']);
+            var entityProperties = Entities.getEntityProperties(id, ['userData']);
             var userData = {
                 grabbableKey: {}
             };
             // Check if existing userData field exists.
-            if (entity.userData && entity.userData.length > 0) {
+            if (entityProperties.userData && entityProperties.userData.length > 0) {
                 try {
-                    userData = JSON.parse(entity.userData);
+                    userData = JSON.parse(entityProperties.userData);
                     if (!userData.grabbableKey) {
                         userData.grabbableKey = {}; // If by random change there is no grabbableKey in the userData.
                     }

From a2d2c41f02876e90711e1c222044af048a5d4c84 Mon Sep 17 00:00:00 2001
From: Seth Alves <seth.alves@gmail.com>
Date: Mon, 6 Mar 2017 11:45:49 -0800
Subject: [PATCH 63/64] remove debug print

---
 scripts/system/controllers/handControllerGrab.js | 1 -
 1 file changed, 1 deletion(-)

diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js
index febeea0c8e..7e9aae17af 100644
--- a/scripts/system/controllers/handControllerGrab.js
+++ b/scripts/system/controllers/handControllerGrab.js
@@ -3552,7 +3552,6 @@ function MyController(hand) {
                 // we appear to be holding something and this script isn't in a state that would be holding something.
                 // unhook it.  if we previously took note of this entity's parent, put it back where it was.  This
                 // works around some problems that happen when more than one hand or avatar is passing something around.
-                print("disconnecting stray child of hand: (" + _this.hand + ") " + childID);
                 if (_this.previousParentID[childID]) {
                     var previousParentID = _this.previousParentID[childID];
                     var previousParentJointIndex = _this.previousParentJointIndex[childID];

From 21f36d72123a292f2045dd96504ea74f80581551 Mon Sep 17 00:00:00 2001
From: samcake <samuel.gateau@gmail.com>
Date: Mon, 6 Mar 2017 12:41:00 -0800
Subject: [PATCH 64/64] removing comments and restoring the correct behavior
 for Pal.js

---
 libraries/render-utils/src/MeshPartPayload.h | 1 -
 scripts/system/pal.js                        | 3 +--
 2 files changed, 1 insertion(+), 3 deletions(-)

diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h
index 9d021f4f34..c585c95025 100644
--- a/libraries/render-utils/src/MeshPartPayload.h
+++ b/libraries/render-utils/src/MeshPartPayload.h
@@ -119,7 +119,6 @@ public:
 private:
     mutable quint64 _fadeStartTime { 0 };
     mutable uint8_t _fadeState { FADE_WAITING_TO_START };
-   // mutable uint8_t _fadeState { FADE_COMPLETE };
 };
 
 namespace render {
diff --git a/scripts/system/pal.js b/scripts/system/pal.js
index ae9c8265aa..70b2739c96 100644
--- a/scripts/system/pal.js
+++ b/scripts/system/pal.js
@@ -184,8 +184,7 @@ function HighlightedEntity(id, entityProperties) {
         },
         lineWidth: 1.0,
         ignoreRayIntersection: true,
-        //drawInFront: false // Arguable. For now, let's not distract with mysterious wires around the scene.
-        drawInFront: true // Arguable. For now, let's not distract with mysterious wires around the scene.
+        drawInFront: false // Arguable. For now, let's not distract with mysterious wires around the scene.
     });
     HighlightedEntity.overlays.push(this);
 }