From bab7d1e596ac5975dc73501a59033fa7c450d4fc Mon Sep 17 00:00:00 2001
From: ZappoMan <brad@highfidelity.io>
Date: Tue, 11 Apr 2017 12:17:28 -0700
Subject: [PATCH 01/38] first cut at translate and scale pose filters

---
 .../src/controllers/UserInputMapper.cpp       | 13 +++++
 .../src/controllers/impl/Filter.cpp           | 27 ++++++++++-
 .../controllers/src/controllers/impl/Filter.h |  5 ++
 .../controllers/impl/RouteBuilderProxy.cpp    |  7 +++
 .../src/controllers/impl/RouteBuilderProxy.h  |  1 +
 .../controllers/impl/filters/ScaleFilter.h    |  7 +++
 .../impl/filters/TranslateFilter.cpp          | 23 +++++++++
 .../impl/filters/TranslateFilter.h            | 47 +++++++++++++++++++
 script-archive/controllers/puppetFeet3.js     | 24 ++++++++++
 9 files changed, 153 insertions(+), 1 deletion(-)
 create mode 100644 libraries/controllers/src/controllers/impl/filters/TranslateFilter.cpp
 create mode 100644 libraries/controllers/src/controllers/impl/filters/TranslateFilter.h
 create mode 100644 script-archive/controllers/puppetFeet3.js

diff --git a/libraries/controllers/src/controllers/UserInputMapper.cpp b/libraries/controllers/src/controllers/UserInputMapper.cpp
index fe50f023c3..27da222fa6 100755
--- a/libraries/controllers/src/controllers/UserInputMapper.cpp
+++ b/libraries/controllers/src/controllers/UserInputMapper.cpp
@@ -20,6 +20,8 @@
 #include <PathUtils.h>
 #include <NumericalConstants.h>
 
+#include <StreamUtils.h>
+
 #include "StandardController.h"
 #include "StateController.h"
 
@@ -561,7 +563,18 @@ bool UserInputMapper::applyRoute(const Route::Pointer& route, bool force) {
     if (source->isPose()) {
         Pose value = getPose(source, route->peek);
         static const Pose IDENTITY_POSE { vec3(), quat() };
+
         if (debugRoutes && route->debug) {
+            qCDebug(controllers) << "Value was t:" << value.translation << "r:" << value.rotation;
+        }
+        // Apply each of the filters.
+        for (const auto& filter : route->filters) {
+            value = filter->apply(value);
+        }
+
+        if (debugRoutes && route->debug) {
+            qCDebug(controllers) << "Filtered value was t:" << value.translation << "r:" << value.rotation;
+
             if (!value.valid) {
                 qCDebug(controllers) << "Applying invalid pose";
             } else if (value == IDENTITY_POSE) {
diff --git a/libraries/controllers/src/controllers/impl/Filter.cpp b/libraries/controllers/src/controllers/impl/Filter.cpp
index 09188318eb..a75c43463e 100644
--- a/libraries/controllers/src/controllers/impl/Filter.cpp
+++ b/libraries/controllers/src/controllers/impl/Filter.cpp
@@ -24,6 +24,7 @@
 #include "filters/InvertFilter.h"
 #include "filters/PulseFilter.h"
 #include "filters/ScaleFilter.h"
+#include "filters/TranslateFilter.h"
 
 using namespace controller;
 
@@ -37,6 +38,7 @@ REGISTER_FILTER_CLASS_INSTANCE(HysteresisFilter, "hysteresis")
 REGISTER_FILTER_CLASS_INSTANCE(InvertFilter, "invert")
 REGISTER_FILTER_CLASS_INSTANCE(ScaleFilter, "scale")
 REGISTER_FILTER_CLASS_INSTANCE(PulseFilter, "pulse")
+REGISTER_FILTER_CLASS_INSTANCE(TranslateFilter, "translate")
 
 const QString JSON_FILTER_TYPE = QStringLiteral("type");
 const QString JSON_FILTER_PARAMS = QStringLiteral("params");
@@ -76,7 +78,6 @@ bool Filter::parseSingleFloatParameter(const QJsonValue& parameters, const QStri
             return true;
         }
     } else if (parameters.isObject()) {
-        static const QString JSON_MIN = QStringLiteral("interval");
         auto objectParameters = parameters.toObject();
         if (objectParameters.contains(name)) {
             output = objectParameters[name].toDouble();
@@ -86,6 +87,30 @@ bool Filter::parseSingleFloatParameter(const QJsonValue& parameters, const QStri
     return false;
 }
 
+// FIXME - we're not really using the name 
+bool Filter::parseVec3Parameter(const QJsonValue& parameters, const QString& name, glm::vec3& output) {
+    if (parameters.isDouble()) {
+        output = glm::vec3(parameters.toDouble());
+        return true;
+    } else if (parameters.isArray()) {
+        auto arrayParameters = parameters.toArray();
+        if (arrayParameters.size() == 3) {
+            output = glm::vec3(arrayParameters[0].toDouble(),
+                               arrayParameters[1].toDouble(),
+                               arrayParameters[2].toDouble());
+            return true;
+        }
+    } else if (parameters.isObject()) {
+        auto objectParameters = parameters.toObject();
+        if (objectParameters.contains("x") && objectParameters.contains("y") && objectParameters.contains("z")) {
+            output = glm::vec3(objectParameters["x"].toDouble(),
+                               objectParameters["y"].toDouble(),
+                               objectParameters["z"].toDouble());
+            return true;
+        }
+    } 
+    return false;
+}
 
 
 #if 0
diff --git a/libraries/controllers/src/controllers/impl/Filter.h b/libraries/controllers/src/controllers/impl/Filter.h
index 77585c8ebb..c268a227d7 100644
--- a/libraries/controllers/src/controllers/impl/Filter.h
+++ b/libraries/controllers/src/controllers/impl/Filter.h
@@ -21,6 +21,8 @@
 
 #include <QtCore/QEasingCurve>
 
+#include "../Pose.h"
+
 class QJsonValue;
 
 namespace controller {
@@ -34,6 +36,8 @@ namespace controller {
         using Factory = hifi::SimpleFactory<Filter, QString>;
 
         virtual float apply(float value) const = 0;
+        virtual Pose apply(Pose value) const { return value; } // most filters don't operate on poses
+
         // Factory features
         virtual bool parseParameters(const QJsonValue& parameters) { return true; }
 
@@ -42,6 +46,7 @@ namespace controller {
         static Factory& getFactory() { return _factory; }
 
         static bool parseSingleFloatParameter(const QJsonValue& parameters, const QString& name, float& output);
+        static bool parseVec3Parameter(const QJsonValue& parameters, const QString& name, glm::vec3& output);
     protected:
         static Factory _factory;
     };
diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp
index 7dedfda3cb..93ec8d4acd 100644
--- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp
+++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp
@@ -26,6 +26,7 @@
 #include "filters/InvertFilter.h"
 #include "filters/PulseFilter.h"
 #include "filters/ScaleFilter.h"
+#include "filters/TranslateFilter.h"
 #include "conditionals/AndConditional.h"
 
 using namespace controller;
@@ -103,6 +104,12 @@ QObject* RouteBuilderProxy::deadZone(float min) {
     return this;
 }
 
+QObject* RouteBuilderProxy::translate(glm::vec3 translate) {
+    qDebug() << __FUNCTION__ << "translate:" << translate;
+    addFilter(std::make_shared<TranslateFilter>(translate));
+    return this;
+}
+
 QObject* RouteBuilderProxy::constrainToInteger() {
     addFilter(std::make_shared<ConstrainToIntegerFilter>());
     return this;
diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h
index 1c0ed6931d..0d3ddd55fb 100644
--- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h
+++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h
@@ -48,6 +48,7 @@ class RouteBuilderProxy : public QObject {
         Q_INVOKABLE QObject* deadZone(float min);
         Q_INVOKABLE QObject* constrainToInteger();
         Q_INVOKABLE QObject* constrainToPositiveInteger();
+        Q_INVOKABLE QObject* translate(glm::vec3 translate);
 
     private:
         void to(const Endpoint::Pointer& destination);
diff --git a/libraries/controllers/src/controllers/impl/filters/ScaleFilter.h b/libraries/controllers/src/controllers/impl/filters/ScaleFilter.h
index 670da53fe8..7b03e2ce48 100644
--- a/libraries/controllers/src/controllers/impl/filters/ScaleFilter.h
+++ b/libraries/controllers/src/controllers/impl/filters/ScaleFilter.h
@@ -10,6 +10,8 @@
 #ifndef hifi_Controllers_Filters_Scale_h
 #define hifi_Controllers_Filters_Scale_h
 
+#include <glm/gtc/matrix_transform.hpp>
+
 #include "../Filter.h"
 
 namespace controller {
@@ -23,6 +25,11 @@ public:
     virtual float apply(float value) const override {
         return value * _scale;
     }
+
+    virtual Pose apply(Pose value) const override {
+        return value.transform(glm::scale(glm::mat4(), glm::vec3(_scale)));
+    }
+
     virtual bool parseParameters(const QJsonValue& parameters) override;
 
 private:
diff --git a/libraries/controllers/src/controllers/impl/filters/TranslateFilter.cpp b/libraries/controllers/src/controllers/impl/filters/TranslateFilter.cpp
new file mode 100644
index 0000000000..6f7e1e6a45
--- /dev/null
+++ b/libraries/controllers/src/controllers/impl/filters/TranslateFilter.cpp
@@ -0,0 +1,23 @@
+//
+//  Created by Bradley Austin Davis 2015/10/25
+//  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 "TranslateFilter.h"
+
+#include <QtCore/QJsonObject>
+#include <QtCore/QJsonArray>
+
+#include <StreamUtils.h>
+
+using namespace controller;
+
+bool TranslateFilter::parseParameters(const QJsonValue& parameters) {
+    static const QString JSON_TRANSLATE = QStringLiteral("translate");
+    bool result = parseVec3Parameter(parameters, JSON_TRANSLATE, _translate);
+    qDebug() << __FUNCTION__ << "_translate:" << _translate;
+    return result;
+}
diff --git a/libraries/controllers/src/controllers/impl/filters/TranslateFilter.h b/libraries/controllers/src/controllers/impl/filters/TranslateFilter.h
new file mode 100644
index 0000000000..504e4c3151
--- /dev/null
+++ b/libraries/controllers/src/controllers/impl/filters/TranslateFilter.h
@@ -0,0 +1,47 @@
+//
+//  Created by Brad Hefta-Gaub 2017/04/11
+//  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
+//
+
+#pragma once
+#ifndef hifi_Controllers_Filters_Translate_h
+#define hifi_Controllers_Filters_Translate_h
+
+#include <glm/gtx/transform.hpp>
+
+#include <StreamUtils.h>
+
+#include "../Filter.h"
+
+namespace controller {
+
+class TranslateFilter : public Filter {
+    REGISTER_FILTER_CLASS(TranslateFilter);
+public:
+    TranslateFilter() {
+        qDebug() << __FUNCTION__;
+    }
+    TranslateFilter(glm::vec3 translate) : _translate(translate) {
+        qDebug() << __FUNCTION__ << "translate:" << translate;
+    }
+
+    virtual float apply(float value) const override {
+        return value;
+    }
+
+    virtual Pose apply(Pose value) const override {
+        return value.transform(glm::translate(_translate));
+    }
+
+    virtual bool parseParameters(const QJsonValue& parameters) override;
+
+private:
+    glm::vec3 _translate { 0.0f };
+};
+
+}
+
+#endif
diff --git a/script-archive/controllers/puppetFeet3.js b/script-archive/controllers/puppetFeet3.js
new file mode 100644
index 0000000000..8fa7d5c632
--- /dev/null
+++ b/script-archive/controllers/puppetFeet3.js
@@ -0,0 +1,24 @@
+//
+//  puppetFeet3.js
+//  examples/controllers
+//
+//  Created by Brad Hefta-Gaub on 2017/04/11
+//  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
+//
+
+
+var MAPPING_NAME = "com.highfidelity.examples.puppetFeet3";
+var mapping = Controller.newMapping(MAPPING_NAME);
+var puppetOffset = { x: 0, y: -1, z: 0 };
+
+mapping.from(Controller.Standard.LeftHand).peek().translate(puppetOffset).to(Controller.Standard.LeftFoot);
+
+Controller.enableMapping(MAPPING_NAME);
+
+
+Script.scriptEnding.connect(function(){
+    mapping.disable();
+});

From 82166f494738a6338d53750a92f79dd1ce1fabe0 Mon Sep 17 00:00:00 2001
From: ZappoMan <brad@highfidelity.io>
Date: Tue, 11 Apr 2017 14:06:36 -0700
Subject: [PATCH 02/38] add rotate and transform filters for poses

---
 .../src/controllers/impl/Filter.cpp           | 65 +++++++++++++++++++
 .../controllers/src/controllers/impl/Filter.h |  2 +
 .../controllers/impl/RouteBuilderProxy.cpp    | 13 +++-
 .../src/controllers/impl/RouteBuilderProxy.h  |  2 +
 .../controllers/impl/filters/RotateFilter.cpp | 21 ++++++
 .../controllers/impl/filters/RotateFilter.h   | 40 ++++++++++++
 .../impl/filters/TransformFilter.cpp          | 21 ++++++
 .../impl/filters/TransformFilter.h            | 41 ++++++++++++
 .../impl/filters/TranslateFilter.cpp          |  4 +-
 .../impl/filters/TranslateFilter.h            | 10 +--
 script-archive/controllers/puppetFeet3.js     | 12 +++-
 11 files changed, 218 insertions(+), 13 deletions(-)
 create mode 100644 libraries/controllers/src/controllers/impl/filters/RotateFilter.cpp
 create mode 100644 libraries/controllers/src/controllers/impl/filters/RotateFilter.h
 create mode 100644 libraries/controllers/src/controllers/impl/filters/TransformFilter.cpp
 create mode 100644 libraries/controllers/src/controllers/impl/filters/TransformFilter.h

diff --git a/libraries/controllers/src/controllers/impl/Filter.cpp b/libraries/controllers/src/controllers/impl/Filter.cpp
index a75c43463e..12e666825b 100644
--- a/libraries/controllers/src/controllers/impl/Filter.cpp
+++ b/libraries/controllers/src/controllers/impl/Filter.cpp
@@ -25,6 +25,7 @@
 #include "filters/PulseFilter.h"
 #include "filters/ScaleFilter.h"
 #include "filters/TranslateFilter.h"
+#include "filters/TransformFilter.h"
 
 using namespace controller;
 
@@ -39,6 +40,7 @@ REGISTER_FILTER_CLASS_INSTANCE(InvertFilter, "invert")
 REGISTER_FILTER_CLASS_INSTANCE(ScaleFilter, "scale")
 REGISTER_FILTER_CLASS_INSTANCE(PulseFilter, "pulse")
 REGISTER_FILTER_CLASS_INSTANCE(TranslateFilter, "translate")
+REGISTER_FILTER_CLASS_INSTANCE(TransformFilter, "transform")
 
 const QString JSON_FILTER_TYPE = QStringLiteral("type");
 const QString JSON_FILTER_PARAMS = QStringLiteral("params");
@@ -112,6 +114,69 @@ bool Filter::parseVec3Parameter(const QJsonValue& parameters, const QString& nam
     return false;
 }
 
+bool Filter::parseMat4Parameter(const QJsonValue& parameters, const QString& name, glm::mat4& output) {
+    if (parameters.isObject()) {
+        auto objectParameters = parameters.toObject();
+
+
+        if (objectParameters.contains("r0c0") && 
+            objectParameters.contains("r1c0") &&
+            objectParameters.contains("r2c0") &&
+            objectParameters.contains("r3c0") &&
+            objectParameters.contains("r0c1") &&
+            objectParameters.contains("r1c1") &&
+            objectParameters.contains("r2c1") &&
+            objectParameters.contains("r3c1") &&
+            objectParameters.contains("r0c2") &&
+            objectParameters.contains("r1c2") &&
+            objectParameters.contains("r2c2") &&
+            objectParameters.contains("r3c2") &&
+            objectParameters.contains("r0c3") &&
+            objectParameters.contains("r1c3") &&
+            objectParameters.contains("r2c3") &&
+            objectParameters.contains("r3c3")) {
+
+            output[0][0] = objectParameters["r0c0"].toDouble();
+            output[0][1] = objectParameters["r1c0"].toDouble();
+            output[0][2] = objectParameters["r2c0"].toDouble();
+            output[0][3] = objectParameters["r3c0"].toDouble();
+            output[1][0] = objectParameters["r0c1"].toDouble();
+            output[1][1] = objectParameters["r1c1"].toDouble();
+            output[1][2] = objectParameters["r2c1"].toDouble();
+            output[1][3] = objectParameters["r3c1"].toDouble();
+            output[2][0] = objectParameters["r0c2"].toDouble();
+            output[2][1] = objectParameters["r1c2"].toDouble();
+            output[2][2] = objectParameters["r2c2"].toDouble();
+            output[2][3] = objectParameters["r3c2"].toDouble();
+            output[3][0] = objectParameters["r0c3"].toDouble();
+            output[3][1] = objectParameters["r1c3"].toDouble();
+            output[3][2] = objectParameters["r2c3"].toDouble();
+            output[3][3] = objectParameters["r3c3"].toDouble();
+
+            return true;
+        }
+    }
+    return false;
+}
+
+bool Filter::parseQuatParameter(const QJsonValue& parameters, const QString& name, glm::quat& output) {
+    if (parameters.isObject()) {
+        auto objectParameters = parameters.toObject();
+        if (objectParameters.contains("w") && 
+            objectParameters.contains("x") && 
+            objectParameters.contains("y") && 
+            objectParameters.contains("z")) {
+
+            output = glm::quat(objectParameters["w"].toDouble(),
+                               objectParameters["x"].toDouble(),
+                               objectParameters["y"].toDouble(),
+                               objectParameters["z"].toDouble());
+            return true;
+        }
+    }
+    return false;
+}
+
 
 #if 0
 
diff --git a/libraries/controllers/src/controllers/impl/Filter.h b/libraries/controllers/src/controllers/impl/Filter.h
index c268a227d7..d20a3d08e3 100644
--- a/libraries/controllers/src/controllers/impl/Filter.h
+++ b/libraries/controllers/src/controllers/impl/Filter.h
@@ -47,6 +47,8 @@ namespace controller {
 
         static bool parseSingleFloatParameter(const QJsonValue& parameters, const QString& name, float& output);
         static bool parseVec3Parameter(const QJsonValue& parameters, const QString& name, glm::vec3& output);
+        static bool parseMat4Parameter(const QJsonValue& parameters, const QString& name, glm::mat4& output);
+        static bool parseQuatParameter(const QJsonValue& parameters, const QString& name, glm::quat& output);
     protected:
         static Factory _factory;
     };
diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp
index 93ec8d4acd..fa662dcc13 100644
--- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp
+++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp
@@ -27,6 +27,8 @@
 #include "filters/PulseFilter.h"
 #include "filters/ScaleFilter.h"
 #include "filters/TranslateFilter.h"
+#include "filters/TransformFilter.h"
+#include "filters/RotateFilter.h"
 #include "conditionals/AndConditional.h"
 
 using namespace controller;
@@ -105,11 +107,20 @@ QObject* RouteBuilderProxy::deadZone(float min) {
 }
 
 QObject* RouteBuilderProxy::translate(glm::vec3 translate) {
-    qDebug() << __FUNCTION__ << "translate:" << translate;
     addFilter(std::make_shared<TranslateFilter>(translate));
     return this;
 }
 
+QObject* RouteBuilderProxy::transform(glm::mat4 transform) {
+    addFilter(std::make_shared<TransformFilter>(transform));
+    return this;
+}
+
+QObject* RouteBuilderProxy::rotate(glm::quat rotation) {
+    addFilter(std::make_shared<RotateFilter>(rotation));
+    return this;
+}
+
 QObject* RouteBuilderProxy::constrainToInteger() {
     addFilter(std::make_shared<ConstrainToIntegerFilter>());
     return this;
diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h
index 0d3ddd55fb..7696eefebe 100644
--- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h
+++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h
@@ -49,6 +49,8 @@ class RouteBuilderProxy : public QObject {
         Q_INVOKABLE QObject* constrainToInteger();
         Q_INVOKABLE QObject* constrainToPositiveInteger();
         Q_INVOKABLE QObject* translate(glm::vec3 translate);
+        Q_INVOKABLE QObject* transform(glm::mat4 transform);
+        Q_INVOKABLE QObject* rotate(glm::quat rotation);
 
     private:
         void to(const Endpoint::Pointer& destination);
diff --git a/libraries/controllers/src/controllers/impl/filters/RotateFilter.cpp b/libraries/controllers/src/controllers/impl/filters/RotateFilter.cpp
new file mode 100644
index 0000000000..b5a94ba492
--- /dev/null
+++ b/libraries/controllers/src/controllers/impl/filters/RotateFilter.cpp
@@ -0,0 +1,21 @@
+//
+//  Created by Brad Hefta-Gaub 2017/04/11
+//  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 "RotateFilter.h"
+
+#include <QtCore/QJsonObject>
+#include <QtCore/QJsonArray>
+
+#include <StreamUtils.h>
+
+using namespace controller;
+
+bool RotateFilter::parseParameters(const QJsonValue& parameters) {
+    static const QString JSON_ROTATION = QStringLiteral("rotation");
+    return parseQuatParameter(parameters, JSON_ROTATION, _rotation);
+}
diff --git a/libraries/controllers/src/controllers/impl/filters/RotateFilter.h b/libraries/controllers/src/controllers/impl/filters/RotateFilter.h
new file mode 100644
index 0000000000..a5c3b10655
--- /dev/null
+++ b/libraries/controllers/src/controllers/impl/filters/RotateFilter.h
@@ -0,0 +1,40 @@
+//
+//  Created by Brad Hefta-Gaub 2017/04/11
+//  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
+//
+
+#pragma once
+#ifndef hifi_Controllers_Filters_Rotate_h
+#define hifi_Controllers_Filters_Rotate_h
+
+#include <glm/gtx/transform.hpp>
+
+#include "../Filter.h"
+
+namespace controller {
+
+class RotateFilter : public Filter {
+    REGISTER_FILTER_CLASS(RotateFilter);
+public:
+    RotateFilter() { }
+    RotateFilter(glm::quat rotation) : _rotation(rotation) {}
+
+    virtual float apply(float value) const override { return value; }
+
+    virtual Pose apply(Pose value) const override {
+        glm::quat temp = _rotation;
+        return value.transform(glm::mat4(temp));
+    }
+
+    virtual bool parseParameters(const QJsonValue& parameters) override;
+
+private:
+    glm::quat _rotation;
+};
+
+}
+
+#endif
diff --git a/libraries/controllers/src/controllers/impl/filters/TransformFilter.cpp b/libraries/controllers/src/controllers/impl/filters/TransformFilter.cpp
new file mode 100644
index 0000000000..71568b4c29
--- /dev/null
+++ b/libraries/controllers/src/controllers/impl/filters/TransformFilter.cpp
@@ -0,0 +1,21 @@
+//
+//  Created by Bradley Austin Davis 2015/10/25
+//  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 "TransformFilter.h"
+
+#include <QtCore/QJsonObject>
+#include <QtCore/QJsonArray>
+
+#include <StreamUtils.h>
+
+using namespace controller;
+
+bool TransformFilter::parseParameters(const QJsonValue& parameters) {
+    static const QString JSON_TRANSFORM = QStringLiteral("transform");
+    return parseMat4Parameter(parameters, JSON_TRANSFORM, _transform);
+}
diff --git a/libraries/controllers/src/controllers/impl/filters/TransformFilter.h b/libraries/controllers/src/controllers/impl/filters/TransformFilter.h
new file mode 100644
index 0000000000..9999fd03c7
--- /dev/null
+++ b/libraries/controllers/src/controllers/impl/filters/TransformFilter.h
@@ -0,0 +1,41 @@
+//
+//  Created by Brad Hefta-Gaub 2017/04/11
+//  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
+//
+
+#pragma once
+#ifndef hifi_Controllers_Filters_Transform_h
+#define hifi_Controllers_Filters_Transform_h
+
+#include <glm/gtx/transform.hpp>
+
+#include "../Filter.h"
+
+namespace controller {
+
+class TransformFilter : public Filter {
+    REGISTER_FILTER_CLASS(TransformFilter);
+public:
+    TransformFilter() { }
+    TransformFilter(glm::mat4 transform) : _transform(transform) {}
+
+    virtual float apply(float value) const override {
+        return value;
+    }
+
+    virtual Pose apply(Pose value) const override {
+        return value.transform(_transform);
+    }
+
+    virtual bool parseParameters(const QJsonValue& parameters) override;
+
+private:
+    glm::mat4 _transform;
+};
+
+}
+
+#endif
diff --git a/libraries/controllers/src/controllers/impl/filters/TranslateFilter.cpp b/libraries/controllers/src/controllers/impl/filters/TranslateFilter.cpp
index 6f7e1e6a45..e63afcdd9e 100644
--- a/libraries/controllers/src/controllers/impl/filters/TranslateFilter.cpp
+++ b/libraries/controllers/src/controllers/impl/filters/TranslateFilter.cpp
@@ -17,7 +17,5 @@ using namespace controller;
 
 bool TranslateFilter::parseParameters(const QJsonValue& parameters) {
     static const QString JSON_TRANSLATE = QStringLiteral("translate");
-    bool result = parseVec3Parameter(parameters, JSON_TRANSLATE, _translate);
-    qDebug() << __FUNCTION__ << "_translate:" << _translate;
-    return result;
+    return parseVec3Parameter(parameters, JSON_TRANSLATE, _translate);
 }
diff --git a/libraries/controllers/src/controllers/impl/filters/TranslateFilter.h b/libraries/controllers/src/controllers/impl/filters/TranslateFilter.h
index 504e4c3151..d5ac417b98 100644
--- a/libraries/controllers/src/controllers/impl/filters/TranslateFilter.h
+++ b/libraries/controllers/src/controllers/impl/filters/TranslateFilter.h
@@ -12,8 +12,6 @@
 
 #include <glm/gtx/transform.hpp>
 
-#include <StreamUtils.h>
-
 #include "../Filter.h"
 
 namespace controller {
@@ -21,12 +19,8 @@ namespace controller {
 class TranslateFilter : public Filter {
     REGISTER_FILTER_CLASS(TranslateFilter);
 public:
-    TranslateFilter() {
-        qDebug() << __FUNCTION__;
-    }
-    TranslateFilter(glm::vec3 translate) : _translate(translate) {
-        qDebug() << __FUNCTION__ << "translate:" << translate;
-    }
+    TranslateFilter() { }
+    TranslateFilter(glm::vec3 translate) : _translate(translate) {}
 
     virtual float apply(float value) const override {
         return value;
diff --git a/script-archive/controllers/puppetFeet3.js b/script-archive/controllers/puppetFeet3.js
index 8fa7d5c632..3c9618edd9 100644
--- a/script-archive/controllers/puppetFeet3.js
+++ b/script-archive/controllers/puppetFeet3.js
@@ -14,7 +14,17 @@ var MAPPING_NAME = "com.highfidelity.examples.puppetFeet3";
 var mapping = Controller.newMapping(MAPPING_NAME);
 var puppetOffset = { x: 0, y: -1, z: 0 };
 
-mapping.from(Controller.Standard.LeftHand).peek().translate(puppetOffset).to(Controller.Standard.LeftFoot);
+var rotation = Quat.fromPitchYawRollDegrees(0, 0, -90); 
+var noTranslation = { x: 0, y: 0, z: 0 };
+var transformMatrix = Mat4.createFromRotAndTrans(rotation, noTranslation);
+var rotateAndTranslate = Mat4.createFromRotAndTrans(rotation, puppetOffset);
+
+
+mapping.from(Controller.Standard.LeftHand).peek().rotate(rotation).translate(puppetOffset).to(Controller.Standard.LeftFoot);
+
+//mapping.from(Controller.Standard.LeftHand).peek().translate(puppetOffset).to(Controller.Standard.LeftFoot);
+//mapping.from(Controller.Standard.LeftHand).peek().transform(transformMatrix).translate(puppetOffset).to(Controller.Standard.LeftFoot);
+//mapping.from(Controller.Standard.LeftHand).peek().transform(rotateAndTranslate).to(Controller.Standard.LeftFoot);
 
 Controller.enableMapping(MAPPING_NAME);
 

From 6daf68b338282ad68f615339bc185c48a38d64ce Mon Sep 17 00:00:00 2001
From: ZappoMan <brad@highfidelity.io>
Date: Tue, 11 Apr 2017 14:18:21 -0700
Subject: [PATCH 03/38] tweak

---
 libraries/controllers/src/controllers/impl/Filter.h            | 2 +-
 .../controllers/src/controllers/impl/filters/ClampFilter.h     | 3 +++
 .../src/controllers/impl/filters/ConstrainToIntegerFilter.h    | 3 +++
 .../impl/filters/ConstrainToPositiveIntegerFilter.h            | 3 +++
 .../controllers/src/controllers/impl/filters/DeadZoneFilter.h  | 3 +++
 .../src/controllers/impl/filters/HysteresisFilter.h            | 3 +++
 .../controllers/src/controllers/impl/filters/PulseFilter.h     | 2 ++
 7 files changed, 18 insertions(+), 1 deletion(-)

diff --git a/libraries/controllers/src/controllers/impl/Filter.h b/libraries/controllers/src/controllers/impl/Filter.h
index d20a3d08e3..2d79a28ddb 100644
--- a/libraries/controllers/src/controllers/impl/Filter.h
+++ b/libraries/controllers/src/controllers/impl/Filter.h
@@ -36,7 +36,7 @@ namespace controller {
         using Factory = hifi::SimpleFactory<Filter, QString>;
 
         virtual float apply(float value) const = 0;
-        virtual Pose apply(Pose value) const { return value; } // most filters don't operate on poses
+        virtual Pose apply(Pose value) const = 0;
 
         // Factory features
         virtual bool parseParameters(const QJsonValue& parameters) { return true; }
diff --git a/libraries/controllers/src/controllers/impl/filters/ClampFilter.h b/libraries/controllers/src/controllers/impl/filters/ClampFilter.h
index fd82821b3e..b06a43515f 100644
--- a/libraries/controllers/src/controllers/impl/filters/ClampFilter.h
+++ b/libraries/controllers/src/controllers/impl/filters/ClampFilter.h
@@ -21,6 +21,9 @@ public:
     virtual float apply(float value) const override {
         return glm::clamp(value, _min, _max);
     }
+
+    virtual Pose apply(Pose value) const override { return value; }
+
     virtual bool parseParameters(const QJsonValue& parameters) override;
 protected:
     float _min = 0.0f;
diff --git a/libraries/controllers/src/controllers/impl/filters/ConstrainToIntegerFilter.h b/libraries/controllers/src/controllers/impl/filters/ConstrainToIntegerFilter.h
index 580dc2a856..c9a25fde72 100644
--- a/libraries/controllers/src/controllers/impl/filters/ConstrainToIntegerFilter.h
+++ b/libraries/controllers/src/controllers/impl/filters/ConstrainToIntegerFilter.h
@@ -22,6 +22,9 @@ public:
     virtual float apply(float value) const override {
         return glm::sign(value);
     }
+
+    virtual Pose apply(Pose value) const override { return value; }
+
 protected:
 };
 
diff --git a/libraries/controllers/src/controllers/impl/filters/ConstrainToPositiveIntegerFilter.h b/libraries/controllers/src/controllers/impl/filters/ConstrainToPositiveIntegerFilter.h
index 27395cde24..e3f4ee8929 100644
--- a/libraries/controllers/src/controllers/impl/filters/ConstrainToPositiveIntegerFilter.h
+++ b/libraries/controllers/src/controllers/impl/filters/ConstrainToPositiveIntegerFilter.h
@@ -22,6 +22,9 @@ public:
     virtual float apply(float value) const override {
         return (value <= 0.0f) ? 0.0f : 1.0f;
     }
+
+    virtual Pose apply(Pose value) const override { return value; }
+
 protected:
 };
 
diff --git a/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.h b/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.h
index 70ac657415..d898647126 100644
--- a/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.h
+++ b/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.h
@@ -20,6 +20,9 @@ public:
     DeadZoneFilter(float min = 0.0) : _min(min) {};
 
     virtual float apply(float value) const override;
+
+    virtual Pose apply(Pose value) const override { return value; }
+
     virtual bool parseParameters(const QJsonValue& parameters) override;
 protected:
     float _min = 0.0f;
diff --git a/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.h b/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.h
index 4f7e07928d..4eb563754f 100644
--- a/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.h
+++ b/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.h
@@ -19,6 +19,9 @@ class HysteresisFilter : public Filter {
 public:
     HysteresisFilter(float min = 0.25, float max = 0.75);
     virtual float apply(float value) const override;
+
+    virtual Pose apply(Pose value) const override { return value; }
+
     virtual bool parseParameters(const QJsonValue& parameters) override;
 protected:
     float _min;
diff --git a/libraries/controllers/src/controllers/impl/filters/PulseFilter.h b/libraries/controllers/src/controllers/impl/filters/PulseFilter.h
index 271f4a04f6..a8c7cbf9e6 100644
--- a/libraries/controllers/src/controllers/impl/filters/PulseFilter.h
+++ b/libraries/controllers/src/controllers/impl/filters/PulseFilter.h
@@ -23,6 +23,8 @@ public:
 
     virtual float apply(float value) const override;
 
+    virtual Pose apply(Pose value) const override { return value; }
+
     virtual bool parseParameters(const QJsonValue& parameters) override;
 
 private:

From afe7c386c3831ddc6e75d456fad1879a20f9a818 Mon Sep 17 00:00:00 2001
From: ZappoMan <brad@highfidelity.io>
Date: Tue, 11 Apr 2017 17:03:33 -0700
Subject: [PATCH 04/38] added postTransform filter

---
 .../controllers/src/controllers/Pose.cpp      | 19 +++++++++++
 libraries/controllers/src/controllers/Pose.h  |  3 ++
 .../src/controllers/impl/Filter.cpp           |  6 +++-
 .../controllers/src/controllers/impl/Filter.h |  2 +-
 .../controllers/impl/RouteBuilderProxy.cpp    |  6 ++++
 .../src/controllers/impl/RouteBuilderProxy.h  |  1 +
 .../impl/filters/PostTransformFilter.h        | 33 +++++++++++++++++++
 .../impl/filters/TransformFilter.cpp          |  3 +-
 8 files changed, 69 insertions(+), 4 deletions(-)
 create mode 100644 libraries/controllers/src/controllers/impl/filters/PostTransformFilter.h

diff --git a/libraries/controllers/src/controllers/Pose.cpp b/libraries/controllers/src/controllers/Pose.cpp
index b86391bbba..6bcea82720 100644
--- a/libraries/controllers/src/controllers/Pose.cpp
+++ b/libraries/controllers/src/controllers/Pose.cpp
@@ -69,5 +69,24 @@ namespace controller {
         pose.valid = valid;
         return pose;
     }
+
+    Pose Pose::postTransform(const glm::mat4& mat) const {
+        glm::mat4 original = getMat4();
+        glm::mat4 result = original * mat;
+
+        auto translationOut = ::extractTranslation(result);
+        auto rotationOut = ::glmExtractRotation(result);
+        auto velocityOut = velocity + glm::cross(angularVelocity, translation - translationOut); // warning: this may be completely wrong
+        auto angularVelocityOut = angularVelocity;
+
+        Pose pose(translationOut,
+            rotationOut,
+            velocityOut,
+            angularVelocityOut);
+
+        pose.valid = valid;
+        return pose;
+    }
+
 }
 
diff --git a/libraries/controllers/src/controllers/Pose.h b/libraries/controllers/src/controllers/Pose.h
index 47ba59279a..9e5371ce59 100644
--- a/libraries/controllers/src/controllers/Pose.h
+++ b/libraries/controllers/src/controllers/Pose.h
@@ -41,6 +41,9 @@ namespace controller {
         vec3 getAngularVelocity() const { return angularVelocity; }
 
         Pose transform(const glm::mat4& mat) const;
+        Pose postTransform(const glm::mat4& mat) const;
+
+        glm::mat4 getMat4() const { return ::createMatFromQuatAndPos(rotation, translation); }
 
         static QScriptValue toScriptValue(QScriptEngine* engine, const Pose& event);
         static void fromScriptValue(const QScriptValue& object, Pose& event);
diff --git a/libraries/controllers/src/controllers/impl/Filter.cpp b/libraries/controllers/src/controllers/impl/Filter.cpp
index 12e666825b..74fa5d99cc 100644
--- a/libraries/controllers/src/controllers/impl/Filter.cpp
+++ b/libraries/controllers/src/controllers/impl/Filter.cpp
@@ -26,6 +26,8 @@
 #include "filters/ScaleFilter.h"
 #include "filters/TranslateFilter.h"
 #include "filters/TransformFilter.h"
+#include "filters/PostTransformFilter.h"
+#include "filters/RotateFilter.h"
 
 using namespace controller;
 
@@ -41,6 +43,8 @@ REGISTER_FILTER_CLASS_INSTANCE(ScaleFilter, "scale")
 REGISTER_FILTER_CLASS_INSTANCE(PulseFilter, "pulse")
 REGISTER_FILTER_CLASS_INSTANCE(TranslateFilter, "translate")
 REGISTER_FILTER_CLASS_INSTANCE(TransformFilter, "transform")
+REGISTER_FILTER_CLASS_INSTANCE(PostTransformFilter, "postTransform")
+REGISTER_FILTER_CLASS_INSTANCE(RotateFilter, "rotate")
 
 const QString JSON_FILTER_TYPE = QStringLiteral("type");
 const QString JSON_FILTER_PARAMS = QStringLiteral("params");
@@ -114,7 +118,7 @@ bool Filter::parseVec3Parameter(const QJsonValue& parameters, const QString& nam
     return false;
 }
 
-bool Filter::parseMat4Parameter(const QJsonValue& parameters, const QString& name, glm::mat4& output) {
+bool Filter::parseMat4Parameter(const QJsonValue& parameters, glm::mat4& output) {
     if (parameters.isObject()) {
         auto objectParameters = parameters.toObject();
 
diff --git a/libraries/controllers/src/controllers/impl/Filter.h b/libraries/controllers/src/controllers/impl/Filter.h
index 2d79a28ddb..73b0aa06f5 100644
--- a/libraries/controllers/src/controllers/impl/Filter.h
+++ b/libraries/controllers/src/controllers/impl/Filter.h
@@ -47,8 +47,8 @@ namespace controller {
 
         static bool parseSingleFloatParameter(const QJsonValue& parameters, const QString& name, float& output);
         static bool parseVec3Parameter(const QJsonValue& parameters, const QString& name, glm::vec3& output);
-        static bool parseMat4Parameter(const QJsonValue& parameters, const QString& name, glm::mat4& output);
         static bool parseQuatParameter(const QJsonValue& parameters, const QString& name, glm::quat& output);
+        static bool parseMat4Parameter(const QJsonValue& parameters, glm::mat4& output);
     protected:
         static Factory _factory;
     };
diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp
index fa662dcc13..f3c447238a 100644
--- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp
+++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp
@@ -28,6 +28,7 @@
 #include "filters/ScaleFilter.h"
 #include "filters/TranslateFilter.h"
 #include "filters/TransformFilter.h"
+#include "filters/PostTransformFilter.h"
 #include "filters/RotateFilter.h"
 #include "conditionals/AndConditional.h"
 
@@ -116,6 +117,11 @@ QObject* RouteBuilderProxy::transform(glm::mat4 transform) {
     return this;
 }
 
+QObject* RouteBuilderProxy::postTransform(glm::mat4 transform) {
+    addFilter(std::make_shared<PostTransformFilter>(transform));
+    return this;
+}
+
 QObject* RouteBuilderProxy::rotate(glm::quat rotation) {
     addFilter(std::make_shared<RotateFilter>(rotation));
     return this;
diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h
index 7696eefebe..de9c23d2cd 100644
--- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h
+++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h
@@ -50,6 +50,7 @@ class RouteBuilderProxy : public QObject {
         Q_INVOKABLE QObject* constrainToPositiveInteger();
         Q_INVOKABLE QObject* translate(glm::vec3 translate);
         Q_INVOKABLE QObject* transform(glm::mat4 transform);
+        Q_INVOKABLE QObject* postTransform(glm::mat4 transform);
         Q_INVOKABLE QObject* rotate(glm::quat rotation);
 
     private:
diff --git a/libraries/controllers/src/controllers/impl/filters/PostTransformFilter.h b/libraries/controllers/src/controllers/impl/filters/PostTransformFilter.h
new file mode 100644
index 0000000000..656a146ff2
--- /dev/null
+++ b/libraries/controllers/src/controllers/impl/filters/PostTransformFilter.h
@@ -0,0 +1,33 @@
+//
+//  Created by Brad Hefta-Gaub 2017/04/11
+//  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
+//
+
+#pragma once
+#ifndef hifi_Controllers_Filters_PostTransform_h
+#define hifi_Controllers_Filters_PostTransform_h
+
+#include <glm/gtx/transform.hpp>
+
+#include "../Filter.h"
+
+namespace controller {
+
+class PostTransformFilter : public Filter {
+    REGISTER_FILTER_CLASS(PostTransformFilter);
+public:
+    PostTransformFilter() { }
+    PostTransformFilter(glm::mat4 transform) : _transform(transform) {}
+    virtual float apply(float value) const override { return value; }
+    virtual Pose apply(Pose value) const override { return value.postTransform(_transform); }
+    virtual bool parseParameters(const QJsonValue& parameters) override { return parseMat4Parameter(parameters, _transform); }
+private:
+    glm::mat4 _transform;
+};
+
+}
+
+#endif
diff --git a/libraries/controllers/src/controllers/impl/filters/TransformFilter.cpp b/libraries/controllers/src/controllers/impl/filters/TransformFilter.cpp
index 71568b4c29..2b7b0ef21d 100644
--- a/libraries/controllers/src/controllers/impl/filters/TransformFilter.cpp
+++ b/libraries/controllers/src/controllers/impl/filters/TransformFilter.cpp
@@ -16,6 +16,5 @@
 using namespace controller;
 
 bool TransformFilter::parseParameters(const QJsonValue& parameters) {
-    static const QString JSON_TRANSFORM = QStringLiteral("transform");
-    return parseMat4Parameter(parameters, JSON_TRANSFORM, _transform);
+    return parseMat4Parameter(parameters, _transform);
 }

From dce73ea428dfe7bbdebd05aed68ed2c4c332f79f Mon Sep 17 00:00:00 2001
From: ZappoMan <brad@highfidelity.io>
Date: Wed, 12 Apr 2017 13:43:57 -0700
Subject: [PATCH 05/38] some cleanup

---
 .../controllers/src/controllers/Pose.cpp      |  3 +--
 libraries/controllers/src/controllers/Pose.h  |  2 --
 .../src/controllers/impl/Filter.cpp           |  5 ++---
 .../controllers/src/controllers/impl/Filter.h |  4 ++--
 .../controllers/impl/filters/RotateFilter.cpp | 21 -------------------
 .../controllers/impl/filters/RotateFilter.h   |  5 ++---
 .../impl/filters/TransformFilter.cpp          | 20 ------------------
 .../impl/filters/TransformFilter.h            | 12 +++--------
 .../impl/filters/TranslateFilter.cpp          | 21 -------------------
 .../impl/filters/TranslateFilter.h            | 12 +++--------
 10 files changed, 13 insertions(+), 92 deletions(-)
 delete mode 100644 libraries/controllers/src/controllers/impl/filters/RotateFilter.cpp
 delete mode 100644 libraries/controllers/src/controllers/impl/filters/TransformFilter.cpp
 delete mode 100644 libraries/controllers/src/controllers/impl/filters/TranslateFilter.cpp

diff --git a/libraries/controllers/src/controllers/Pose.cpp b/libraries/controllers/src/controllers/Pose.cpp
index 6bcea82720..0c0de95bb4 100644
--- a/libraries/controllers/src/controllers/Pose.cpp
+++ b/libraries/controllers/src/controllers/Pose.cpp
@@ -71,9 +71,8 @@ namespace controller {
     }
 
     Pose Pose::postTransform(const glm::mat4& mat) const {
-        glm::mat4 original = getMat4();
+        glm::mat4 original = ::createMatFromQuatAndPos(rotation, translation);
         glm::mat4 result = original * mat;
-
         auto translationOut = ::extractTranslation(result);
         auto rotationOut = ::glmExtractRotation(result);
         auto velocityOut = velocity + glm::cross(angularVelocity, translation - translationOut); // warning: this may be completely wrong
diff --git a/libraries/controllers/src/controllers/Pose.h b/libraries/controllers/src/controllers/Pose.h
index 9e5371ce59..a6d1360f9f 100644
--- a/libraries/controllers/src/controllers/Pose.h
+++ b/libraries/controllers/src/controllers/Pose.h
@@ -43,8 +43,6 @@ namespace controller {
         Pose transform(const glm::mat4& mat) const;
         Pose postTransform(const glm::mat4& mat) const;
 
-        glm::mat4 getMat4() const { return ::createMatFromQuatAndPos(rotation, translation); }
-
         static QScriptValue toScriptValue(QScriptEngine* engine, const Pose& event);
         static void fromScriptValue(const QScriptValue& object, Pose& event);
     };
diff --git a/libraries/controllers/src/controllers/impl/Filter.cpp b/libraries/controllers/src/controllers/impl/Filter.cpp
index 74fa5d99cc..2cb35d85ce 100644
--- a/libraries/controllers/src/controllers/impl/Filter.cpp
+++ b/libraries/controllers/src/controllers/impl/Filter.cpp
@@ -93,8 +93,7 @@ bool Filter::parseSingleFloatParameter(const QJsonValue& parameters, const QStri
     return false;
 }
 
-// FIXME - we're not really using the name 
-bool Filter::parseVec3Parameter(const QJsonValue& parameters, const QString& name, glm::vec3& output) {
+bool Filter::parseVec3Parameter(const QJsonValue& parameters, glm::vec3& output) {
     if (parameters.isDouble()) {
         output = glm::vec3(parameters.toDouble());
         return true;
@@ -163,7 +162,7 @@ bool Filter::parseMat4Parameter(const QJsonValue& parameters, glm::mat4& output)
     return false;
 }
 
-bool Filter::parseQuatParameter(const QJsonValue& parameters, const QString& name, glm::quat& output) {
+bool Filter::parseQuatParameter(const QJsonValue& parameters, glm::quat& output) {
     if (parameters.isObject()) {
         auto objectParameters = parameters.toObject();
         if (objectParameters.contains("w") && 
diff --git a/libraries/controllers/src/controllers/impl/Filter.h b/libraries/controllers/src/controllers/impl/Filter.h
index 73b0aa06f5..cde8f991b7 100644
--- a/libraries/controllers/src/controllers/impl/Filter.h
+++ b/libraries/controllers/src/controllers/impl/Filter.h
@@ -46,8 +46,8 @@ namespace controller {
         static Factory& getFactory() { return _factory; }
 
         static bool parseSingleFloatParameter(const QJsonValue& parameters, const QString& name, float& output);
-        static bool parseVec3Parameter(const QJsonValue& parameters, const QString& name, glm::vec3& output);
-        static bool parseQuatParameter(const QJsonValue& parameters, const QString& name, glm::quat& output);
+        static bool parseVec3Parameter(const QJsonValue& parameters, glm::vec3& output);
+        static bool parseQuatParameter(const QJsonValue& parameters, glm::quat& output);
         static bool parseMat4Parameter(const QJsonValue& parameters, glm::mat4& output);
     protected:
         static Factory _factory;
diff --git a/libraries/controllers/src/controllers/impl/filters/RotateFilter.cpp b/libraries/controllers/src/controllers/impl/filters/RotateFilter.cpp
deleted file mode 100644
index b5a94ba492..0000000000
--- a/libraries/controllers/src/controllers/impl/filters/RotateFilter.cpp
+++ /dev/null
@@ -1,21 +0,0 @@
-//
-//  Created by Brad Hefta-Gaub 2017/04/11
-//  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 "RotateFilter.h"
-
-#include <QtCore/QJsonObject>
-#include <QtCore/QJsonArray>
-
-#include <StreamUtils.h>
-
-using namespace controller;
-
-bool RotateFilter::parseParameters(const QJsonValue& parameters) {
-    static const QString JSON_ROTATION = QStringLiteral("rotation");
-    return parseQuatParameter(parameters, JSON_ROTATION, _rotation);
-}
diff --git a/libraries/controllers/src/controllers/impl/filters/RotateFilter.h b/libraries/controllers/src/controllers/impl/filters/RotateFilter.h
index a5c3b10655..ee2e081393 100644
--- a/libraries/controllers/src/controllers/impl/filters/RotateFilter.h
+++ b/libraries/controllers/src/controllers/impl/filters/RotateFilter.h
@@ -25,11 +25,10 @@ public:
     virtual float apply(float value) const override { return value; }
 
     virtual Pose apply(Pose value) const override {
-        glm::quat temp = _rotation;
-        return value.transform(glm::mat4(temp));
+        return value.transform(glm::mat4(glm::quat(_rotation)));
     }
 
-    virtual bool parseParameters(const QJsonValue& parameters) override;
+    virtual bool parseParameters(const QJsonValue& parameters) override { return parseQuatParameter(parameters, _rotation); }
 
 private:
     glm::quat _rotation;
diff --git a/libraries/controllers/src/controllers/impl/filters/TransformFilter.cpp b/libraries/controllers/src/controllers/impl/filters/TransformFilter.cpp
deleted file mode 100644
index 2b7b0ef21d..0000000000
--- a/libraries/controllers/src/controllers/impl/filters/TransformFilter.cpp
+++ /dev/null
@@ -1,20 +0,0 @@
-//
-//  Created by Bradley Austin Davis 2015/10/25
-//  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 "TransformFilter.h"
-
-#include <QtCore/QJsonObject>
-#include <QtCore/QJsonArray>
-
-#include <StreamUtils.h>
-
-using namespace controller;
-
-bool TransformFilter::parseParameters(const QJsonValue& parameters) {
-    return parseMat4Parameter(parameters, _transform);
-}
diff --git a/libraries/controllers/src/controllers/impl/filters/TransformFilter.h b/libraries/controllers/src/controllers/impl/filters/TransformFilter.h
index 9999fd03c7..263b70c9b4 100644
--- a/libraries/controllers/src/controllers/impl/filters/TransformFilter.h
+++ b/libraries/controllers/src/controllers/impl/filters/TransformFilter.h
@@ -22,15 +22,9 @@ public:
     TransformFilter() { }
     TransformFilter(glm::mat4 transform) : _transform(transform) {}
 
-    virtual float apply(float value) const override {
-        return value;
-    }
-
-    virtual Pose apply(Pose value) const override {
-        return value.transform(_transform);
-    }
-
-    virtual bool parseParameters(const QJsonValue& parameters) override;
+    virtual float apply(float value) const override { return value; }
+    virtual Pose apply(Pose value) const override { return value.transform(_transform); }
+    virtual bool parseParameters(const QJsonValue& parameters) override { return parseMat4Parameter(parameters, _transform); }
 
 private:
     glm::mat4 _transform;
diff --git a/libraries/controllers/src/controllers/impl/filters/TranslateFilter.cpp b/libraries/controllers/src/controllers/impl/filters/TranslateFilter.cpp
deleted file mode 100644
index e63afcdd9e..0000000000
--- a/libraries/controllers/src/controllers/impl/filters/TranslateFilter.cpp
+++ /dev/null
@@ -1,21 +0,0 @@
-//
-//  Created by Bradley Austin Davis 2015/10/25
-//  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 "TranslateFilter.h"
-
-#include <QtCore/QJsonObject>
-#include <QtCore/QJsonArray>
-
-#include <StreamUtils.h>
-
-using namespace controller;
-
-bool TranslateFilter::parseParameters(const QJsonValue& parameters) {
-    static const QString JSON_TRANSLATE = QStringLiteral("translate");
-    return parseVec3Parameter(parameters, JSON_TRANSLATE, _translate);
-}
diff --git a/libraries/controllers/src/controllers/impl/filters/TranslateFilter.h b/libraries/controllers/src/controllers/impl/filters/TranslateFilter.h
index d5ac417b98..eda2912a8a 100644
--- a/libraries/controllers/src/controllers/impl/filters/TranslateFilter.h
+++ b/libraries/controllers/src/controllers/impl/filters/TranslateFilter.h
@@ -22,15 +22,9 @@ public:
     TranslateFilter() { }
     TranslateFilter(glm::vec3 translate) : _translate(translate) {}
 
-    virtual float apply(float value) const override {
-        return value;
-    }
-
-    virtual Pose apply(Pose value) const override {
-        return value.transform(glm::translate(_translate));
-    }
-
-    virtual bool parseParameters(const QJsonValue& parameters) override;
+    virtual float apply(float value) const override { return value; }
+    virtual Pose apply(Pose value) const override { return value.transform(glm::translate(_translate)); }
+    virtual bool parseParameters(const QJsonValue& parameters) override { return parseVec3Parameter(parameters, _translate); }
 
 private:
     glm::vec3 _translate { 0.0f };

From 8ff1ca8af6b933e2cf1be30b97a1f6360388df09 Mon Sep 17 00:00:00 2001
From: ZappoMan <brad@highfidelity.io>
Date: Wed, 12 Apr 2017 16:11:06 -0700
Subject: [PATCH 06/38] swap the velocity components

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

diff --git a/libraries/controllers/src/controllers/Pose.cpp b/libraries/controllers/src/controllers/Pose.cpp
index 0c0de95bb4..fc0891799e 100644
--- a/libraries/controllers/src/controllers/Pose.cpp
+++ b/libraries/controllers/src/controllers/Pose.cpp
@@ -75,7 +75,7 @@ namespace controller {
         glm::mat4 result = original * mat;
         auto translationOut = ::extractTranslation(result);
         auto rotationOut = ::glmExtractRotation(result);
-        auto velocityOut = velocity + glm::cross(angularVelocity, translation - translationOut); // warning: this may be completely wrong
+        auto velocityOut = velocity + glm::cross(angularVelocity, translationOut - translation); // warning: this may be completely wrong
         auto angularVelocityOut = angularVelocity;
 
         Pose pose(translationOut,

From 4c212fb1e56450c57f4f70792fd3c3041fd77da4 Mon Sep 17 00:00:00 2001
From: Andrew Meadows <andrew@highfidelity.io>
Date: Mon, 10 Apr 2017 17:28:21 -0700
Subject: [PATCH 07/38] pull qApp spaghetti out of Avatar class

some ends of spaghetti get pushed into AvatarManger class
split Camera class into Camera base and FancyCamera derivation
Application::getCamera() returns Camera by refence instead of pointer
---
 interface/src/FancyCamera.h     | 3 ---
 interface/src/avatar/Avatar.cpp | 1 -
 2 files changed, 4 deletions(-)

diff --git a/interface/src/FancyCamera.h b/interface/src/FancyCamera.h
index cd231cd929..9ffa6cafd8 100644
--- a/interface/src/FancyCamera.h
+++ b/interface/src/FancyCamera.h
@@ -13,9 +13,6 @@
 
 #include "Camera.h"
 
-#include <EntityItem.h>
-
-// TODO: come up with a better name than "FancyCamera"
 class FancyCamera : public Camera {
     Q_OBJECT
 
diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp
index ce8ec44f6c..9360421417 100644
--- a/interface/src/avatar/Avatar.cpp
+++ b/interface/src/avatar/Avatar.cpp
@@ -59,7 +59,6 @@ namespace render {
         auto avatarPtr = static_pointer_cast<Avatar>(avatar);
         if (avatarPtr->isInitialized() && args) {
             PROFILE_RANGE_BATCH(*args->_batch, "renderAvatarPayload");
-            // TODO AVATARS_RENDERER: remove need for qApp
             avatarPtr->render(args);
         }
     }

From 65682a914da69a55ed7c75e1fd3a1165e345f291 Mon Sep 17 00:00:00 2001
From: Andrew Meadows <andrew@highfidelity.io>
Date: Fri, 14 Apr 2017 10:22:32 -0700
Subject: [PATCH 08/38] remove cufty AvatarManager LocalLights feature

---
 interface/src/Application.cpp          |  6 -----
 interface/src/avatar/Avatar.cpp        | 31 +++++------------------
 interface/src/avatar/AvatarManager.cpp | 35 --------------------------
 interface/src/avatar/AvatarManager.h   | 15 -----------
 4 files changed, 6 insertions(+), 81 deletions(-)

diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 39a4b8ee7c..8801989e44 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -5451,12 +5451,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
     entityScriptingInterface->setPacketSender(&_entityEditSender);
     entityScriptingInterface->setEntityTree(getEntities()->getTree());
 
-    // AvatarManager has some custom types
-    AvatarManager::registerMetaTypes(scriptEngine);
-
-    // give the script engine to the RecordingScriptingInterface for its callbacks
-    DependencyManager::get<RecordingScriptingInterface>()->setScriptEngine(scriptEngine);
-
     if (property(hifi::properties::TEST).isValid()) {
         scriptEngine->registerGlobalObject("Test", TestScriptingInterface::getInstance());
     }
diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp
index 9360421417..a639b6880b 100644
--- a/interface/src/avatar/Avatar.cpp
+++ b/interface/src/avatar/Avatar.cpp
@@ -638,36 +638,17 @@ void Avatar::render(RenderArgs* renderArgs) {
     glm::vec3 toTarget = frustum.getPosition() - getPosition();
     float distanceToTarget = glm::length(toTarget);
 
-    {
-        fixupModelsInScene(renderArgs->_scene);
+    fixupModelsInScene(renderArgs->_scene);
 
-        if (renderArgs->_renderMode != RenderArgs::SHADOW_RENDER_MODE) {
-            // add local lights
-            const float BASE_LIGHT_DISTANCE = 2.0f;
-            const float LIGHT_FALLOFF_RADIUS = 0.01f;
-            const float LIGHT_EXPONENT = 1.0f;
-            const float LIGHT_CUTOFF = glm::radians(80.0f);
-            float distance = BASE_LIGHT_DISTANCE * getUniformScale();
-            glm::vec3 position = _skeletonModel->getTranslation();
-            glm::quat orientation = getOrientation();
-            foreach (const AvatarManager::LocalLight& light, DependencyManager::get<AvatarManager>()->getLocalLights()) {
-                glm::vec3 direction = orientation * light.direction;
-                DependencyManager::get<DeferredLightingEffect>()->addSpotLight(position - direction * distance,
-                    distance * 2.0f, light.color, 0.5f, LIGHT_FALLOFF_RADIUS, orientation, LIGHT_EXPONENT, LIGHT_CUTOFF);
-            }
-        }
-
-        bool renderBounding = Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes);
-        if (renderBounding && shouldRenderHead(renderArgs) && _skeletonModel->isRenderable()) {
-            PROFILE_RANGE_BATCH(batch, __FUNCTION__":skeletonBoundingCollisionShapes");
-            const float BOUNDING_SHAPE_ALPHA = 0.7f;
-            _skeletonModel->renderBoundingCollisionShapes(*renderArgs->_batch, getUniformScale(), BOUNDING_SHAPE_ALPHA);
-        }
+    bool renderBounding = Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes);
+    if (renderBounding && shouldRenderHead(renderArgs) && _skeletonModel->isRenderable()) {
+        PROFILE_RANGE_BATCH(batch, __FUNCTION__":skeletonBoundingCollisionShapes");
+        const float BOUNDING_SHAPE_ALPHA = 0.7f;
+        _skeletonModel->renderBoundingCollisionShapes(*renderArgs->_batch, getUniformScale(), BOUNDING_SHAPE_ALPHA);
     }
 
     const float DISPLAYNAME_DISTANCE = 20.0f;
     setShowDisplayName(distanceToTarget < DISPLAYNAME_DISTANCE);
-
     if (!isMyAvatar() || renderArgs->_cameraMode != (int8_t)CAMERA_MODE_FIRST_PERSON) {
         auto& frustum = renderArgs->getViewFrustum();
         auto textPosition = getDisplayNamePosition();
diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp
index c4bcb67a16..ef57211d5c 100644
--- a/interface/src/avatar/AvatarManager.cpp
+++ b/interface/src/avatar/AvatarManager.cpp
@@ -50,23 +50,6 @@ static const quint64 MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS = USECS_PER_SECOND /
 // We add _myAvatar into the hash with all the other AvatarData, and we use the default NULL QUid as the key.
 const QUuid MY_AVATAR_KEY;  // NULL key
 
-static QScriptValue localLightToScriptValue(QScriptEngine* engine, const AvatarManager::LocalLight& light) {
-    QScriptValue object = engine->newObject();
-    object.setProperty("direction", vec3toScriptValue(engine, light.direction));
-    object.setProperty("color", vec3toScriptValue(engine, light.color));
-    return object;
-}
-
-static void localLightFromScriptValue(const QScriptValue& value, AvatarManager::LocalLight& light) {
-    vec3FromScriptValue(value.property("direction"), light.direction);
-    vec3FromScriptValue(value.property("color"), light.color);
-}
-
-void AvatarManager::registerMetaTypes(QScriptEngine* engine) {
-    qScriptRegisterMetaType(engine, localLightToScriptValue, localLightFromScriptValue);
-    qScriptRegisterSequenceMetaType<QVector<AvatarManager::LocalLight> >(engine);
-}
-
 AvatarManager::AvatarManager(QObject* parent) :
     _avatarsToFade(),
     _myAvatar(std::make_shared<MyAvatar>(qApp->thread(), std::make_shared<Rig>()))
@@ -387,24 +370,6 @@ void AvatarManager::deleteAllAvatars() {
     }
 }
 
-void AvatarManager::setLocalLights(const QVector<AvatarManager::LocalLight>& localLights) {
-    if (QThread::currentThread() != thread()) {
-        QMetaObject::invokeMethod(this, "setLocalLights", Q_ARG(const QVector<AvatarManager::LocalLight>&, localLights));
-        return;
-    }
-    _localLights = localLights;
-}
-
-QVector<AvatarManager::LocalLight> AvatarManager::getLocalLights() const {
-    if (QThread::currentThread() != thread()) {
-        QVector<AvatarManager::LocalLight> result;
-        QMetaObject::invokeMethod(const_cast<AvatarManager*>(this), "getLocalLights", Qt::BlockingQueuedConnection,
-            Q_RETURN_ARG(QVector<AvatarManager::LocalLight>, result));
-        return result;
-    }
-    return _localLights;
-}
-
 void AvatarManager::getObjectsToRemoveFromPhysics(VectorOfMotionStates& result) {
     result.clear();
     result.swap(_motionStatesToRemoveFromPhysics);
diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h
index 45f1a597eb..074e0f4fba 100644
--- a/interface/src/avatar/AvatarManager.h
+++ b/interface/src/avatar/AvatarManager.h
@@ -64,16 +64,6 @@ public:
 
     bool shouldShowReceiveStats() const { return _shouldShowReceiveStats; }
 
-    class LocalLight {
-    public:
-        glm::vec3 color;
-        glm::vec3 direction;
-    };
-
-    Q_INVOKABLE void setLocalLights(const QVector<AvatarManager::LocalLight>& localLights);
-    Q_INVOKABLE QVector<AvatarManager::LocalLight> getLocalLights() const;
-
-
     void getObjectsToRemoveFromPhysics(VectorOfMotionStates& motionStates);
     void getObjectsToAddToPhysics(VectorOfMotionStates& motionStates);
     void getObjectsToChange(VectorOfMotionStates& motionStates);
@@ -116,8 +106,6 @@ private:
     std::shared_ptr<MyAvatar> _myAvatar;
     quint64 _lastSendAvatarDataTime = 0; // Controls MyAvatar send data rate.
 
-    QVector<AvatarManager::LocalLight> _localLights;
-
     bool _shouldShowReceiveStats = false;
 
     std::list<QPointer<AudioInjector>> _collisionInjectors;
@@ -129,7 +117,4 @@ private:
     bool _shouldRender { true };
 };
 
-Q_DECLARE_METATYPE(AvatarManager::LocalLight)
-Q_DECLARE_METATYPE(QVector<AvatarManager::LocalLight>)
-
 #endif // hifi_AvatarManager_h

From 63939728740a7fea32d30f21695b87a3b8b1190b Mon Sep 17 00:00:00 2001
From: Andrew Meadows <andrew@highfidelity.io>
Date: Fri, 14 Apr 2017 13:36:20 -0700
Subject: [PATCH 09/38] remove AvatarManager dependency from Avatar

---
 interface/src/avatar/Avatar.cpp      | 16 ++++++++++------
 interface/src/avatar/Avatar.h        |  3 ++-
 interface/src/avatar/AvatarManager.h |  6 +-----
 3 files changed, 13 insertions(+), 12 deletions(-)

diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp
index a639b6880b..84638f8008 100644
--- a/interface/src/avatar/Avatar.cpp
+++ b/interface/src/avatar/Avatar.cpp
@@ -28,7 +28,6 @@
 #include <VariantMapToScriptValue.h>
 #include <DebugDraw.h>
 
-#include "AvatarManager.h"
 #include "AvatarMotionState.h"
 #include "Camera.h"
 #include "Menu.h"
@@ -73,6 +72,12 @@ namespace render {
     }
 }
 
+// static
+bool showReceiveStats = false;
+void Avatar::setShowReceiveStats(bool receiveStats) {
+    showReceiveStats = receiveStats;
+}
+
 Avatar::Avatar(QThread* thread, RigPointer rig) :
     AvatarData(),
     _skeletonOffset(0.0f),
@@ -582,7 +587,6 @@ void Avatar::render(RenderArgs* renderArgs) {
         bool havePosition, haveRotation;
 
         if (_handState & LEFT_HAND_POINTING_FLAG) {
-
             if (_handState & IS_FINGER_POINTING_FLAG) {
                 int leftIndexTip = getJointIndex("LeftHandIndex4");
                 int leftIndexTipJoint = getJointIndex("LeftHandIndex3");
@@ -809,7 +813,7 @@ Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& view, const g
 void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const glm::vec3& textPosition) const {
     PROFILE_RANGE_BATCH(batch, __FUNCTION__);
 
-    bool shouldShowReceiveStats = DependencyManager::get<AvatarManager>()->shouldShowReceiveStats() && !isMyAvatar();
+    bool shouldShowReceiveStats = showReceiveStats && !isMyAvatar();
 
     // If we have nothing to draw, or it's totally transparent, or it's too close or behind the camera, return
     static const float CLIP_DISTANCE = 0.2f;
@@ -1473,16 +1477,16 @@ QList<QVariant> Avatar::getSkeleton() {
 
 void Avatar::addToScene(AvatarSharedPointer myHandle, const render::ScenePointer& scene) {
     if (scene) {
-        render::Transaction transaction;
         auto nodelist = DependencyManager::get<NodeList>();
         if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderAvatars()
             && !nodelist->isIgnoringNode(getSessionUUID())
             && !nodelist->isRadiusIgnoringNode(getSessionUUID())) {
+            render::Transaction transaction;
             addToScene(myHandle, scene, transaction);
+            scene->enqueueTransaction(transaction);
         }
-        scene->enqueueTransaction(transaction);
     } else {
-        qCWarning(interfaceapp) << "AvatarManager::addAvatar() : Unexpected null scene, possibly during application shutdown";
+        qCWarning(interfaceapp) << "Avatar::addAvatar() : Unexpected null scene, possibly during application shutdown";
     }
 }
 
diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h
index 14d1da530a..9bb8010b0d 100644
--- a/interface/src/avatar/Avatar.h
+++ b/interface/src/avatar/Avatar.h
@@ -66,6 +66,8 @@ class Avatar : public AvatarData {
     Q_PROPERTY(glm::vec3 skeletonOffset READ getSkeletonOffset WRITE setSkeletonOffset)
 
 public:
+    static void setShowReceiveStats(bool receiveStats);
+
     explicit Avatar(QThread* thread, RigPointer rig = nullptr);
     ~Avatar();
 
@@ -251,7 +253,6 @@ public slots:
     void setModelURLFinished(bool success);
 
 protected:
-    friend class AvatarManager;
 
     const float SMOOTH_TIME_POSITION = 0.125f;
     const float SMOOTH_TIME_ORIENTATION = 0.075f;
diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h
index 074e0f4fba..1832bc7126 100644
--- a/interface/src/avatar/AvatarManager.h
+++ b/interface/src/avatar/AvatarManager.h
@@ -62,8 +62,6 @@ public:
     void clearOtherAvatars();
     void deleteAllAvatars();
 
-    bool shouldShowReceiveStats() const { return _shouldShowReceiveStats; }
-
     void getObjectsToRemoveFromPhysics(VectorOfMotionStates& motionStates);
     void getObjectsToAddToPhysics(VectorOfMotionStates& motionStates);
     void getObjectsToChange(VectorOfMotionStates& motionStates);
@@ -85,7 +83,7 @@ public:
     float getMyAvatarSendRate() const { return _myAvatarSendRate.rate(); }
 
 public slots:
-    void setShouldShowReceiveStats(bool shouldShowReceiveStats) { _shouldShowReceiveStats = shouldShowReceiveStats; }
+    void setShouldShowReceiveStats(bool shouldShowReceiveStats) const { Avatar::setShowReceiveStats(shouldShowReceiveStats); }
     void updateAvatarRenderStatus(bool shouldRenderAvatars);
 
 private:
@@ -106,8 +104,6 @@ private:
     std::shared_ptr<MyAvatar> _myAvatar;
     quint64 _lastSendAvatarDataTime = 0; // Controls MyAvatar send data rate.
 
-    bool _shouldShowReceiveStats = false;
-
     std::list<QPointer<AudioInjector>> _collisionInjectors;
 
     RateCounter<> _myAvatarSendRate;

From 010d1dfa222286744959e47a6d4a8fb370d3198c Mon Sep 17 00:00:00 2001
From: Andrew Meadows <andrew@highfidelity.io>
Date: Fri, 14 Apr 2017 14:39:33 -0700
Subject: [PATCH 10/38] remove Menu dependency from Avatar class

---
 interface/src/avatar/Avatar.cpp        | 34 ++++++++++++++++++++------
 interface/src/avatar/Avatar.h          | 21 ++++++++++------
 interface/src/avatar/AvatarManager.cpp |  6 +++++
 3 files changed, 45 insertions(+), 16 deletions(-)

diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp
index 84638f8008..2234630504 100644
--- a/interface/src/avatar/Avatar.cpp
+++ b/interface/src/avatar/Avatar.cpp
@@ -30,7 +30,6 @@
 
 #include "AvatarMotionState.h"
 #include "Camera.h"
-#include "Menu.h"
 #include "InterfaceLogging.h"
 #include "SceneScriptingInterface.h"
 #include "SoftAttachmentModel.h"
@@ -78,6 +77,26 @@ void Avatar::setShowReceiveStats(bool receiveStats) {
     showReceiveStats = receiveStats;
 }
 
+// static
+bool renderMyLookAtVectors = false;
+bool renderOtherLookAtVectors = false;
+void Avatar::setShowLookAtVectors(bool showMine, bool showOthers) {
+    renderMyLookAtVectors = showMine;
+    renderOtherLookAtVectors = showOthers;
+}
+
+// static
+bool renderCollisionShapes = false;
+void Avatar::setRenderCollisionShapes(bool render) {
+    renderCollisionShapes = render;
+}
+
+// static
+bool showNamesAboveHeads = false;
+void Avatar::setShowNamesAboveHeads(bool show) {
+    showNamesAboveHeads = show;
+}
+
 Avatar::Avatar(QThread* thread, RigPointer rig) :
     AvatarData(),
     _skeletonOffset(0.0f),
@@ -354,7 +373,7 @@ void Avatar::simulate(float deltaTime, bool inView) {
             _smoothPositionTimer += deltaTime;
             if (_smoothPositionTimer < _smoothPositionTime) {
                 AvatarData::setPosition(
-                    lerp(_smoothPositionInitial, 
+                    lerp(_smoothPositionInitial,
                         _smoothPositionTarget,
                         easeInOutQuad(glm::clamp(_smoothPositionTimer / _smoothPositionTime, 0.0f, 1.0f)))
                 );
@@ -367,7 +386,7 @@ void Avatar::simulate(float deltaTime, bool inView) {
             _smoothOrientationTimer += deltaTime;
             if (_smoothOrientationTimer < _smoothOrientationTime) {
                 AvatarData::setOrientation(
-                    slerp(_smoothOrientationInitial, 
+                    slerp(_smoothOrientationInitial,
                         _smoothOrientationTarget,
                         easeInOutQuad(glm::clamp(_smoothOrientationTimer / _smoothOrientationTime, 0.0f, 1.0f)))
                 );
@@ -541,9 +560,9 @@ void Avatar::postUpdate(float deltaTime) {
 
     bool renderLookAtVectors;
     if (isMyAvatar()) {
-        renderLookAtVectors = Menu::getInstance()->isOptionChecked(MenuOption::RenderMyLookAtVectors);
+        renderLookAtVectors = renderMyLookAtVectors;
     } else {
-        renderLookAtVectors = Menu::getInstance()->isOptionChecked(MenuOption::RenderOtherLookAtVectors);
+        renderLookAtVectors = renderOtherLookAtVectors;
     }
 
     if (renderLookAtVectors) {
@@ -644,8 +663,7 @@ void Avatar::render(RenderArgs* renderArgs) {
 
     fixupModelsInScene(renderArgs->_scene);
 
-    bool renderBounding = Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes);
-    if (renderBounding && shouldRenderHead(renderArgs) && _skeletonModel->isRenderable()) {
+    if (renderCollisionShapes && shouldRenderHead(renderArgs) && _skeletonModel->isRenderable()) {
         PROFILE_RANGE_BATCH(batch, __FUNCTION__":skeletonBoundingCollisionShapes");
         const float BOUNDING_SHAPE_ALPHA = 0.7f;
         _skeletonModel->renderBoundingCollisionShapes(*renderArgs->_batch, getUniformScale(), BOUNDING_SHAPE_ALPHA);
@@ -1269,7 +1287,7 @@ float Avatar::getPelvisFloatingHeight() const {
 }
 
 void Avatar::setShowDisplayName(bool showDisplayName) {
-    if (!Menu::getInstance()->isOptionChecked(MenuOption::NamesAboveHeads)) {
+    if (!showNamesAboveHeads) {
         _displayNameAlpha = 0.0f;
         return;
     }
diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h
index 9bb8010b0d..116e5aad11 100644
--- a/interface/src/avatar/Avatar.h
+++ b/interface/src/avatar/Avatar.h
@@ -67,6 +67,9 @@ class Avatar : public AvatarData {
 
 public:
     static void setShowReceiveStats(bool receiveStats);
+    static void setShowLookAtVectors(bool showMine, bool showOthers);
+    static void setRenderCollisionShapes(bool render);
+    static void setShowNamesAboveHeads(bool show);
 
     explicit Avatar(QThread* thread, RigPointer rig = nullptr);
     ~Avatar();
@@ -240,6 +243,13 @@ public:
 
         return (lerpValue*(4.0f - 2.0f * lerpValue) - 1.0f);
     }
+    float getBoundingRadius() const;
+
+    void addToScene(AvatarSharedPointer self, const render::ScenePointer& scene);
+    void ensureInScene(AvatarSharedPointer self, const render::ScenePointer& scene);
+    bool isInScene() const { return render::Item::isValidID(_renderItemID); }
+
+    void setMotionState(AvatarMotionState* motionState);
 
 public slots:
 
@@ -261,8 +271,6 @@ protected:
     QString _empty{};
     virtual void maybeUpdateSessionDisplayNameFromTransport(const QString& sessionDisplayName) override { _sessionDisplayName = sessionDisplayName; } // don't use no-op setter!
 
-    void setMotionState(AvatarMotionState* motionState);
-
     SkeletonModelPointer _skeletonModel;
     glm::vec3 _skeletonOffset;
     std::vector<std::shared_ptr<Model>> _attachmentModels;
@@ -316,16 +324,13 @@ protected:
     ThreadSafeValueCache<glm::vec3> _rightPalmPositionCache { glm::vec3() };
     ThreadSafeValueCache<glm::quat> _rightPalmRotationCache { glm::quat() };
 
-    void addToScene(AvatarSharedPointer self, const render::ScenePointer& scene);
-    void ensureInScene(AvatarSharedPointer self, const render::ScenePointer& scene);
-    bool isInScene() const { return render::Item::isValidID(_renderItemID); }
-
     // Some rate tracking support
     RateCounter<> _simulationRate;
     RateCounter<> _simulationInViewRate;
     RateCounter<> _skeletonModelSimulationRate;
     RateCounter<> _jointDataSimulationRate;
 
+<<<<<<< 4318cce04a59543d80a9364c86aab79408dcb50e
     // Smoothing data for blending from one position/orientation to another on remote agents.
     float _smoothPositionTime;
     float _smoothPositionTimer;
@@ -336,6 +341,8 @@ protected:
     glm::quat _smoothOrientationInitial;
     glm::quat _smoothOrientationTarget;
 
+=======
+>>>>>>> remove Menu dependency from Avatar class
 private:
     class AvatarEntityDataHash {
     public:
@@ -355,8 +362,6 @@ private:
     bool _isLookAtTarget { false };
     bool _isAnimatingScale { false };
 
-    float getBoundingRadius() const;
-
     static int _jointConesID;
 
     int _voiceSphereID;
diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp
index ef57211d5c..35a5681213 100644
--- a/interface/src/avatar/AvatarManager.cpp
+++ b/interface/src/avatar/AvatarManager.cpp
@@ -147,6 +147,12 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
     ViewFrustum cameraView;
     qApp->copyDisplayViewFrustum(cameraView);
 
+    // HACK: update Avatar namespace settings
+    Avatar::setShowLookAtVectors(
+            Menu::getInstance()->isOptionChecked(MenuOption::RenderMyLookAtVectors),
+            Menu::getInstance()->isOptionChecked(MenuOption::RenderOtherLookAtVectors));
+    Avatar::setRenderCollisionShapes(Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes));
+
     std::priority_queue<AvatarPriority> sortedAvatars;
     AvatarData::sortAvatars(avatarList, cameraView, sortedAvatars,
 

From aceac12398674418786cc99165bc01556d5b0b27 Mon Sep 17 00:00:00 2001
From: Andrew Meadows <andrew@highfidelity.io>
Date: Tue, 18 Apr 2017 13:36:33 -0700
Subject: [PATCH 11/38] use 'using' instead of 'typedef'

---
 libraries/physics/src/PhysicsEngine.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h
index 9f2f1aff5c..07de0e7b5c 100644
--- a/libraries/physics/src/PhysicsEngine.h
+++ b/libraries/physics/src/PhysicsEngine.h
@@ -41,8 +41,8 @@ public:
     void* _b; // ObjectMotionState pointer
 };
 
-typedef std::map<ContactKey, ContactInfo> ContactMap;
-typedef std::vector<Collision> CollisionEvents;
+using ContactMap = std::map<ContactKey, ContactInfo>;
+using CollisionEvents = std::vector<Collision>;
 
 class PhysicsEngine {
 public:

From 2441536de387637b48531fcbec6121d68206d968 Mon Sep 17 00:00:00 2001
From: Andrew Meadows <andrew@highfidelity.io>
Date: Tue, 18 Apr 2017 13:37:36 -0700
Subject: [PATCH 12/38] remove Avatar dependency on  AvatarMotionState

---
 interface/src/avatar/Avatar.cpp            | 25 ++++---
 interface/src/avatar/Avatar.h              | 14 ++--
 interface/src/avatar/AvatarManager.cpp     | 76 ++++++++++++++--------
 interface/src/avatar/AvatarManager.h       |  4 +-
 interface/src/avatar/AvatarMotionState.cpp |  6 +-
 interface/src/avatar/AvatarMotionState.h   |  8 +--
 libraries/physics/src/PhysicsEngine.cpp    |  1 +
 7 files changed, 79 insertions(+), 55 deletions(-)

diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp
index 2234630504..91d8dfb447 100644
--- a/interface/src/avatar/Avatar.cpp
+++ b/interface/src/avatar/Avatar.cpp
@@ -149,11 +149,6 @@ Avatar::~Avatar() {
         });
     }
 
-    if (_motionState) {
-        delete _motionState;
-        _motionState = nullptr;
-    }
-
     auto geometryCache = DependencyManager::get<GeometryCache>();
     if (geometryCache) {
         geometryCache->releaseID(_nameRectGeometryID);
@@ -1202,8 +1197,8 @@ int Avatar::parseDataFromBuffer(const QByteArray& buffer) {
 
     const float MOVE_DISTANCE_THRESHOLD = 0.001f;
     _moving = glm::distance(oldPosition, getPosition()) > MOVE_DISTANCE_THRESHOLD;
-    if (_moving && _motionState) {
-        _motionState->addDirtyFlags(Simulation::DIRTY_POSITION);
+    if (_moving) {
+        addPhysicsFlags(Simulation::DIRTY_POSITION);
     }
     if (_moving || _hasNewJointData) {
         locationChanged();
@@ -1325,14 +1320,18 @@ void Avatar::getCapsule(glm::vec3& start, glm::vec3& end, float& radius) {
     radius = halfExtents.x;
 }
 
-void Avatar::setMotionState(AvatarMotionState* motionState) {
-    _motionState = motionState;
-}
-
 // virtual
 void Avatar::rebuildCollisionShape() {
-    if (_motionState) {
-        _motionState->addDirtyFlags(Simulation::DIRTY_SHAPE);
+    addPhysicsFlags(Simulation::DIRTY_SHAPE);
+}
+
+void Avatar::setPhysicsCallback(AvatarPhysicsCallback cb) {
+    _physicsCallback = cb;
+}
+
+void Avatar::addPhysicsFlags(uint32_t flags) {
+    if (_physicsCallback) {
+        _physicsCallback(flags);
     }
 }
 
diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h
index 116e5aad11..17b5ea926e 100644
--- a/interface/src/avatar/Avatar.h
+++ b/interface/src/avatar/Avatar.h
@@ -11,6 +11,7 @@
 #ifndef hifi_Avatar_h
 #define hifi_Avatar_h
 
+#include <functional>
 #include <glm/glm.hpp>
 #include <glm/gtc/quaternion.hpp>
 
@@ -48,9 +49,10 @@ enum ScreenTintLayer {
     NUM_SCREEN_TINT_LAYERS
 };
 
-class AvatarMotionState;
 class Texture;
 
+using AvatarPhysicsCallback = std::function<void(uint32_t)>;
+
 class Avatar : public AvatarData {
     Q_OBJECT
 
@@ -190,7 +192,7 @@ public:
     virtual void computeShapeInfo(ShapeInfo& shapeInfo);
     void getCapsule(glm::vec3& start, glm::vec3& end, float& radius);
 
-    AvatarMotionState* getMotionState() { return _motionState; }
+    //AvatarMotionState* getMotionState() { return _motionState; }
 
     using SpatiallyNestable::setPosition;
     virtual void setPosition(const glm::vec3& position) override;
@@ -248,8 +250,12 @@ public:
     void addToScene(AvatarSharedPointer self, const render::ScenePointer& scene);
     void ensureInScene(AvatarSharedPointer self, const render::ScenePointer& scene);
     bool isInScene() const { return render::Item::isValidID(_renderItemID); }
+    bool isMoving() const { return _moving; }
 
-    void setMotionState(AvatarMotionState* motionState);
+    //void setMotionState(AvatarMotionState* motionState);
+    void setPhysicsCallback(AvatarPhysicsCallback cb);
+    void addPhysicsFlags(uint32_t flags);
+    bool isInPhysicsSimulation() const { return _physicsCallback != nullptr; }
 
 public slots:
 
@@ -366,7 +372,7 @@ private:
 
     int _voiceSphereID;
 
-    AvatarMotionState* _motionState = nullptr;
+    AvatarPhysicsCallback _physicsCallback { nullptr };
 };
 
 #endif // hifi_Avatar_h
diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp
index 35a5681213..41cf797eba 100644
--- a/interface/src/avatar/AvatarManager.cpp
+++ b/interface/src/avatar/AvatarManager.cpp
@@ -74,6 +74,7 @@ AvatarManager::AvatarManager(QObject* parent) :
 }
 
 AvatarManager::~AvatarManager() {
+    assert(_motionStates.empty());
 }
 
 void AvatarManager::init() {
@@ -128,10 +129,9 @@ float AvatarManager::getAvatarUpdateRate(const QUuid& sessionID, const QString&
 
 float AvatarManager::getAvatarSimulationRate(const QUuid& sessionID, const QString& rateName) const {
     auto avatar = std::static_pointer_cast<Avatar>(getAvatarBySessionID(sessionID));
-    return avatar ? avatar->getSimulationRate(rateName) : 0.0f; 
+    return avatar ? avatar->getSimulationRate(rateName) : 0.0f;
 }
 
-
 void AvatarManager::updateOtherAvatars(float deltaTime) {
     // lock the hash for read to check the size
     QReadLocker lock(&_hashLock);
@@ -189,16 +189,15 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
         if (_shouldRender) {
             avatar->ensureInScene(avatar, qApp->getMain3DScene());
         }
-        if (!avatar->getMotionState()) {
+        if (!avatar->isInPhysicsSimulation()) {
             ShapeInfo shapeInfo;
             avatar->computeShapeInfo(shapeInfo);
             btCollisionShape* shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShape(shapeInfo));
             if (shape) {
-                // don't add to the simulation now, instead put it on a list to be added later
-                AvatarMotionState* motionState = new AvatarMotionState(avatar.get(), shape);
-                avatar->setMotionState(motionState);
+                AvatarMotionState* motionState = new AvatarMotionState(avatar, shape);
+                avatar->setPhysicsCallback([=] (uint32_t flags) { motionState->addDirtyFlags(flags); });
+                _motionStates.insert(avatar.get(), motionState);
                 _motionStatesToAddToPhysics.insert(motionState);
-                _motionStatesThatMightUpdate.insert(motionState);
             }
         }
         avatar->animateScaleChanges(deltaTime);
@@ -283,30 +282,34 @@ void AvatarManager::simulateAvatarFades(float deltaTime) {
     const float MIN_FADE_SCALE = MIN_AVATAR_SCALE;
 
     QReadLocker locker(&_hashLock);
-    QVector<AvatarSharedPointer>::iterator itr = _avatarsToFade.begin();
-    while (itr != _avatarsToFade.end()) {
-        auto avatar = std::static_pointer_cast<Avatar>(*itr);
+    QVector<AvatarSharedPointer>::iterator avatarItr = _avatarsToFade.begin();
+    while (avatarItr != _avatarsToFade.end()) {
+        auto avatar = std::static_pointer_cast<Avatar>(*avatarItr);
         avatar->setTargetScale(avatar->getUniformScale() * SHRINK_RATE);
         avatar->animateScaleChanges(deltaTime);
         if (avatar->getTargetScale() <= MIN_FADE_SCALE) {
-            // fading to zero is such a rare event we push unique transaction for each one
+            // fading to zero is such a rare event we push a unique transaction for each
             if (avatar->isInScene()) {
                 const render::ScenePointer& scene = qApp->getMain3DScene();
                 render::Transaction transaction;
-                avatar->removeFromScene(*itr, scene, transaction);
+                avatar->removeFromScene(*avatarItr, scene, transaction);
                 scene->enqueueTransaction(transaction);
             }
 
-            // only remove from _avatarsToFade if we're sure its motionState has been removed from PhysicsEngine
-            if (_motionStatesToRemoveFromPhysics.empty()) {
-                itr = _avatarsToFade.erase(itr);
-            } else {
-                ++itr;
+            // delete the motionState
+            // TODO: use SharedPointer technology to make this happen automagically
+            assert(!avatar->isInPhysicsSimulation());
+            AvatarMotionStateMap::iterator motionStateItr = _motionStates.find(avatar.get());
+            if (motionStateItr != _motionStates.end()) {
+                delete *motionStateItr;
+                _motionStates.erase(motionStateItr);
             }
+
+            avatarItr = _avatarsToFade.erase(avatarItr);
         } else {
             const bool inView = true; // HACK
             avatar->simulate(deltaTime, inView);
-            ++itr;
+            ++avatarItr;
         }
     }
 }
@@ -315,19 +318,24 @@ AvatarSharedPointer AvatarManager::newSharedAvatar() {
     return std::make_shared<Avatar>(qApp->thread(), std::make_shared<Rig>());
 }
 
+void AvatarManager::removeAvatarFromPhysicsSimulation(Avatar* avatar) {
+    assert(avatar);
+    avatar->setPhysicsCallback(nullptr);
+    AvatarMotionStateMap::iterator itr = _motionStates.find(avatar);
+    if (itr != _motionStates.end()) {
+        AvatarMotionState* motionState = *itr;
+        _motionStatesToAddToPhysics.remove(motionState);
+        _motionStatesToRemoveFromPhysics.push_back(motionState);
+    }
+}
+
 void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) {
     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.
     auto avatar = std::static_pointer_cast<Avatar>(removedAvatar);
-
-    AvatarMotionState* motionState = avatar->getMotionState();
-    if (motionState) {
-        _motionStatesThatMightUpdate.remove(motionState);
-        _motionStatesToAddToPhysics.remove(motionState);
-        _motionStatesToRemoveFromPhysics.push_back(motionState);
-    }
+    removeAvatarFromPhysicsSimulation(avatar.get());
 
     if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble) {
         emit DependencyManager::get<UsersScriptingInterface>()->enteredIgnoreRadius();
@@ -362,11 +370,21 @@ void AvatarManager::clearOtherAvatars() {
             ++avatarIterator;
         }
     }
+    assert(scene);
     scene->enqueueTransaction(transaction);
     _myAvatar->clearLookAtTargetAvatar();
 }
 
 void AvatarManager::deleteAllAvatars() {
+    // delete motionStates
+    // TODO: use shared_ptr technology to make this work automagically
+    AvatarMotionStateMap::iterator motionStateItr = _motionStates.begin();
+    while (motionStateItr != _motionStates.end()) {
+        delete *motionStateItr;
+        ++motionStateItr;
+    }
+    _motionStates.clear();
+
     QReadLocker locker(&_hashLock);
     AvatarHash::iterator avatarIterator =  _avatarHash.begin();
     while (avatarIterator != _avatarHash.end()) {
@@ -391,10 +409,12 @@ void AvatarManager::getObjectsToAddToPhysics(VectorOfMotionStates& result) {
 
 void AvatarManager::getObjectsToChange(VectorOfMotionStates& result) {
     result.clear();
-    for (auto state : _motionStatesThatMightUpdate) {
-        if (state->_dirtyFlags > 0) {
-            result.push_back(state);
+    AvatarMotionStateMap::iterator motionStateItr = _motionStates.begin();
+    while (motionStateItr != _motionStates.end()) {
+        if ((*motionStateItr)->getIncomingDirtyFlags() != 0) {
+            result.push_back(*motionStateItr);
         }
+        ++motionStateItr;
     }
 }
 
diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h
index 1832bc7126..5c8935417b 100644
--- a/interface/src/avatar/AvatarManager.h
+++ b/interface/src/avatar/AvatarManager.h
@@ -93,11 +93,13 @@ private:
     void simulateAvatarFades(float deltaTime);
 
     AvatarSharedPointer newSharedAvatar() override;
+    void removeAvatarFromPhysicsSimulation(Avatar* avatar);
     void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason) override;
 
     QVector<AvatarSharedPointer> _avatarsToFade;
 
-    QSet<AvatarMotionState*> _motionStatesThatMightUpdate;
+    using AvatarMotionStateMap = QMap<Avatar*, AvatarMotionState*>;
+    AvatarMotionStateMap _motionStates;
     VectorOfMotionStates _motionStatesToRemoveFromPhysics;
     SetOfMotionStates _motionStatesToAddToPhysics;
 
diff --git a/interface/src/avatar/AvatarMotionState.cpp b/interface/src/avatar/AvatarMotionState.cpp
index 335245670b..ffa99e3990 100644
--- a/interface/src/avatar/AvatarMotionState.cpp
+++ b/interface/src/avatar/AvatarMotionState.cpp
@@ -17,7 +17,7 @@
 #include "AvatarMotionState.h"
 #include "BulletUtil.h"
 
-AvatarMotionState::AvatarMotionState(Avatar* avatar, const btCollisionShape* shape) : ObjectMotionState(shape), _avatar(avatar) {
+AvatarMotionState::AvatarMotionState(AvatarSharedPointer avatar, const btCollisionShape* shape) : ObjectMotionState(shape), _avatar(avatar) {
     assert(_avatar);
     _type = MOTIONSTATE_TYPE_AVATAR;
     if (_shape) {
@@ -49,7 +49,7 @@ PhysicsMotionType AvatarMotionState::computePhysicsMotionType() const {
 // virtual and protected
 const btCollisionShape* AvatarMotionState::computeNewShape() {
     ShapeInfo shapeInfo;
-    _avatar->computeShapeInfo(shapeInfo);
+    std::static_pointer_cast<Avatar>(_avatar)->computeShapeInfo(shapeInfo);
     return getShapeManager()->getShape(shapeInfo);
 }
 
@@ -130,7 +130,7 @@ glm::vec3 AvatarMotionState::getObjectAngularVelocity() const {
 
 // virtual
 glm::vec3 AvatarMotionState::getObjectGravity() const {
-    return _avatar->getAcceleration();
+    return std::static_pointer_cast<Avatar>(_avatar)->getAcceleration();
 }
 
 // virtual
diff --git a/interface/src/avatar/AvatarMotionState.h b/interface/src/avatar/AvatarMotionState.h
index 98b2b69373..a8dd7327ca 100644
--- a/interface/src/avatar/AvatarMotionState.h
+++ b/interface/src/avatar/AvatarMotionState.h
@@ -20,7 +20,7 @@ class Avatar;
 
 class AvatarMotionState : public ObjectMotionState {
 public:
-    AvatarMotionState(Avatar* avatar, const btCollisionShape* shape);
+    AvatarMotionState(AvatarSharedPointer avatar, const btCollisionShape* shape);
 
     virtual PhysicsMotionType getMotionType() const override { return _motionType; }
 
@@ -74,11 +74,7 @@ protected:
     virtual bool isReadyToComputeShape() const override { return true; }
     virtual const btCollisionShape* computeNewShape() override;
 
-    // The AvatarMotionState keeps a RAW backpointer to its Avatar because all AvatarMotionState
-    // instances are "owned" by their corresponding Avatar instance and are deleted in the Avatar dtor.
-    // In other words, it is impossible for the Avatar to be deleted out from under its MotionState.
-    // In conclusion: weak pointer shennanigans would be pure overhead.
-    Avatar* _avatar; // do NOT use smartpointer here, no need for weakpointer
+    AvatarSharedPointer _avatar;
 
     uint32_t _dirtyFlags;
 };
diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp
index ca6889485a..87a15eb264 100644
--- a/libraries/physics/src/PhysicsEngine.cpp
+++ b/libraries/physics/src/PhysicsEngine.cpp
@@ -220,6 +220,7 @@ void PhysicsEngine::removeObjects(const SetOfMotionStates& objects) {
             body->setMotionState(nullptr);
             delete body;
         }
+        object->clearIncomingDirtyFlags();
     }
 }
 

From caf6a77baf15c8f6cacd420ef5c273f651b8b093 Mon Sep 17 00:00:00 2001
From: Andrew Meadows <andrew@highfidelity.io>
Date: Tue, 18 Apr 2017 16:35:32 -0700
Subject: [PATCH 13/38] repair bad merge during rebase

---
 interface/src/FancyCamera.h   | 2 ++
 interface/src/avatar/Avatar.h | 3 ---
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/interface/src/FancyCamera.h b/interface/src/FancyCamera.h
index 9ffa6cafd8..66f7a07dbd 100644
--- a/interface/src/FancyCamera.h
+++ b/interface/src/FancyCamera.h
@@ -13,6 +13,8 @@
 
 #include "Camera.h"
 
+#include <EntityTypes.h>
+
 class FancyCamera : public Camera {
     Q_OBJECT
 
diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h
index 17b5ea926e..e777460ecb 100644
--- a/interface/src/avatar/Avatar.h
+++ b/interface/src/avatar/Avatar.h
@@ -336,7 +336,6 @@ protected:
     RateCounter<> _skeletonModelSimulationRate;
     RateCounter<> _jointDataSimulationRate;
 
-<<<<<<< 4318cce04a59543d80a9364c86aab79408dcb50e
     // Smoothing data for blending from one position/orientation to another on remote agents.
     float _smoothPositionTime;
     float _smoothPositionTimer;
@@ -347,8 +346,6 @@ protected:
     glm::quat _smoothOrientationInitial;
     glm::quat _smoothOrientationTarget;
 
-=======
->>>>>>> remove Menu dependency from Avatar class
 private:
     class AvatarEntityDataHash {
     public:

From 72f5860538de8f3467b432d2141b53e8e09f2a5d Mon Sep 17 00:00:00 2001
From: Andrew Meadows <andrew@highfidelity.io>
Date: Wed, 19 Apr 2017 10:37:49 -0700
Subject: [PATCH 14/38] restore RecordingScriptingInterface to Application

---
 interface/src/Application.cpp | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 8801989e44..5fd0b30d01 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -5451,6 +5451,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
     entityScriptingInterface->setPacketSender(&_entityEditSender);
     entityScriptingInterface->setEntityTree(getEntities()->getTree());
 
+    // give the script engine to the RecordingScriptingInterface for its callbacks
+    DependencyManager::get<RecordingScriptingInterface>()->setScriptEngine(scriptEngine);
+
     if (property(hifi::properties::TEST).isValid()) {
         scriptEngine->registerGlobalObject("Test", TestScriptingInterface::getInstance());
     }

From 823f3a73c6b96d1afee64304e7afd8a66fe355ae Mon Sep 17 00:00:00 2001
From: Andrew Meadows <andrew@highfidelity.io>
Date: Wed, 19 Apr 2017 11:00:29 -0700
Subject: [PATCH 15/38] remove commented out cruft

---
 interface/src/avatar/Avatar.h | 2 --
 1 file changed, 2 deletions(-)

diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h
index e777460ecb..b323954049 100644
--- a/interface/src/avatar/Avatar.h
+++ b/interface/src/avatar/Avatar.h
@@ -192,8 +192,6 @@ public:
     virtual void computeShapeInfo(ShapeInfo& shapeInfo);
     void getCapsule(glm::vec3& start, glm::vec3& end, float& radius);
 
-    //AvatarMotionState* getMotionState() { return _motionState; }
-
     using SpatiallyNestable::setPosition;
     virtual void setPosition(const glm::vec3& position) override;
     using SpatiallyNestable::setOrientation;

From 00b05ed137bf064aaa21e23ed9c9deca2f80180c Mon Sep 17 00:00:00 2001
From: Andrew Meadows <andrew@highfidelity.io>
Date: Fri, 21 Apr 2017 09:31:33 -0700
Subject: [PATCH 16/38] connect menu options to avatar render features

---
 interface/src/Menu.cpp                 | 11 +++---
 interface/src/Menu.h                   |  8 ++---
 interface/src/avatar/Avatar.cpp        | 46 +++++++++++---------------
 interface/src/avatar/Avatar.h          | 10 +++---
 interface/src/avatar/AvatarManager.cpp |  6 ----
 interface/src/avatar/AvatarManager.h   |  1 -
 6 files changed, 36 insertions(+), 46 deletions(-)

diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp
index 8754951317..97e566309f 100644
--- a/interface/src/Menu.cpp
+++ b/interface/src/Menu.cpp
@@ -504,11 +504,14 @@ Menu::Menu() {
 #endif
 
     addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AvatarReceiveStats, 0, false,
-        avatarManager.data(), SLOT(setShouldShowReceiveStats(bool)));
+        avatar.get(), SLOT(setShowReceiveStats(bool)));
+    addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowBoundingCollisionShapes, 0, false,
+        avatar.get(), SLOT(setShowCollisionShapes(bool)));
+    addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowMyLookAtVectors, 0, false,
+        avatar.get(), SLOT(setShowMyLookAtVectors(bool)));
+    addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowOtherLookAtVectors, 0, false,
+        avatar.get(), SLOT(setShowOtherLookAtVectors(bool)));
 
-    addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderBoundingCollisionShapes);
-    addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderMyLookAtVectors, 0, false);
-    addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderOtherLookAtVectors, 0, false);
     addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::FixGaze, 0, false);
     addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawDefaultPose, 0, false,
         avatar.get(), SLOT(setEnableDebugDrawDefaultPose(bool)));
diff --git a/interface/src/Menu.h b/interface/src/Menu.h
index eeffcac7ca..d46c2736a4 100644
--- a/interface/src/Menu.h
+++ b/interface/src/Menu.h
@@ -146,9 +146,6 @@ namespace MenuOption {
     const QString Quit =  "Quit";
     const QString ReloadAllScripts = "Reload All Scripts";
     const QString ReloadContent = "Reload Content (Clears all caches)";
-    const QString RenderBoundingCollisionShapes = "Show Bounding Collision Shapes";
-    const QString RenderMyLookAtVectors = "Show My Eye Vectors";
-    const QString RenderOtherLookAtVectors = "Show Other Eye Vectors";
     const QString RenderMaxTextureMemory = "Maximum Texture Memory";
     const QString RenderMaxTextureAutomatic = "Automatic Texture Memory";
     const QString RenderMaxTexture4MB = "4 MB";
@@ -174,8 +171,11 @@ namespace MenuOption {
     const QString SendWrongDSConnectVersion = "Send wrong DS connect version";
     const QString SendWrongProtocolVersion = "Send wrong protocol version";
     const QString SetHomeLocation = "Set Home Location";
-    const QString ShowDSConnectTable = "Show Domain Connection Timing";
     const QString ShowBordersEntityNodes = "Show Entity Nodes";
+    const QString ShowBoundingCollisionShapes = "Show Bounding Collision Shapes";
+    const QString ShowDSConnectTable = "Show Domain Connection Timing";
+    const QString ShowMyLookAtVectors = "Show My Eye Vectors";
+    const QString ShowOtherLookAtVectors = "Show Other Eye Vectors";
     const QString ShowRealtimeEntityStats = "Show Realtime Entity Stats";
     const QString StandingHMDSensorMode = "Standing HMD Sensor Mode";
     const QString SimulateEyeTracking = "Simulate";
diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp
index 91d8dfb447..1e759ed396 100644
--- a/interface/src/avatar/Avatar.cpp
+++ b/interface/src/avatar/Avatar.cpp
@@ -71,28 +71,27 @@ namespace render {
     }
 }
 
-// static
-bool showReceiveStats = false;
+static bool showReceiveStats = false;
 void Avatar::setShowReceiveStats(bool receiveStats) {
     showReceiveStats = receiveStats;
 }
 
-// static
-bool renderMyLookAtVectors = false;
-bool renderOtherLookAtVectors = false;
-void Avatar::setShowLookAtVectors(bool showMine, bool showOthers) {
-    renderMyLookAtVectors = showMine;
-    renderOtherLookAtVectors = showOthers;
+static bool showMyLookAtVectors = false;
+void Avatar::setShowMyLookAtVectors(bool showMine) {
+    showMyLookAtVectors = showMine;
 }
 
-// static
-bool renderCollisionShapes = false;
-void Avatar::setRenderCollisionShapes(bool render) {
-    renderCollisionShapes = render;
+static bool showOtherLookAtVectors = false;
+void Avatar::setShowOtherLookAtVectors(bool showOthers) {
+    showOtherLookAtVectors = showOthers;
 }
 
-// static
-bool showNamesAboveHeads = false;
+static bool showCollisionShapes = false;
+void Avatar::setShowCollisionShapes(bool render) {
+    showCollisionShapes = render;
+}
+
+static bool showNamesAboveHeads = false;
 void Avatar::setShowNamesAboveHeads(bool show) {
     showNamesAboveHeads = show;
 }
@@ -553,14 +552,7 @@ void Avatar::updateRenderItem(render::Transaction& transaction) {
 
 void Avatar::postUpdate(float deltaTime) {
 
-    bool renderLookAtVectors;
-    if (isMyAvatar()) {
-        renderLookAtVectors = renderMyLookAtVectors;
-    } else {
-        renderLookAtVectors = renderOtherLookAtVectors;
-    }
-
-    if (renderLookAtVectors) {
+    if (isMyAvatar() ? showMyLookAtVectors : showOtherLookAtVectors) {
         const float EYE_RAY_LENGTH = 10.0;
         const glm::vec4 BLUE(0.0f, 0.0f, 1.0f, 1.0f);
         const glm::vec4 RED(1.0f, 0.0f, 0.0f, 1.0f);
@@ -653,17 +645,18 @@ void Avatar::render(RenderArgs* renderArgs) {
         return;
     }
 
-    glm::vec3 toTarget = frustum.getPosition() - getPosition();
-    float distanceToTarget = glm::length(toTarget);
-
     fixupModelsInScene(renderArgs->_scene);
 
-    if (renderCollisionShapes && shouldRenderHead(renderArgs) && _skeletonModel->isRenderable()) {
+    if (showCollisionShapes && shouldRenderHead(renderArgs) && _skeletonModel->isRenderable()) {
         PROFILE_RANGE_BATCH(batch, __FUNCTION__":skeletonBoundingCollisionShapes");
         const float BOUNDING_SHAPE_ALPHA = 0.7f;
         _skeletonModel->renderBoundingCollisionShapes(*renderArgs->_batch, getUniformScale(), BOUNDING_SHAPE_ALPHA);
     }
 
+#if 0 ///  -------------- REMOVED FOR NOW --------------
+    // removed CPU calculations as per removal of menu option
+    glm::vec3 toTarget = frustum.getPosition() - getPosition();
+    float distanceToTarget = glm::length(toTarget);
     const float DISPLAYNAME_DISTANCE = 20.0f;
     setShowDisplayName(distanceToTarget < DISPLAYNAME_DISTANCE);
     if (!isMyAvatar() || renderArgs->_cameraMode != (int8_t)CAMERA_MODE_FIRST_PERSON) {
@@ -673,6 +666,7 @@ void Avatar::render(RenderArgs* renderArgs) {
             renderDisplayName(batch, frustum, textPosition);
         }
     }
+#endif
 }
 
 glm::quat Avatar::computeRotationFromBodyToWorldUp(float proportion) const {
diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h
index b323954049..b2b0120b9a 100644
--- a/interface/src/avatar/Avatar.h
+++ b/interface/src/avatar/Avatar.h
@@ -68,11 +68,6 @@ class Avatar : public AvatarData {
     Q_PROPERTY(glm::vec3 skeletonOffset READ getSkeletonOffset WRITE setSkeletonOffset)
 
 public:
-    static void setShowReceiveStats(bool receiveStats);
-    static void setShowLookAtVectors(bool showMine, bool showOthers);
-    static void setRenderCollisionShapes(bool render);
-    static void setShowNamesAboveHeads(bool show);
-
     explicit Avatar(QThread* thread, RigPointer rig = nullptr);
     ~Avatar();
 
@@ -256,6 +251,11 @@ public:
     bool isInPhysicsSimulation() const { return _physicsCallback != nullptr; }
 
 public slots:
+    void setShowReceiveStats(bool receiveStats);
+    void setShowMyLookAtVectors(bool showMine);
+    void setShowOtherLookAtVectors(bool showOthers);
+    void setShowCollisionShapes(bool render);
+    void setShowNamesAboveHeads(bool show);
 
     // FIXME - these should be migrated to use Pose data instead
     // thread safe, will return last valid palm from cache
diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp
index 41cf797eba..ec22c3403a 100644
--- a/interface/src/avatar/AvatarManager.cpp
+++ b/interface/src/avatar/AvatarManager.cpp
@@ -147,12 +147,6 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
     ViewFrustum cameraView;
     qApp->copyDisplayViewFrustum(cameraView);
 
-    // HACK: update Avatar namespace settings
-    Avatar::setShowLookAtVectors(
-            Menu::getInstance()->isOptionChecked(MenuOption::RenderMyLookAtVectors),
-            Menu::getInstance()->isOptionChecked(MenuOption::RenderOtherLookAtVectors));
-    Avatar::setRenderCollisionShapes(Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes));
-
     std::priority_queue<AvatarPriority> sortedAvatars;
     AvatarData::sortAvatars(avatarList, cameraView, sortedAvatars,
 
diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h
index 5c8935417b..e5b2117beb 100644
--- a/interface/src/avatar/AvatarManager.h
+++ b/interface/src/avatar/AvatarManager.h
@@ -83,7 +83,6 @@ public:
     float getMyAvatarSendRate() const { return _myAvatarSendRate.rate(); }
 
 public slots:
-    void setShouldShowReceiveStats(bool shouldShowReceiveStats) const { Avatar::setShowReceiveStats(shouldShowReceiveStats); }
     void updateAvatarRenderStatus(bool shouldRenderAvatars);
 
 private:

From 442080dec7134ba01c38703f7cae2b9c7bc74d54 Mon Sep 17 00:00:00 2001
From: Andrew Meadows <andrew@highfidelity.io>
Date: Fri, 21 Apr 2017 09:32:07 -0700
Subject: [PATCH 17/38] remove unused RenderArgs::_cameraMode hack

---
 interface/src/Application.cpp     | 1 -
 libraries/shared/src/RenderArgs.h | 3 +--
 2 files changed, 1 insertion(+), 3 deletions(-)

diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 5fd0b30d01..dff50fd616 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -5119,7 +5119,6 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se
             QMutexLocker viewLocker(&_viewMutex);
             renderArgs->setViewFrustum(_displayViewFrustum);
         }
-        renderArgs->_cameraMode = (int8_t)theCamera.getMode(); // HACK
         renderArgs->_scene = getMain3DScene();
         _renderEngine->getRenderContext()->args = renderArgs;
 
diff --git a/libraries/shared/src/RenderArgs.h b/libraries/shared/src/RenderArgs.h
index 10a9a20287..9d81913078 100644
--- a/libraries/shared/src/RenderArgs.h
+++ b/libraries/shared/src/RenderArgs.h
@@ -130,8 +130,7 @@ public:
     bool _enableTexturing { true };
 
     RenderDetails _details;
-    render::ScenePointer _scene; // HACK
-    int8_t _cameraMode { -1 }; // HACK
+    render::ScenePointer _scene;
 };
 
 #endif // hifi_RenderArgs_h

From 75b563d5982b5fa0651c40ad2ae4dcc4acf831ec Mon Sep 17 00:00:00 2001
From: Andrew Meadows <andrew@highfidelity.io>
Date: Fri, 21 Apr 2017 11:07:12 -0700
Subject: [PATCH 18/38] restore rendering of avatar receive stats

---
 interface/src/Application.cpp        |  1 +
 interface/src/avatar/Avatar.cpp      | 27 +++++++++++++--------------
 interface/src/avatar/Avatar.h        |  6 +++++-
 libraries/avatars/src/AvatarData.cpp |  2 --
 libraries/avatars/src/AvatarData.h   |  3 ---
 libraries/shared/src/RenderArgs.h    |  1 +
 6 files changed, 20 insertions(+), 20 deletions(-)

diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index dff50fd616..5fd0b30d01 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -5119,6 +5119,7 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se
             QMutexLocker viewLocker(&_viewMutex);
             renderArgs->setViewFrustum(_displayViewFrustum);
         }
+        renderArgs->_cameraMode = (int8_t)theCamera.getMode(); // HACK
         renderArgs->_scene = getMain3DScene();
         _renderEngine->getRenderContext()->args = renderArgs;
 
diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp
index 1e759ed396..3bd4c663d2 100644
--- a/interface/src/avatar/Avatar.cpp
+++ b/interface/src/avatar/Avatar.cpp
@@ -653,20 +653,19 @@ void Avatar::render(RenderArgs* renderArgs) {
         _skeletonModel->renderBoundingCollisionShapes(*renderArgs->_batch, getUniformScale(), BOUNDING_SHAPE_ALPHA);
     }
 
-#if 0 ///  -------------- REMOVED FOR NOW --------------
-    // removed CPU calculations as per removal of menu option
-    glm::vec3 toTarget = frustum.getPosition() - getPosition();
-    float distanceToTarget = glm::length(toTarget);
-    const float DISPLAYNAME_DISTANCE = 20.0f;
-    setShowDisplayName(distanceToTarget < DISPLAYNAME_DISTANCE);
-    if (!isMyAvatar() || renderArgs->_cameraMode != (int8_t)CAMERA_MODE_FIRST_PERSON) {
-        auto& frustum = renderArgs->getViewFrustum();
-        auto textPosition = getDisplayNamePosition();
-        if (frustum.pointIntersectsFrustum(textPosition)) {
-            renderDisplayName(batch, frustum, textPosition);
+    if (showReceiveStats || showNamesAboveHeads) {
+        glm::vec3 toTarget = frustum.getPosition() - getPosition();
+        float distanceToTarget = glm::length(toTarget);
+        const float DISPLAYNAME_DISTANCE = 20.0f;
+        updateDisplayNameAlpha(distanceToTarget < DISPLAYNAME_DISTANCE);
+        if (!isMyAvatar() || renderArgs->_cameraMode != (int8_t)CAMERA_MODE_FIRST_PERSON) {
+            auto& frustum = renderArgs->getViewFrustum();
+            auto textPosition = getDisplayNamePosition();
+            if (frustum.pointIntersectsFrustum(textPosition)) {
+                renderDisplayName(batch, frustum, textPosition);
+            }
         }
     }
-#endif
 }
 
 glm::quat Avatar::computeRotationFromBodyToWorldUp(float proportion) const {
@@ -1275,8 +1274,8 @@ float Avatar::getPelvisFloatingHeight() const {
     return -_skeletonModel->getBindExtents().minimum.y;
 }
 
-void Avatar::setShowDisplayName(bool showDisplayName) {
-    if (!showNamesAboveHeads) {
+void Avatar::updateDisplayNameAlpha(bool showDisplayName) {
+    if (!(showNamesAboveHeads || showReceiveStats)) {
         _displayNameAlpha = 0.0f;
         return;
     }
diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h
index b2b0120b9a..192de146b9 100644
--- a/interface/src/avatar/Avatar.h
+++ b/interface/src/avatar/Avatar.h
@@ -150,7 +150,7 @@ public:
     virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override;
     virtual void setAttachmentData(const QVector<AttachmentData>& attachmentData) override;
 
-    void setShowDisplayName(bool showDisplayName);
+    void updateDisplayNameAlpha(bool showDisplayName);
     virtual void setSessionDisplayName(const QString& sessionDisplayName) override { }; // no-op
 
     virtual int parseDataFromBuffer(const QByteArray& buffer) override;
@@ -368,6 +368,10 @@ private:
     int _voiceSphereID;
 
     AvatarPhysicsCallback _physicsCallback { nullptr };
+
+    float _displayNameTargetAlpha { 1.0f };
+    float _displayNameAlpha { 1.0f };
+
 };
 
 #endif // hifi_Avatar_h
diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp
index 1427ce6359..55c8cc3b65 100644
--- a/libraries/avatars/src/AvatarData.cpp
+++ b/libraries/avatars/src/AvatarData.cpp
@@ -63,8 +63,6 @@ AvatarData::AvatarData() :
     _keyState(NO_KEY_DOWN),
     _forceFaceTrackerConnected(false),
     _headData(NULL),
-    _displayNameTargetAlpha(1.0f),
-    _displayNameAlpha(1.0f),
     _errorLogExpiry(0),
     _owningAvatarMixer(),
     _targetVelocity(0.0f)
diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h
index 545a5f1f8c..f325b6bce7 100644
--- a/libraries/avatars/src/AvatarData.h
+++ b/libraries/avatars/src/AvatarData.h
@@ -692,9 +692,6 @@ protected:
     QString _sessionDisplayName { };
     QUrl cannonicalSkeletonModelURL(const QUrl& empty) const;
 
-    float _displayNameTargetAlpha;
-    float _displayNameAlpha;
-
     QHash<QString, int> _jointIndices; ///< 1-based, since zero is returned for missing keys
     QStringList _jointNames; ///< in order of depth-first traversal
 
diff --git a/libraries/shared/src/RenderArgs.h b/libraries/shared/src/RenderArgs.h
index 9d81913078..f44d736e1a 100644
--- a/libraries/shared/src/RenderArgs.h
+++ b/libraries/shared/src/RenderArgs.h
@@ -131,6 +131,7 @@ public:
 
     RenderDetails _details;
     render::ScenePointer _scene;
+    int8_t _cameraMode { -1 };
 };
 
 #endif // hifi_RenderArgs_h

From aa90a6bd028e729060fd1ead9bb99c51a67ebfe8 Mon Sep 17 00:00:00 2001
From: Andrew Meadows <andrew@highfidelity.io>
Date: Fri, 21 Apr 2017 11:41:00 -0700
Subject: [PATCH 19/38] use static methods for setting avatar debug options

---
 interface/src/Menu.cpp        | 16 ++++++++--------
 interface/src/avatar/Avatar.h | 11 ++++++-----
 2 files changed, 14 insertions(+), 13 deletions(-)

diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp
index 97e566309f..1fbf3f9cf2 100644
--- a/interface/src/Menu.cpp
+++ b/interface/src/Menu.cpp
@@ -503,14 +503,14 @@ Menu::Menu() {
         qApp, SLOT(setActiveEyeTracker()));
 #endif
 
-    addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AvatarReceiveStats, 0, false,
-        avatar.get(), SLOT(setShowReceiveStats(bool)));
-    addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowBoundingCollisionShapes, 0, false,
-        avatar.get(), SLOT(setShowCollisionShapes(bool)));
-    addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowMyLookAtVectors, 0, false,
-        avatar.get(), SLOT(setShowMyLookAtVectors(bool)));
-    addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowOtherLookAtVectors, 0, false,
-        avatar.get(), SLOT(setShowOtherLookAtVectors(bool)));
+    action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AvatarReceiveStats, 0, false);
+    connect(action, &QAction::triggered, []{ Avatar::setShowReceiveStats(Menu::getInstance()->isOptionChecked(MenuOption::AvatarReceiveStats)); });
+    action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowBoundingCollisionShapes, 0, false);
+    connect(action, &QAction::triggered, []{ Avatar::setShowCollisionShapes(Menu::getInstance()->isOptionChecked(MenuOption::ShowBoundingCollisionShapes)); });
+    action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowMyLookAtVectors, 0, false);
+    connect(action, &QAction::triggered, []{ Avatar::setShowMyLookAtVectors(Menu::getInstance()->isOptionChecked(MenuOption::ShowMyLookAtVectors)); });
+    action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowOtherLookAtVectors, 0, false);
+    connect(action, &QAction::triggered, []{ Avatar::setShowOtherLookAtVectors(Menu::getInstance()->isOptionChecked(MenuOption::ShowOtherLookAtVectors)); });
 
     addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::FixGaze, 0, false);
     addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawDefaultPose, 0, false,
diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h
index 192de146b9..f86bf35bd9 100644
--- a/interface/src/avatar/Avatar.h
+++ b/interface/src/avatar/Avatar.h
@@ -68,6 +68,12 @@ class Avatar : public AvatarData {
     Q_PROPERTY(glm::vec3 skeletonOffset READ getSkeletonOffset WRITE setSkeletonOffset)
 
 public:
+    static void setShowReceiveStats(bool receiveStats);
+    static void setShowMyLookAtVectors(bool showMine);
+    static void setShowOtherLookAtVectors(bool showOthers);
+    static void setShowCollisionShapes(bool render);
+    static void setShowNamesAboveHeads(bool show);
+
     explicit Avatar(QThread* thread, RigPointer rig = nullptr);
     ~Avatar();
 
@@ -251,11 +257,6 @@ public:
     bool isInPhysicsSimulation() const { return _physicsCallback != nullptr; }
 
 public slots:
-    void setShowReceiveStats(bool receiveStats);
-    void setShowMyLookAtVectors(bool showMine);
-    void setShowOtherLookAtVectors(bool showOthers);
-    void setShowCollisionShapes(bool render);
-    void setShowNamesAboveHeads(bool show);
 
     // FIXME - these should be migrated to use Pose data instead
     // thread safe, will return last valid palm from cache

From 5cf233db3a7e0c16bc97d158f03dc74f053bdb97 Mon Sep 17 00:00:00 2001
From: Andrew Meadows <andrew@highfidelity.io>
Date: Fri, 21 Apr 2017 16:17:53 -0700
Subject: [PATCH 20/38] less labyrinth

---
 interface/src/Menu.cpp | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp
index 1fbf3f9cf2..4ce9085053 100644
--- a/interface/src/Menu.cpp
+++ b/interface/src/Menu.cpp
@@ -504,13 +504,13 @@ Menu::Menu() {
 #endif
 
     action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AvatarReceiveStats, 0, false);
-    connect(action, &QAction::triggered, []{ Avatar::setShowReceiveStats(Menu::getInstance()->isOptionChecked(MenuOption::AvatarReceiveStats)); });
+    connect(action, &QAction::triggered, [this]{ Avatar::setShowReceiveStats(isOptionChecked(MenuOption::AvatarReceiveStats)); });
     action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowBoundingCollisionShapes, 0, false);
-    connect(action, &QAction::triggered, []{ Avatar::setShowCollisionShapes(Menu::getInstance()->isOptionChecked(MenuOption::ShowBoundingCollisionShapes)); });
+    connect(action, &QAction::triggered, [this]{ Avatar::setShowCollisionShapes(isOptionChecked(MenuOption::ShowBoundingCollisionShapes)); });
     action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowMyLookAtVectors, 0, false);
-    connect(action, &QAction::triggered, []{ Avatar::setShowMyLookAtVectors(Menu::getInstance()->isOptionChecked(MenuOption::ShowMyLookAtVectors)); });
+    connect(action, &QAction::triggered, [this]{ Avatar::setShowMyLookAtVectors(isOptionChecked(MenuOption::ShowMyLookAtVectors)); });
     action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowOtherLookAtVectors, 0, false);
-    connect(action, &QAction::triggered, []{ Avatar::setShowOtherLookAtVectors(Menu::getInstance()->isOptionChecked(MenuOption::ShowOtherLookAtVectors)); });
+    connect(action, &QAction::triggered, [this]{ Avatar::setShowOtherLookAtVectors(isOptionChecked(MenuOption::ShowOtherLookAtVectors)); });
 
     addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::FixGaze, 0, false);
     addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawDefaultPose, 0, false,

From 32c367b644181ecc3db953f5a8df5914df66fd0c Mon Sep 17 00:00:00 2001
From: Andrew Meadows <andrew@highfidelity.io>
Date: Fri, 28 Apr 2017 10:18:15 -0700
Subject: [PATCH 21/38] use 'using' rather than 'typedef'

---
 libraries/physics/src/ObjectMotionState.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h
index 1d258560c3..4230f636b3 100644
--- a/libraries/physics/src/ObjectMotionState.h
+++ b/libraries/physics/src/ObjectMotionState.h
@@ -170,7 +170,7 @@ protected:
     bool _hasInternalKinematicChanges { false };
 };
 
-typedef QSet<ObjectMotionState*> SetOfMotionStates;
-typedef QVector<ObjectMotionState*> VectorOfMotionStates;
+using SetOfMotionStates = QSet<ObjectMotionState*>;
+using VectorOfMotionStates = QVector<ObjectMotionState*>;
 
 #endif // hifi_ObjectMotionState_h

From 994eed7b83a58b60a6a40f3e550b4366e9cd10aa Mon Sep 17 00:00:00 2001
From: Andrew Meadows <andrew@highfidelity.io>
Date: Fri, 28 Apr 2017 10:18:19 -0700
Subject: [PATCH 22/38] safer delete of AvatarMotionStates

---
 interface/src/avatar/AvatarManager.cpp | 52 ++++++++++----------------
 interface/src/avatar/AvatarManager.h   |  3 +-
 2 files changed, 22 insertions(+), 33 deletions(-)

diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp
index ec22c3403a..04ab1531ba 100644
--- a/interface/src/avatar/AvatarManager.cpp
+++ b/interface/src/avatar/AvatarManager.cpp
@@ -289,16 +289,6 @@ void AvatarManager::simulateAvatarFades(float deltaTime) {
                 avatar->removeFromScene(*avatarItr, scene, transaction);
                 scene->enqueueTransaction(transaction);
             }
-
-            // delete the motionState
-            // TODO: use SharedPointer technology to make this happen automagically
-            assert(!avatar->isInPhysicsSimulation());
-            AvatarMotionStateMap::iterator motionStateItr = _motionStates.find(avatar.get());
-            if (motionStateItr != _motionStates.end()) {
-                delete *motionStateItr;
-                _motionStates.erase(motionStateItr);
-            }
-
             avatarItr = _avatarsToFade.erase(avatarItr);
         } else {
             const bool inView = true; // HACK
@@ -312,24 +302,19 @@ AvatarSharedPointer AvatarManager::newSharedAvatar() {
     return std::make_shared<Avatar>(qApp->thread(), std::make_shared<Rig>());
 }
 
-void AvatarManager::removeAvatarFromPhysicsSimulation(Avatar* avatar) {
-    assert(avatar);
+void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) {
+    AvatarHashMap::handleRemovedAvatar(removedAvatar, removalReason);
+
+    // remove from physics
+    auto avatar = std::static_pointer_cast<Avatar>(removedAvatar);
     avatar->setPhysicsCallback(nullptr);
-    AvatarMotionStateMap::iterator itr = _motionStates.find(avatar);
+    AvatarMotionStateMap::iterator itr = _motionStates.find(avatar.get());
     if (itr != _motionStates.end()) {
         AvatarMotionState* motionState = *itr;
         _motionStatesToAddToPhysics.remove(motionState);
         _motionStatesToRemoveFromPhysics.push_back(motionState);
+        _motionStates.erase(itr);
     }
-}
-
-void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) {
-    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.
-    auto avatar = std::static_pointer_cast<Avatar>(removedAvatar);
-    removeAvatarFromPhysicsSimulation(avatar.get());
 
     if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble) {
         emit DependencyManager::get<UsersScriptingInterface>()->enteredIgnoreRadius();
@@ -370,14 +355,8 @@ void AvatarManager::clearOtherAvatars() {
 }
 
 void AvatarManager::deleteAllAvatars() {
-    // delete motionStates
-    // TODO: use shared_ptr technology to make this work automagically
-    AvatarMotionStateMap::iterator motionStateItr = _motionStates.begin();
-    while (motionStateItr != _motionStates.end()) {
-        delete *motionStateItr;
-        ++motionStateItr;
-    }
-    _motionStates.clear();
+    assert(_motionStates.empty()); // should have called clearOtherAvatars() before getting here
+    deleteMotionStates();
 
     QReadLocker locker(&_hashLock);
     AvatarHash::iterator avatarIterator =  _avatarHash.begin();
@@ -388,9 +367,18 @@ void AvatarManager::deleteAllAvatars() {
     }
 }
 
+void AvatarManager::deleteMotionStates() {
+    // delete motionstates that were removed from physics last frame
+    for (auto state : _motionStatesToDelete) {
+        delete state;
+    }
+    _motionStatesToDelete.clear();
+}
+
 void AvatarManager::getObjectsToRemoveFromPhysics(VectorOfMotionStates& result) {
-    result.clear();
-    result.swap(_motionStatesToRemoveFromPhysics);
+    deleteMotionStates();
+    result = _motionStatesToRemoveFromPhysics;
+    _motionStatesToDelete.swap(_motionStatesToRemoveFromPhysics);
 }
 
 void AvatarManager::getObjectsToAddToPhysics(VectorOfMotionStates& result) {
diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h
index e5b2117beb..c67088a4be 100644
--- a/interface/src/avatar/AvatarManager.h
+++ b/interface/src/avatar/AvatarManager.h
@@ -92,7 +92,7 @@ private:
     void simulateAvatarFades(float deltaTime);
 
     AvatarSharedPointer newSharedAvatar() override;
-    void removeAvatarFromPhysicsSimulation(Avatar* avatar);
+    void deleteMotionStates();
     void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason) override;
 
     QVector<AvatarSharedPointer> _avatarsToFade;
@@ -100,6 +100,7 @@ private:
     using AvatarMotionStateMap = QMap<Avatar*, AvatarMotionState*>;
     AvatarMotionStateMap _motionStates;
     VectorOfMotionStates _motionStatesToRemoveFromPhysics;
+    VectorOfMotionStates _motionStatesToDelete;
     SetOfMotionStates _motionStatesToAddToPhysics;
 
     std::shared_ptr<MyAvatar> _myAvatar;

From f60deb0cfcaffce2d06a73fadae17a34e9162f25 Mon Sep 17 00:00:00 2001
From: druiz17 <danteruiz11@yahoo.com>
Date: Sun, 30 Apr 2017 22:55:44 -0700
Subject: [PATCH 23/38] fixed the twitter link

---
 .../resources/qml/controls/TabletWebView.qml  | 24 +++++++++++++++----
 interface/resources/qml/controls/WebView.qml  |  1 +
 2 files changed, 21 insertions(+), 4 deletions(-)

diff --git a/interface/resources/qml/controls/TabletWebView.qml b/interface/resources/qml/controls/TabletWebView.qml
index e202d89060..67fb724ad0 100644
--- a/interface/resources/qml/controls/TabletWebView.qml
+++ b/interface/resources/qml/controls/TabletWebView.qml
@@ -28,6 +28,7 @@ Item {
     property alias webView: webview
     property alias profile: webview.profile
     property bool remove: false
+    property bool newPage: false
 
     
     property int currentPage: -1 // used as a model for repeater
@@ -144,9 +145,22 @@ Item {
         view.setEnabled(true);
     }
 
+    function isNewPageOpen() {
+	return (web.newPage && web.currentPage > 0);
+    }
+    
+    function shouldLoadUrl(url) {
+	switch (url) {
+	case "https://twitter.com/intent/sessions":
+	    return true;
+	}
+	return false;
+    }
     function urlAppend(url) {
-        if (removingPage) {
+	console.log(url);
+        if (removingPage || shouldLoadUrl(url) || isNewPageOpen()) {
             removingPage = false;
+	    web.newPage = false;
             return;
         }
         var lurl = decodeURIComponent(url)
@@ -156,7 +170,7 @@ Item {
         if (currentPage === -1 || (pagesModel.get(currentPage).webUrl !== lurl && !timer.running)) {
             timer.start();
             pagesModel.append({webUrl: lurl});
-        }
+        };
     }
 
     onCurrentPageChanged: {
@@ -228,6 +242,7 @@ Item {
             worldId: WebEngineScript.MainWorld
         }
 
+	property string urlTag: "noDownload=false";
         userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ]
 
         property string newUrl: ""
@@ -254,9 +269,9 @@ Item {
             keyboard.resetShiftMode(false);
             // Required to support clicking on "hifi://" links
             if (WebEngineView.LoadStartedStatus == loadRequest.status) {
-                urlAppend(loadRequest.url.toString());
+		var url = loadRequest.url.toString();
+                urlAppend(url);
                 loadingPage = true;
-                var url = loadRequest.url.toString();
                 if (urlHandler.canHandleUrl(url)) {
                     if (urlHandler.handleUrl(url)) {
                         root.stop();
@@ -270,6 +285,7 @@ Item {
         }
 
         onNewViewRequested: {
+	    web.newPage = true;
             request.openIn(webview);
         }
     }
diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml
index 04ff731a25..b954fbc11b 100644
--- a/interface/resources/qml/controls/WebView.qml
+++ b/interface/resources/qml/controls/WebView.qml
@@ -99,6 +99,7 @@ Item {
             // Required to support clicking on "hifi://" links
             if (WebEngineView.LoadStartedStatus == loadRequest.status) {
                 var url = loadRequest.url.toString();
+		console.log(url);
                 url = (url.indexOf("?") >= 0) ? url + urlTag : url + "?" + urlTag;
                 if (urlHandler.canHandleUrl(url)) {
                     if (urlHandler.handleUrl(url)) {

From 9f33af479dd97c7b86d0011a88f0e3a4bdd28ccc Mon Sep 17 00:00:00 2001
From: Dante Ruiz <dante@highfidelity.io>
Date: Mon, 1 May 2017 18:04:22 +0100
Subject: [PATCH 24/38] saving work

---
 .../resources/qml/controls/TabletWebView.qml  | 49 ++++++++++++++-----
 1 file changed, 36 insertions(+), 13 deletions(-)

diff --git a/interface/resources/qml/controls/TabletWebView.qml b/interface/resources/qml/controls/TabletWebView.qml
index 67fb724ad0..8fd92a0cb0 100644
--- a/interface/resources/qml/controls/TabletWebView.qml
+++ b/interface/resources/qml/controls/TabletWebView.qml
@@ -23,12 +23,15 @@ Item {
     property bool keyboardRaised: false
     property bool punctuationMode: false
     property bool isDesktop: false
+    property bool startingUp: true
     property bool removingPage: false
-    property bool loadingPage: false
     property alias webView: webview
     property alias profile: webview.profile
     property bool remove: false
-    property bool newPage: false
+    property bool windowClosed: false
+    property bool loadingStarted: false
+    property bool loadingFinished: false
+    property var urlList: []
 
     
     property int currentPage: -1 // used as a model for repeater
@@ -144,10 +147,6 @@ Item {
         view.setActiveFocusOnPress(true);
         view.setEnabled(true);
     }
-
-    function isNewPageOpen() {
-	return (web.newPage && web.currentPage > 0);
-    }
     
     function shouldLoadUrl(url) {
 	switch (url) {
@@ -156,30 +155,41 @@ Item {
 	}
 	return false;
     }
+    
     function urlAppend(url) {
 	console.log(url);
-        if (removingPage || shouldLoadUrl(url) || isNewPageOpen()) {
+        if (removingPage || shouldLoadUrl(url)) {
             removingPage = false;
-	    web.newPage = false;
             return;
         }
         var lurl = decodeURIComponent(url)
         if (lurl[lurl.length - 1] !== "/") {
             lurl = lurl + "/"
         }
-        if (currentPage === -1 || (pagesModel.get(currentPage).webUrl !== lurl && !timer.running)) {
+        console.log("-------> trying to append url <------------");
+        console.log(currentPage);
+        console.log(pagesModel.get(currentPage).webUrl !== lurl);
+        if (currentPage === -1 || (pagesModel.get(currentPage).webUrl !== lurl)) {
             timer.start();
+            console.log("---------> appending url <-------------");
             pagesModel.append({webUrl: lurl});
         };
     }
 
     onCurrentPageChanged: {
-        if (currentPage >= 0 && currentPage < pagesModel.count) {
+        if (currentPage >= 0 && currentPage < pagesModel.count && removingPage) {
             timer.start();
             webview.url = pagesModel.get(currentPage).webUrl;
             web.url = webview.url;
             web.address = webview.url;
+            removingPage = false;
+        } else if (startingUp) {
+            webview.url = pagesModel.get(currentPage).webUrl;
+            web.url = webview.url;
+            web.address = webview.url;
+            startingUp = false;
         }
+            
     }
 
     onUrlChanged: {
@@ -270,8 +280,9 @@ Item {
             // Required to support clicking on "hifi://" links
             if (WebEngineView.LoadStartedStatus == loadRequest.status) {
 		var url = loadRequest.url.toString();
-                urlAppend(url);
-                loadingPage = true;
+                web.urlList.push(url);
+                //urlAppend(url);
+                web.loadingStarted  = true;
                 if (urlHandler.canHandleUrl(url)) {
                     if (urlHandler.handleUrl(url)) {
                         root.stop();
@@ -282,10 +293,22 @@ Item {
             if (WebEngineView.LoadFailedStatus == loadRequest.status) {
                 console.log(" Tablet WebEngineView failed to laod url: " + loadRequest.url.toString());
             }
+
+            if (WebEngineView.LoadSucceededStatus == loadRequest.status) {
+                console.log
+                urlList = [];
+            }
+        }
+
+        onWindowCloseRequested: {
+            console.log("---------->requested to closeWindow <--------------");
         }
 
         onNewViewRequested: {
-	    web.newPage = true;
+            console.log("-----------> newViewRequested <--------------");
+            var currentUrl = webview.url;
+            console.log(currentUrl);
+            urlAppend(currentUrl);
             request.openIn(webview);
         }
     }

From c205bf0980a2d4450b5bcaf876cd68f11840406d Mon Sep 17 00:00:00 2001
From: Dante Ruiz <dante@highfidelity.io>
Date: Mon, 1 May 2017 21:23:38 +0100
Subject: [PATCH 25/38] working on cleaner navigation

---
 .../resources/qml/controls/TabletWebView.qml  | 105 +++++++-----------
 1 file changed, 40 insertions(+), 65 deletions(-)

diff --git a/interface/resources/qml/controls/TabletWebView.qml b/interface/resources/qml/controls/TabletWebView.qml
index 8fd92a0cb0..ca49dafe53 100644
--- a/interface/resources/qml/controls/TabletWebView.qml
+++ b/interface/resources/qml/controls/TabletWebView.qml
@@ -23,6 +23,7 @@ Item {
     property bool keyboardRaised: false
     property bool punctuationMode: false
     property bool isDesktop: false
+    property string initialPage: ""
     property bool startingUp: true
     property bool removingPage: false
     property alias webView: webview
@@ -32,6 +33,7 @@ Item {
     property bool loadingStarted: false
     property bool loadingFinished: false
     property var urlList: []
+    property var forwardList: []
 
     
     property int currentPage: -1 // used as a model for repeater
@@ -103,15 +105,15 @@ Item {
     }
         
     function goBack() {
-        if (webview.canGoBack && !isUrlLoaded(webview.url)) {
-            if (currentPage > 0) {
-                removingPage = true;
-                pagesModel.remove(currentPage);
-            }
+        if (webview.canGoBack) {
+            forwardList.push(webview.url);
             webview.goBack();
-        } else if (currentPage > 0) {
+        } else if (web.urlList.length > 0) {
             removingPage = true;
-            pagesModel.remove(currentPage);
+            var url = web.urlList.pop();
+            loadUrl(url);
+        } else if (fowardList.length == 1) {
+            console.log("--------------> going foward <---------------");
         }
     }
 
@@ -147,53 +149,43 @@ Item {
         view.setActiveFocusOnPress(true);
         view.setEnabled(true);
     }
-    
-    function shouldLoadUrl(url) {
-	switch (url) {
-	case "https://twitter.com/intent/sessions":
-	    return true;
-	}
-	return false;
+
+    function loadUrl(url) {
+        webview.url = url
+        web.url = webview.url;
+        web.address = webview.url;
     }
+
+    function onInitialPage(url) {
+        return (url === webview.url);
+    }
+        
     
     function urlAppend(url) {
-	console.log(url);
-        if (removingPage || shouldLoadUrl(url)) {
-            removingPage = false;
-            return;
-        }
         var lurl = decodeURIComponent(url)
         if (lurl[lurl.length - 1] !== "/") {
             lurl = lurl + "/"
         }
-        console.log("-------> trying to append url <------------");
-        console.log(currentPage);
-        console.log(pagesModel.get(currentPage).webUrl !== lurl);
-        if (currentPage === -1 || (pagesModel.get(currentPage).webUrl !== lurl)) {
-            timer.start();
-            console.log("---------> appending url <-------------");
-            pagesModel.append({webUrl: lurl});
-        };
+        web.urlList.push(url);
+        setBackButtonStatus();
     }
 
-    onCurrentPageChanged: {
-        if (currentPage >= 0 && currentPage < pagesModel.count && removingPage) {
-            timer.start();
-            webview.url = pagesModel.get(currentPage).webUrl;
-            web.url = webview.url;
-            web.address = webview.url;
-            removingPage = false;
-        } else if (startingUp) {
-            webview.url = pagesModel.get(currentPage).webUrl;
-            web.url = webview.url;
-            web.address = webview.url;
-            startingUp = false;
+    function setBackButtonStatus() {
+        if (web.urlList.length > 0 || webview.canGoBack) {
+            back.enabledColor = hifi.colors.darkGray;
+            back.enabled = true;
+        } else {
+            back.enabledColor = hifi.colors.baseGray;
+            back.enabled = false;
         }
-            
     }
 
     onUrlChanged: {
-        gotoPage(url)
+        loadUrl(url);
+        if (startingUp) {
+            web.initialPage = webview.url;
+            startingUp = false;
+        }
     }
 
     QtObject {
@@ -201,18 +193,7 @@ Item {
         WebChannel.id: "eventBridgeWrapper"
         property var eventBridge;
     }
-
-    Timer {
-        id: timer
-        interval: 200
-        running: false
-        repeat: false
-        onTriggered: timer.stop();
-    }
-
-        
-
-
+    
     WebEngineView {
         id: webview
         objectName: "webEngineView"
@@ -280,9 +261,6 @@ Item {
             // Required to support clicking on "hifi://" links
             if (WebEngineView.LoadStartedStatus == loadRequest.status) {
 		var url = loadRequest.url.toString();
-                web.urlList.push(url);
-                //urlAppend(url);
-                web.loadingStarted  = true;
                 if (urlHandler.canHandleUrl(url)) {
                     if (urlHandler.handleUrl(url)) {
                         root.stop();
@@ -295,19 +273,16 @@ Item {
             }
 
             if (WebEngineView.LoadSucceededStatus == loadRequest.status) {
-                console.log
-                urlList = [];
+                web.address = webview.url;
+                if (startingUp) {
+                    web.initialPage = webview.url;
+                    startingUp = false;
+                }
             }
         }
-
-        onWindowCloseRequested: {
-            console.log("---------->requested to closeWindow <--------------");
-        }
-
+        
         onNewViewRequested: {
-            console.log("-----------> newViewRequested <--------------");
             var currentUrl = webview.url;
-            console.log(currentUrl);
             urlAppend(currentUrl);
             request.openIn(webview);
         }

From 64b8237bd66d69ed2cb53d2728050c525f4780e1 Mon Sep 17 00:00:00 2001
From: Dante Ruiz <dante@highfidelity.io>
Date: Mon, 1 May 2017 22:45:13 +0100
Subject: [PATCH 26/38] fix twitter and facebook links

---
 .../resources/qml/controls/TabletWebView.qml      | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)

diff --git a/interface/resources/qml/controls/TabletWebView.qml b/interface/resources/qml/controls/TabletWebView.qml
index 42706d47c0..04e784e2ba 100644
--- a/interface/resources/qml/controls/TabletWebView.qml
+++ b/interface/resources/qml/controls/TabletWebView.qml
@@ -25,13 +25,9 @@ Item {
     property bool isDesktop: false
     property string initialPage: ""
     property bool startingUp: true
-    property bool removingPage: false
     property alias webView: webview
     property alias profile: webview.profile
     property bool remove: false
-    property bool windowClosed: false
-    property bool loadingStarted: false
-    property bool loadingFinished: false
     property var urlList: []
     property var forwardList: []
 
@@ -85,9 +81,12 @@ Item {
             id: displayUrl
             color: hifi.colors.baseGray
             font.pixelSize: 12
+            verticalAlignment: Text.AlignLeft
             anchors {
                 top: nav.bottom
                 horizontalCenter: parent.horizontalCenter;
+                left: parent.left
+                leftMargin: 20
             }
         }
 
@@ -116,15 +115,15 @@ Item {
             forwardList.push(webview.url);
             webview.goBack();
         } else if (web.urlList.length > 0) {
-            removingPage = true;
             var url = web.urlList.pop();
             loadUrl(url);
-        } else if (fowardList.length == 1) {
-            console.log("--------------> going foward <---------------");
+        } else if (web.forwardList.length > 0) {
+            var url = web.forwardList.pop();
+            loadUrl(url);
+            web.forwardList = [];
         }
     }
 
-
     function closeWebEngine() {
         if (remove) {
             web.destroy();

From a08b55d5f560e21373bca67d08ec938b096b5573 Mon Sep 17 00:00:00 2001
From: Dante Ruiz <dante@highfidelity.io>
Date: Mon, 1 May 2017 23:08:25 +0100
Subject: [PATCH 27/38] minimize diff

---
 interface/resources/qml/controls/WebView.qml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml
index b954fbc11b..04ff731a25 100644
--- a/interface/resources/qml/controls/WebView.qml
+++ b/interface/resources/qml/controls/WebView.qml
@@ -99,7 +99,6 @@ Item {
             // Required to support clicking on "hifi://" links
             if (WebEngineView.LoadStartedStatus == loadRequest.status) {
                 var url = loadRequest.url.toString();
-		console.log(url);
                 url = (url.indexOf("?") >= 0) ? url + urlTag : url + "?" + urlTag;
                 if (urlHandler.canHandleUrl(url)) {
                     if (urlHandler.handleUrl(url)) {

From 61b738e3cda63103c9b29b99c8c3ddac2831a192 Mon Sep 17 00:00:00 2001
From: howard-stearns <howard.stearns@gmail.com>
Date: Mon, 1 May 2017 15:35:17 -0700
Subject: [PATCH 28/38] announcment styling

---
 interface/resources/qml/hifi/Card.qml | 60 ++++++++++++++++++++++-----
 1 file changed, 49 insertions(+), 11 deletions(-)

diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml
index 0b34a8f9ac..6b1beaec3a 100644
--- a/interface/resources/qml/hifi/Card.qml
+++ b/interface/resources/qml/hifi/Card.qml
@@ -31,7 +31,7 @@ Item {
 
     property bool drillDownToPlace: false;
     property bool showPlace: isConcurrency;
-    property string messageColor: hifi.colors.blueAccent;
+    property string messageColor: lozenge.visible ? "white" : hifi.colors.blueAccent;
     property string timePhrase: pastTime(timestamp);
     property int onlineUsers: 0;
     property bool isConcurrency: action === 'concurrency';
@@ -115,7 +115,7 @@ Item {
         id: lobby;
         visible: !hasGif || (animation.status !== Image.Ready);
         width: parent.width - (isConcurrency ? 0 : (2 * smallMargin));
-        height: parent.height - messageHeight - (isConcurrency ? 0 : smallMargin);
+        height: parent.height -(isAnnouncement ? smallMargin : messageHeight) - (isConcurrency ? 0 : smallMargin);
         source: thumbnail || defaultThumbnail;
         fillMode: Image.PreserveAspectCrop;
         anchors {
@@ -160,7 +160,24 @@ Item {
             margins: textPadding;
         }
     }
+    Rectangle {
+        id: lozenge;
+        visible: isAnnouncement;
+        color: hifi.colors.redHighlight;
+        anchors.fill: infoRow;
+        radius: lozenge.height / 2.0;
+        border.width: lozengeHot.containsMouse ? 4 : 0;
+        border.color: "white";
+    }
     Row {
+        id: infoRow;
+        Image {
+            id: icon;
+            source: isAnnouncement ? "../../images/Announce-Blast.svg" : "../../images/snap-icon.svg";
+            width: 40;
+            height: 40;
+            visible: ((action === 'snapshot') || isAnnouncement) && (messageHeight >= 40);
+        }
         FiraSansRegular {
             id: users;
             visible: isConcurrency || isAnnouncement;
@@ -169,34 +186,42 @@ Item {
             color: messageColor;
             anchors.verticalCenter: message.verticalCenter;
         }
-        Image {
-            id: icon;
-            source: "../../images/snap-icon.svg"
-            width: 40;
-            height: 40;
-            visible: (action === 'snapshot') && (messageHeight >= 40);
-        }
         RalewayRegular {
             id: message;
+            visible: !isAnnouncement;
             text: isConcurrency ? ((onlineUsers === 1) ? "person" : "people") : (isAnnouncement ? "connections" : (drillDownToPlace ? "snapshots" : ("by " + userName)));
             size: textSizeSmall;
             color: messageColor;
             elide: Text.ElideRight; // requires a width to be specified`
             width: root.width - textPadding
-                - (users.visible ? users.width + parent.spacing : 0)
                 - (icon.visible ? icon.width + parent.spacing : 0)
+                - (users.visible ? users.width + parent.spacing : 0)
                 - (actionIcon.width + (2 * smallMargin));
             anchors {
                 bottom: parent.bottom;
                 bottomMargin: parent.spacing;
             }
         }
+        Column {
+            visible: isAnnouncement;
+            RalewayRegular {
+                text: "connections" + "   ";  // fixme: pluralize
+                size: textSizeSmall;
+                color: "white"; // fixme not needed? get rid of complication in messageColor?
+            }
+            RalewayRegular {
+                text: "are here now"; // fixme pluralize
+                size: textSizeSmall * 0.7;
+                color: "white"; //' fixme not needed? get rid of complication in messageColor?
+            }
+        }
         spacing: textPadding;
         height: messageHeight;
         anchors {
             bottom: parent.bottom;
             left: parent.left;
             leftMargin: textPadding;
+            bottomMargin: isAnnouncement ? textPadding : 0;
         }
     }
     // These two can be supplied to provide hover behavior.
@@ -214,6 +239,7 @@ Item {
     }
     StateImage {
         id: actionIcon;
+        visible: !isAnnouncement;
         imageURL: "../../images/info-icon-2-state.svg";
         size: 30;
         buttonState: messageArea.containsMouse ? 1 : 0;
@@ -223,13 +249,25 @@ Item {
             margins: smallMargin;
         }
     }
+    function go() {
+        goFunction(drillDownToPlace ? ("/places/" + placeName) : ("/user_stories/" + storyId));
+    }
     MouseArea {
         id: messageArea;
+        visible: !isAnnouncement;
         width: parent.width;
         height: messageHeight;
         anchors.top: lobby.bottom;
         acceptedButtons: Qt.LeftButton;
-        onClicked: goFunction(drillDownToPlace ? ("/places/" + placeName) : ("/user_stories/" + storyId));
+        onClicked: go();
+        hoverEnabled: true;
+    }
+    MouseArea {
+        id: lozengeHot;
+        visible: lozenge.visible;
+        anchors.fill: lozenge;
+        acceptedButtons: Qt.LeftButton;
+        onClicked: go();
         hoverEnabled: true;
     }
 }

From 545fd1355ff8cd043c44725b227995cfef576f22 Mon Sep 17 00:00:00 2001
From: howard-stearns <howard.stearns@gmail.com>
Date: Mon, 1 May 2017 15:45:26 -0700
Subject: [PATCH 29/38] cleanup

---
 interface/resources/qml/hifi/Card.qml | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml
index 6b1beaec3a..9617b41150 100644
--- a/interface/resources/qml/hifi/Card.qml
+++ b/interface/resources/qml/hifi/Card.qml
@@ -31,7 +31,7 @@ Item {
 
     property bool drillDownToPlace: false;
     property bool showPlace: isConcurrency;
-    property string messageColor: lozenge.visible ? "white" : hifi.colors.blueAccent;
+    property string messageColor: isAnnouncement ? "white" : hifi.colors.blueAccent;
     property string timePhrase: pastTime(timestamp);
     property int onlineUsers: 0;
     property bool isConcurrency: action === 'concurrency';
@@ -71,6 +71,10 @@ Item {
 
     property bool hasGif: imageUrl.indexOf('.gif') === (imageUrl.length - 4);
 
+    function pluralize(count, singular, optionalPlural) {
+        return (count === 1) ? singular : (optionalPlural || (singular + "s"));
+    }
+
     DropShadow {
         visible: isStacked;
         anchors.fill: shadow1;
@@ -189,7 +193,7 @@ Item {
         RalewayRegular {
             id: message;
             visible: !isAnnouncement;
-            text: isConcurrency ? ((onlineUsers === 1) ? "person" : "people") : (isAnnouncement ? "connections" : (drillDownToPlace ? "snapshots" : ("by " + userName)));
+            text: isConcurrency ? pluralize(onlineUsers, "person", "people") : (drillDownToPlace ? "snapshots" : ("by " + userName));
             size: textSizeSmall;
             color: messageColor;
             elide: Text.ElideRight; // requires a width to be specified`
@@ -205,14 +209,14 @@ Item {
         Column {
             visible: isAnnouncement;
             RalewayRegular {
-                text: "connections" + "   ";  // fixme: pluralize
+                text: pluralize(onlineUsers, "connection") + "   "; // hack padding
                 size: textSizeSmall;
-                color: "white"; // fixme not needed? get rid of complication in messageColor?
+                color: messageColor;
             }
             RalewayRegular {
-                text: "are here now"; // fixme pluralize
+                text: pluralize(onlineUsers, "is here now", "are here now");
                 size: textSizeSmall * 0.7;
-                color: "white"; //' fixme not needed? get rid of complication in messageColor?
+                color: messageColor;
             }
         }
         spacing: textPadding;

From ca5318f12176a6899685ed2cb2c8259196d6a63a Mon Sep 17 00:00:00 2001
From: howard-stearns <howard.stearns@gmail.com>
Date: Mon, 1 May 2017 15:57:22 -0700
Subject: [PATCH 30/38] remove debugging code

---
 interface/resources/qml/hifi/tablet/TabletAddressDialog.qml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml
index b7c0d24b24..7159b078ee 100644
--- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml
+++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml
@@ -280,7 +280,6 @@ StackView {
                         cardHeight: 163 + (2 * 4);
                         metaverseServerUrl: addressBarDialog.metaverseServerUrl;
                         labelText: 'HAPPENING NOW';
-                        //actions: 'concurrency,snapshot'; // uncomment this line instead of next to produce fake announcement data for testing.
                         actions: 'announcement';
                         filter: addressLine.text;
                         goFunction: goCard;

From ac86c134779c5ee356efb1e56aa7e9753cfd0ece Mon Sep 17 00:00:00 2001
From: Zach Fox <fox@highfidelity.io>
Date: Mon, 1 May 2017 12:18:06 -0700
Subject: [PATCH 31/38] Fix sharing; disable HiFi buttons independently

---
 scripts/system/html/js/SnapshotReview.js |  32 ++--
 scripts/system/snapshot.js               | 199 +++++++++++++----------
 2 files changed, 128 insertions(+), 103 deletions(-)

diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js
index 53f4d17930..b1a6bb2303 100644
--- a/scripts/system/html/js/SnapshotReview.js
+++ b/scripts/system/html/js/SnapshotReview.js
@@ -52,7 +52,7 @@ function clearImages() {
     imageCount = 0;
     idCounter = 0;
 }
-function addImage(image_data, isGifLoading, isShowingPreviousImages, canSharePreviousImages, hifiShareButtonsDisabled) {
+function addImage(image_data, isGifLoading, canShare, isShowingPreviousImages, blastButtonDisabled, hifiButtonDisabled) {
     if (!image_data.localPath) {
         return;
     }
@@ -80,22 +80,22 @@ function addImage(image_data, isGifLoading, isShowingPreviousImages, canSharePre
     if (isGif) {
         imageContainer.innerHTML += '<span class="gifLabel">GIF</span>';
     }
-    if (!isGifLoading && !isShowingPreviousImages) {
+    if (!isGifLoading && !isShowingPreviousImages && canShare) {
         shareForUrl(id);
-    } else if (isShowingPreviousImages && canSharePreviousImages) {
-        appendShareBar(id, image_data.story_id, isGif, hifiShareButtonsDisabled)
+    } else if (isShowingPreviousImages && canShare) {
+        appendShareBar(id, image_data.story_id, isGif, blastButtonDisabled, hifiButtonDisabled)
     }
 }
-function appendShareBar(divID, story_id, isGif, hifiShareButtonsDisabled) {
+function appendShareBar(divID, story_id, isGif, blastButtonDisabled, hifiButtonDisabled) {
     var story_url = "https://highfidelity.com/user_stories/" + story_id;
     var parentDiv = document.getElementById(divID);
     parentDiv.setAttribute('data-story-id', story_id);
-    document.getElementById(divID).appendChild(createShareBar(divID, isGif, story_url, hifiShareButtonsDisabled));
+    document.getElementById(divID).appendChild(createShareBar(divID, isGif, story_url, blastButtonDisabled, hifiButtonDisabled));
     if (divID === "p0") {
         selectImageToShare(divID, true);
     }
 }
-function createShareBar(parentID, isGif, shareURL, hifiShareButtonsDisabled) {
+function createShareBar(parentID, isGif, shareURL, blastButtonDisabled, hifiButtonDisabled) {
     var shareBar = document.createElement("div");
     shareBar.id = parentID + "shareBar";
     shareBar.className = "shareControls";
@@ -109,8 +109,8 @@ function createShareBar(parentID, isGif, shareURL, hifiShareButtonsDisabled) {
     var twitterButtonID = parentID + "twitterButton";
     shareBar.innerHTML += '' +
         '<div class="shareButtons" id="' + shareButtonsDivID + '" style="visibility:hidden">' +
-            '<input type="button"' + (hifiShareButtonsDisabled ? ' disabled' : '') + ' class="blastToConnections blueButton" id="' + blastToConnectionsButtonID + '" value="BLAST TO MY CONNECTIONS" onclick="blastToConnections(' + parentID + ', ' + isGif + ')" />' +
-            '<input type="button"' + (hifiShareButtonsDisabled ? ' disabled' : '') + ' class="shareWithEveryone" id="' + shareWithEveryoneButtonID + '" onclick="shareWithEveryone(' + parentID + ', ' + isGif + ')" />' +
+            '<input type="button"' + (blastButtonDisabled ? ' disabled' : '') + ' class="blastToConnections blueButton" id="' + blastToConnectionsButtonID + '" value="BLAST TO MY CONNECTIONS" onclick="blastToConnections(' + parentID + ', ' + isGif + ')" />' +
+            '<input type="button"' + (hifiButtonDisabled ? ' disabled' : '') + ' class="shareWithEveryone" id="' + shareWithEveryoneButtonID + '" onclick="shareWithEveryone(' + parentID + ', ' + isGif + ')" />' +
             '<a class="facebookButton" id="' + facebookButtonID + '" onclick="shareButtonClicked(' + parentID + ')" target="_blank" href="https://www.facebook.com/dialog/feed?app_id=1585088821786423&link=' + shareURL + '"></a>' +
             '<a class="twitterButton" id="' + twitterButtonID + '" onclick="shareButtonClicked(' + parentID + ')" target="_blank" href="https://twitter.com/intent/tweet?text=I%20just%20took%20a%20snapshot!&url=' + shareURL + '&via=highfidelity&hashtags=VR,HiFi"></a>' +
         '</div>' +
@@ -173,7 +173,6 @@ function blastToConnections(selectedID, isGif) {
     selectedID = selectedID.id; // `selectedID` is passed as an HTML object to these functions; we just want the ID
 
     document.getElementById(selectedID + "blastToConnectionsButton").disabled = true;
-    document.getElementById(selectedID + "shareWithEveryoneButton").disabled = true;
 
     EventBridge.emitWebEvent(JSON.stringify({
         type: "snapshot",
@@ -185,7 +184,6 @@ function blastToConnections(selectedID, isGif) {
 function shareWithEveryone(selectedID, isGif) {
     selectedID = selectedID.id; // `selectedID` is passed as an HTML object to these functions; we just want the ID
 
-    document.getElementById(selectedID + "blastToConnectionsButton").disabled = true;
     document.getElementById(selectedID + "shareWithEveryoneButton").disabled = true;
 
     EventBridge.emitWebEvent(JSON.stringify({
@@ -260,7 +258,7 @@ window.onload = function () {
                     var messageOptions = message.options;
                     imageCount = message.image_data.length;
                     message.image_data.forEach(function (element, idx, array) {
-                        addImage(element, true, true, message.canShare, message.image_data[idx].buttonDisabled);
+                        addImage(element, true, message.canShare, true, message.image_data[idx].blastButtonDisabled, message.image_data[idx].hifiButtonDisabled);
                     });
                     break;
                 case 'addImages':
@@ -274,7 +272,7 @@ window.onload = function () {
                             imageCount = message.image_data.length + 1; // "+1" for the GIF that'll finish processing soon
                             message.image_data.unshift({ localPath: messageOptions.loadingGifPath });
                             message.image_data.forEach(function (element, idx, array) {
-                                addImage(element, idx === 0, false, false);
+                                addImage(element, idx === 0, messageOptions.canShare, false);
                             });
                         } else {
                             var gifPath = message.image_data[0].localPath;
@@ -282,12 +280,14 @@ window.onload = function () {
                             p0img.src = gifPath;
 
                             paths[0] = gifPath;
-                            shareForUrl("p0");
+                            if (messageOptions.canShare) {
+                                shareForUrl("p0");
+                            }
                         }
                     } else {
                         imageCount = message.image_data.length;
                         message.image_data.forEach(function (element, idx, array) {
-                            addImage(element, false, false, false);
+                            addImage(element, false, messageOptions.canShare, false);
                         });
                     }
                     break;
@@ -329,6 +329,6 @@ function testInBrowser(isTestingSetupInstructions) {
     } else {
         imageCount = 1;
         //addImage({ localPath: 'http://lorempixel.com/553/255' });
-        addImage({ localPath: 'C:/Users/valef/Desktop/hifi-snap-by-zfox-on-2017-04-26_10-26-53.gif' }, false, true, true, false);
+        addImage({ localPath: 'C:/Users/valef/Desktop/hifi-snap-by-zfox-on-2017-04-26_10-26-53.gif' }, false, true, true, false, false);
     }
 }
diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js
index 1cc24b8265..d8635dcb03 100644
--- a/scripts/system/snapshot.js
+++ b/scripts/system/snapshot.js
@@ -117,13 +117,15 @@ function onMessage(message) {
                 setting: Settings.getValue("alsoTakeAnimatedSnapshot", true)
             }));
             if (Snapshot.getSnapshotsLocation() !== "") {
-                tablet.emitScriptEvent(JSON.stringify({
-                    type: "snapshot",
-                    action: "showPreviousImages",
-                    options: snapshotOptions,
-                    image_data: imageData,
-                    canShare: !isDomainOpen(Settings.getValue("previousSnapshotDomainID"))
-                }));
+                isDomainOpen(Settings.getValue("previousSnapshotDomainID"), function (canShare) {
+                    tablet.emitScriptEvent(JSON.stringify({
+                        type: "snapshot",
+                        action: "showPreviousImages",
+                        options: snapshotOptions,
+                        image_data: imageData,
+                        canShare: canShare
+                    }));
+                });                
             } else {
                 tablet.emitScriptEvent(JSON.stringify({
                     type: "snapshot",
@@ -131,10 +133,12 @@ function onMessage(message) {
                 }));
                 Settings.setValue("previousStillSnapPath", "");
                 Settings.setValue("previousStillSnapStoryID", "");
-                Settings.setValue("previousStillSnapSharingDisabled", false);
+                Settings.setValue("previousStillSnapBlastingDisabled", false);
+                Settings.setValue("previousStillSnapHifiSharingDisabled", false);
                 Settings.setValue("previousAnimatedSnapPath", "");
                 Settings.setValue("previousAnimatedSnapStoryID", "");
-                Settings.setValue("previousAnimatedSnapSharingDisabled", false);
+                Settings.setValue("previousAnimatedSnapBlastingDisabled", false);
+                Settings.setValue("previousAnimatedSnapHifiSharingDisabled", false);
             }
             break;
         case 'chooseSnapshotLocation':
@@ -180,9 +184,9 @@ function onMessage(message) {
             isLoggedIn = Account.isLoggedIn();
             storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(message.story_id), 1);
             if (message.isGif) {
-                Settings.setValue("previousAnimatedSnapSharingDisabled", true);
+                Settings.setValue("previousAnimatedSnapBlastingDisabled", true);
             } else {
-                Settings.setValue("previousStillSnapSharingDisabled", true);
+                Settings.setValue("previousStillSnapBlastingDisabled", true);
             }
 
             if (isLoggedIn) {
@@ -220,9 +224,9 @@ function onMessage(message) {
                             if (error || (response.status !== 'success')) {
                                 print("ERROR uploading announcement story: ", error || response.status);
                                 if (message.isGif) {
-                                    Settings.setValue("previousAnimatedSnapSharingDisabled", false);
+                                    Settings.setValue("previousAnimatedSnapBlastingDisabled", false);
                                 } else {
-                                    Settings.setValue("previousStillSnapSharingDisabled", false);
+                                    Settings.setValue("previousStillSnapBlastingDisabled", false);
                                 }
                                 return;
                             } else {
@@ -240,9 +244,9 @@ function onMessage(message) {
             isLoggedIn = Account.isLoggedIn();
             storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(message.story_id), 1);
             if (message.isGif) {
-                Settings.setValue("previousAnimatedSnapSharingDisabled", true);
+                Settings.setValue("previousAnimatedSnapHifiSharingDisabled", true);
             } else {
-                Settings.setValue("previousStillSnapSharingDisabled", true);
+                Settings.setValue("previousStillSnapHifiSharingDisabled", true);
             }
 
             if (isLoggedIn) {
@@ -264,9 +268,9 @@ function onMessage(message) {
                     if (error || (response.status !== 'success')) {
                         print("ERROR changing audience: ", error || response.status);
                         if (message.isGif) {
-                            Settings.setValue("previousAnimatedSnapSharingDisabled", false);
+                            Settings.setValue("previousAnimatedSnapHifiSharingDisabled", false);
                         } else {
-                            Settings.setValue("previousStillSnapSharingDisabled", false);
+                            Settings.setValue("previousStillSnapHifiSharingDisabled", false);
                         }
                         return;
                     } else {
@@ -301,10 +305,12 @@ function onButtonClicked() {
         shouldActivateButton = true;
         var previousStillSnapPath = Settings.getValue("previousStillSnapPath");
         var previousStillSnapStoryID = Settings.getValue("previousStillSnapStoryID");
-        var previousStillSnapSharingDisabled = Settings.getValue("previousStillSnapSharingDisabled");
+        var previousStillSnapBlastingDisabled = Settings.getValue("previousStillSnapBlastingDisabled");
+        var previousStillSnapHifiSharingDisabled = Settings.getValue("previousStillSnapHifiSharingDisabled");
         var previousAnimatedSnapPath = Settings.getValue("previousAnimatedSnapPath");
         var previousAnimatedSnapStoryID = Settings.getValue("previousAnimatedSnapStoryID");
-        var previousAnimatedSnapSharingDisabled = Settings.getValue("previousAnimatedSnapSharingDisabled");
+        var previousAnimatedSnapBlastingDisabled = Settings.getValue("previousAnimatedSnapBlastingDisabled");
+        var previousAnimatedSnapHifiSharingDisabled = Settings.getValue("previousAnimatedSnapHifiSharingDisabled");
         snapshotOptions = {
             containsGif: previousAnimatedSnapPath !== "",
             processingGif: false,
@@ -312,10 +318,20 @@ function onButtonClicked() {
         }
         imageData = [];
         if (previousAnimatedSnapPath !== "") {
-            imageData.push({ localPath: previousAnimatedSnapPath, story_id: previousAnimatedSnapStoryID, buttonDisabled: previousAnimatedSnapSharingDisabled });
+            imageData.push({
+                localPath: previousAnimatedSnapPath,
+                story_id: previousAnimatedSnapStoryID,
+                blastButtonDisabled: previousAnimatedSnapBlastingDisabled,
+                hifiButtonDisabled: previousAnimatedSnapHifiSharingDisabled
+            });
         }
         if (previousStillSnapPath !== "") {
-            imageData.push({ localPath: previousStillSnapPath, story_id: previousStillSnapStoryID, buttonDisabled: previousStillSnapSharingDisabled });
+            imageData.push({
+                localPath: previousStillSnapPath,
+                story_id: previousStillSnapStoryID,
+                blastButtonDisabled: previousStillSnapBlastingDisabled,
+                hifiButtonDisabled: previousStillSnapHifiSharingDisabled
+            });
         }
         tablet.gotoWebScreen(SNAPSHOT_REVIEW_URL);
         tablet.webEventReceived.connect(onMessage);
@@ -355,10 +371,12 @@ function takeSnapshot() {
     }));
     Settings.setValue("previousStillSnapPath", "");
     Settings.setValue("previousStillSnapStoryID", "");
-    Settings.setValue("previousStillSnapSharingDisabled", false);
+    Settings.setValue("previousStillSnapBlastingDisabled", false);
+    Settings.setValue("previousStillSnapHifiSharingDisabled", false);
     Settings.setValue("previousAnimatedSnapPath", "");
     Settings.setValue("previousAnimatedSnapStoryID", "");
-    Settings.setValue("previousAnimatedSnapSharingDisabled", false);
+    Settings.setValue("previousAnimatedSnapBlastingDisabled", false);
+    Settings.setValue("previousAnimatedSnapHifiSharingDisabled", false);
 
     // Raising the desktop for the share dialog at end will interact badly with clearOverlayWhenMoving.
     // Turn it off now, before we start futzing with things (and possibly moving).
@@ -403,32 +421,34 @@ function takeSnapshot() {
     }, FINISH_SOUND_DELAY);
 }
 
-function isDomainOpen(id) {
+function isDomainOpen(id, callback) {
     print("Checking open status of domain with ID:", id);
-    if (!id) {
-        return false;
+    var status = false;
+    if (id) {
+        var options = [
+            'now=' + new Date().toISOString(),
+            'include_actions=concurrency',
+            'domain_id=' + id.slice(1, -1),
+            'restriction=open,hifi' // If we're sharing, we're logged in
+            // If we're here, protocol matches, and it is online
+        ];
+        var url = METAVERSE_BASE + "/api/v1/user_stories?" + options.join('&');
+
+        request({
+            uri: url,
+            method: 'GET'
+        }, function (error, response) {
+            if (error || (response.status !== 'success')) {
+                print("ERROR getting open status of domain: ", error || response.status);
+            } else {
+                status = response.total_entries ? true : false;
+            }
+            print("Domain open status:", status);
+            callback(status);
+        });
+    } else {
+        callback(status);
     }
-
-    var options = [
-        'now=' + new Date().toISOString(),
-        'include_actions=concurrency',
-        'domain_id=' + id.slice(1, -1),
-        'restriction=open,hifi' // If we're sharing, we're logged in
-        // If we're here, protocol matches, and it is online
-    ];
-    var url = METAVERSE_BASE + "/api/v1/user_stories?" + options.join('&');
-
-    return request({
-        uri: url,
-        method: 'GET'
-    }, function (error, response) {
-        if (error || (response.status !== 'success')) {
-            print("ERROR getting open status of domain: ", error || response.status);
-            return false;
-        } else {
-            return response.total_entries;
-        }
-    });
 }
 
 function stillSnapshotTaken(pathStillSnapshot, notify) {
@@ -448,25 +468,27 @@ function stillSnapshotTaken(pathStillSnapshot, notify) {
     // during which time the user may have moved. So stash that info in the dialog so that
     // it records the correct href. (We can also stash in .jpegs, but not .gifs.)
     // last element in data array tells dialog whether we can share or not
-    snapshotOptions = {
-        containsGif: false,
-        processingGif: false,
-        canShare: !isDomainOpen(domainId)
-    };
-    imageData = [{ localPath: pathStillSnapshot, href: href }];
     Settings.setValue("previousStillSnapPath", pathStillSnapshot);
 
-    tablet.emitScriptEvent(JSON.stringify({
-        type: "snapshot",
-        action: "addImages",
-        options: snapshotOptions,
-        image_data: imageData
-    }));
-
     if (clearOverlayWhenMoving) {
         MyAvatar.setClearOverlayWhenMoving(true); // not until after the share dialog
     }
     HMD.openTablet();
+
+    isDomainOpen(domainId, function (canShare) {
+        snapshotOptions = {
+            containsGif: false,
+            processingGif: false,
+            canShare: canShare
+        };
+        imageData = [{ localPath: pathStillSnapshot, href: href }];
+        tablet.emitScriptEvent(JSON.stringify({
+            type: "snapshot",
+            action: "addImages",
+            options: snapshotOptions,
+            image_data: imageData
+        }));
+    });
 }
 
 function processingGifStarted(pathStillSnapshot) {
@@ -478,27 +500,28 @@ function processingGifStarted(pathStillSnapshot) {
     if (resetOverlays) {
         Menu.setIsOptionChecked("Overlays", true);
     }
-
-    snapshotOptions = {
-        containsGif: true,
-        processingGif: true,
-        loadingGifPath: Script.resolvePath(Script.resourcesPath() + 'icons/loadingDark.gif'),
-        canShare: !isDomainOpen(domainId)
-    };
-    imageData = [{ localPath: pathStillSnapshot, href: href }];
     Settings.setValue("previousStillSnapPath", pathStillSnapshot);
 
-    tablet.emitScriptEvent(JSON.stringify({
-        type: "snapshot",
-        action: "addImages",
-        options: snapshotOptions,
-        image_data: imageData
-    }));
-
     if (clearOverlayWhenMoving) {
         MyAvatar.setClearOverlayWhenMoving(true); // not until after the share dialog
     }
     HMD.openTablet();
+    
+    isDomainOpen(domainId, function (canShare) {
+        snapshotOptions = {
+            containsGif: true,
+            processingGif: true,
+            loadingGifPath: Script.resolvePath(Script.resourcesPath() + 'icons/loadingDark.gif'),
+            canShare: canShare
+        };
+        imageData = [{ localPath: pathStillSnapshot, href: href }];
+        tablet.emitScriptEvent(JSON.stringify({
+            type: "snapshot",
+            action: "addImages",
+            options: snapshotOptions,
+            image_data: imageData
+        }));
+    });
 }
 
 function processingGifCompleted(pathAnimatedSnapshot) {
@@ -508,20 +531,22 @@ function processingGifCompleted(pathAnimatedSnapshot) {
         buttonConnected = true;
     }
 
-    snapshotOptions = {
-        containsGif: true,
-        processingGif: false,
-        canShare: !isDomainOpen(domainId)
-    }
-    imageData = [{ localPath: pathAnimatedSnapshot, href: href }];
     Settings.setValue("previousAnimatedSnapPath", pathAnimatedSnapshot);
 
-    tablet.emitScriptEvent(JSON.stringify({
-        type: "snapshot",
-        action: "addImages",
-        options: snapshotOptions,
-        image_data: imageData
-    }));
+    isDomainOpen(domainId, function (canShare) {
+        snapshotOptions = {
+            containsGif: true,
+            processingGif: false,
+            canShare: canShare
+        };
+        imageData = [{ localPath: pathAnimatedSnapshot, href: href }];
+        tablet.emitScriptEvent(JSON.stringify({
+            type: "snapshot",
+            action: "addImages",
+            options: snapshotOptions,
+            image_data: imageData
+        }));
+    });
 }
 function maybeDeleteSnapshotStories() {
     storyIDsToMaybeDelete.forEach(function (element, idx, array) {

From 5b168301bcac0fc6f046806f892e72f3c6c625c4 Mon Sep 17 00:00:00 2001
From: Zach Fox <fox@highfidelity.io>
Date: Mon, 1 May 2017 15:28:50 -0700
Subject: [PATCH 32/38] Update some typefaces; Add margins to pics

---
 scripts/system/html/css/SnapshotReview.css | 33 ++++++++++------------
 scripts/system/html/css/hifi-style.css     |  9 +++---
 scripts/system/html/js/SnapshotReview.js   |  9 +++---
 3 files changed, 25 insertions(+), 26 deletions(-)

diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css
index 12b91d372b..fc0f9b461b 100644
--- a/scripts/system/html/css/SnapshotReview.css
+++ b/scripts/system/html/css/SnapshotReview.css
@@ -80,6 +80,8 @@ input[type=button].naked:active {
 
 #snapshot-images {
     width: 100%;
+    display: flex;
+    justify-content: center;
 }
 
 #snapshot-images img {
@@ -119,13 +121,13 @@ input[type=button].naked:active {
 .shareButtons {
     display: flex;
     align-items: center;
-    margin-left: 30px;
+    margin-left: 15px;
     height: 100%;
-    width: 80%;
+    width: 75%;
 }
 .blastToConnections {
     text-align: left;
-    margin-right: 25px;
+    margin-right: 20px;
     height: 29px;
 }
 .shareWithEveryone {
@@ -158,10 +160,11 @@ input[type=button].naked:active {
     font-family: Raleway-SemiBold;
     font-size: 14px;
     color: white;
-    text-shadow: 2px 2px 3px #000000;
     height: 100%;
     margin-right: 10px;
-    width: 20%;
+}
+.showShareButtonsButtonDiv > label {
+    text-shadow: 2px 2px 3px #000000;
 }
 .showShareButton {
     width: 40px;
@@ -193,23 +196,17 @@ input[type=button].naked:active {
     background-color: white;
 }
 .showShareButtonDots {
-    display: flex;
-    width: 32px;
+    display: block;
+    width: 40px;
     height: 40px;
+    font-family: HiFi-Glyphs;
+    font-size: 60px;
     position: absolute;
-    top: 5px;
-    right: 14px;
+    right: 20px;
+    bottom: 15px;
+    color: #00b4ef;
     pointer-events: none;
 }
-.showShareButtonDots > span {
-    width: 10px;
-    height: 10px;
-    margin: auto;
-    background-color: #0093C5;
-    border-radius: 50%;
-    border-width: 0;
-    display: inline;
-}
 /*
 // END styling of share overlay
 */
diff --git a/scripts/system/html/css/hifi-style.css b/scripts/system/html/css/hifi-style.css
index f1ace02eb0..ac34cee09f 100644
--- a/scripts/system/html/css/hifi-style.css
+++ b/scripts/system/html/css/hifi-style.css
@@ -101,9 +101,11 @@ input[type=radio] {
   opacity: 0;
 }
 input[type=radio] + label{
-  display: inline-block;
-  margin-left: -2em;
-  line-height: 2em;
+    display: inline-block;
+    margin-left: -2em;
+    line-height: 2em;
+    font-family: Raleway-SemiBold;
+    font-size: 14px;
 }
 input[type=radio] + label > span{
   display: inline-block;
@@ -157,7 +159,6 @@ input[type=radio]:active + label > span > span{
     border-width: 0px;
     background-image: linear-gradient(#00B4EF, #1080B8);
     min-height: 30px;
-
 }
 .blueButton:hover {
     background-image: linear-gradient(#00B4EF, #00B4EF);
diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js
index b1a6bb2303..475a1e71ff 100644
--- a/scripts/system/html/js/SnapshotReview.js
+++ b/scripts/system/html/js/SnapshotReview.js
@@ -60,8 +60,9 @@ function addImage(image_data, isGifLoading, canShare, isShowingPreviousImages, b
     // imageContainer setup
     var imageContainer = document.createElement("DIV");
     imageContainer.id = id;
-    imageContainer.style.width = "100%";
-    imageContainer.style.height = "251px";
+    imageContainer.style.width = "95%";
+    imageContainer.style.height = "240px";
+    imageContainer.style.margin = "5px 0";
     imageContainer.style.display = "flex";
     imageContainer.style.justifyContent = "center";
     imageContainer.style.alignItems = "center";
@@ -118,7 +119,7 @@ function createShareBar(parentID, isGif, shareURL, blastButtonDisabled, hifiButt
             '<label id="' + showShareButtonsLabelID + '" for="' + showShareButtonsButtonID + '">SHARE</label>' +
             '<input type="button" class="showShareButton inactive" id="' + showShareButtonsButtonID + '" onclick="selectImageToShare(' + parentID + ', true)" />' +
             '<div class="showShareButtonDots">' +
-                '<span></span><span></span><span></span>' +
+                '&#xe019;' +
             '</div>' +
         '</div>';
 
@@ -231,7 +232,7 @@ function handleCaptureSetting(setting) {
 window.onload = function () {
     // Uncomment the line below to test functionality in a browser.
     // See definition of "testInBrowser()" to modify tests.
-    //testInBrowser(true);
+    testInBrowser(false);
     openEventBridge(function () {
         // Set up a handler for receiving the data, and tell the .js we are ready to receive it.
         EventBridge.scriptEventReceived.connect(function (message) {

From d295b5a69d6067ca516945c7dd21b53596c31776 Mon Sep 17 00:00:00 2001
From: Zach Fox <fox@highfidelity.io>
Date: Mon, 1 May 2017 15:31:22 -0700
Subject: [PATCH 33/38] ShareBarHeight-= 5px; Remove test call

---
 scripts/system/html/css/SnapshotReview.css | 4 ++--
 scripts/system/html/js/SnapshotReview.js   | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css
index fc0f9b461b..f8fa21a1f7 100644
--- a/scripts/system/html/css/SnapshotReview.css
+++ b/scripts/system/html/css/SnapshotReview.css
@@ -110,7 +110,7 @@ input[type=button].naked:active {
     justify-content: space-between;
     flex-direction: row;
     align-items: center;
-    height: 50px;
+    height: 45px;
     line-height: 60px;
     width: calc(100% - 8px);
     position: absolute;
@@ -203,7 +203,7 @@ input[type=button].naked:active {
     font-size: 60px;
     position: absolute;
     right: 20px;
-    bottom: 15px;
+    bottom: 12px;
     color: #00b4ef;
     pointer-events: none;
 }
diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js
index 475a1e71ff..ad7578b4c0 100644
--- a/scripts/system/html/js/SnapshotReview.js
+++ b/scripts/system/html/js/SnapshotReview.js
@@ -232,7 +232,7 @@ function handleCaptureSetting(setting) {
 window.onload = function () {
     // Uncomment the line below to test functionality in a browser.
     // See definition of "testInBrowser()" to modify tests.
-    testInBrowser(false);
+    //testInBrowser(false);
     openEventBridge(function () {
         // Set up a handler for receiving the data, and tell the .js we are ready to receive it.
         EventBridge.scriptEventReceived.connect(function (message) {

From 0ead9e5a2468c0877dd5a8eb670ca7ef619c78d0 Mon Sep 17 00:00:00 2001
From: Zach Fox <fox@highfidelity.io>
Date: Mon, 1 May 2017 16:17:38 -0700
Subject: [PATCH 34/38] Visual fixes; Select only 1 image at a time

---
 scripts/system/html/css/SnapshotReview.css |  1 +
 scripts/system/html/js/SnapshotReview.js   | 16 +++++++++++++---
 2 files changed, 14 insertions(+), 3 deletions(-)

diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css
index f8fa21a1f7..ccc9386d13 100644
--- a/scripts/system/html/css/SnapshotReview.css
+++ b/scripts/system/html/css/SnapshotReview.css
@@ -82,6 +82,7 @@ input[type=button].naked:active {
     width: 100%;
     display: flex;
     justify-content: center;
+    flex-direction: column;
 }
 
 #snapshot-images img {
diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js
index ad7578b4c0..b7380e83c9 100644
--- a/scripts/system/html/js/SnapshotReview.js
+++ b/scripts/system/html/js/SnapshotReview.js
@@ -62,7 +62,7 @@ function addImage(image_data, isGifLoading, canShare, isShowingPreviousImages, b
     imageContainer.id = id;
     imageContainer.style.width = "95%";
     imageContainer.style.height = "240px";
-    imageContainer.style.margin = "5px 0";
+    imageContainer.style.margin = "5px auto";
     imageContainer.style.display = "flex";
     imageContainer.style.justifyContent = "center";
     imageContainer.style.alignItems = "center";
@@ -150,6 +150,15 @@ function selectImageToShare(selectedID, isSelected) {
         shareBar.style.backgroundColor = "rgba(0, 0, 0, 0.5)";
 
         shareButtonsDiv.style.visibility = "visible";
+
+        var containers = document.getElementsByClassName("shareControls");
+        var parentID;
+        for (var i = 0; i < containers.length; ++i) {
+            parentID = containers[i].id.slice(0, 2);
+            if (parentID !== selectedID) {
+                selectImageToShare(parentID, false);
+            }
+        }
     } else {
         showShareButtonsButton.onclick = function () { selectImageToShare(selectedID, true) };
         showShareButtonsButton.classList.remove("active");
@@ -232,7 +241,7 @@ function handleCaptureSetting(setting) {
 window.onload = function () {
     // Uncomment the line below to test functionality in a browser.
     // See definition of "testInBrowser()" to modify tests.
-    //testInBrowser(false);
+    testInBrowser(false);
     openEventBridge(function () {
         // Set up a handler for receiving the data, and tell the .js we are ready to receive it.
         EventBridge.scriptEventReceived.connect(function (message) {
@@ -330,6 +339,7 @@ function testInBrowser(isTestingSetupInstructions) {
     } else {
         imageCount = 1;
         //addImage({ localPath: 'http://lorempixel.com/553/255' });
-        addImage({ localPath: 'C:/Users/valef/Desktop/hifi-snap-by-zfox-on-2017-04-26_10-26-53.gif' }, false, true, true, false, false);
+        addImage({ localPath: 'C:/Users/valef/Desktop/hifi-snap-by-zfox-on-2017-05-01_15-48-15.gif' }, false, true, true, false, false);
+        addImage({ localPath: 'C:/Users/valef/Desktop/hifi-snap-by-zfox-on-2017-05-01_15-48-15.jpg' }, false, true, true, false, false);
     }
 }

From a754c4d6cc2639e36206a52f956ab262568fe991 Mon Sep 17 00:00:00 2001
From: Zach Fox <fox@highfidelity.io>
Date: Mon, 1 May 2017 16:40:47 -0700
Subject: [PATCH 35/38] Still image on top

---
 scripts/system/html/js/SnapshotReview.js | 18 +++++++++---------
 scripts/system/snapshot.js               | 16 ++++++++--------
 2 files changed, 17 insertions(+), 17 deletions(-)

diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js
index b7380e83c9..ed290e9756 100644
--- a/scripts/system/html/js/SnapshotReview.js
+++ b/scripts/system/html/js/SnapshotReview.js
@@ -83,7 +83,7 @@ function addImage(image_data, isGifLoading, canShare, isShowingPreviousImages, b
     }
     if (!isGifLoading && !isShowingPreviousImages && canShare) {
         shareForUrl(id);
-    } else if (isShowingPreviousImages && canShare) {
+    } else if (isShowingPreviousImages && canShare && image_data.story_id) {
         appendShareBar(id, image_data.story_id, isGif, blastButtonDisabled, hifiButtonDisabled)
     }
 }
@@ -241,7 +241,7 @@ function handleCaptureSetting(setting) {
 window.onload = function () {
     // Uncomment the line below to test functionality in a browser.
     // See definition of "testInBrowser()" to modify tests.
-    testInBrowser(false);
+    //testInBrowser(false);
     openEventBridge(function () {
         // Set up a handler for receiving the data, and tell the .js we are ready to receive it.
         EventBridge.scriptEventReceived.connect(function (message) {
@@ -280,18 +280,18 @@ window.onload = function () {
                     if (messageOptions.containsGif) {
                         if (messageOptions.processingGif) {
                             imageCount = message.image_data.length + 1; // "+1" for the GIF that'll finish processing soon
-                            message.image_data.unshift({ localPath: messageOptions.loadingGifPath });
+                            message.image_data.push({ localPath: messageOptions.loadingGifPath });
                             message.image_data.forEach(function (element, idx, array) {
-                                addImage(element, idx === 0, messageOptions.canShare, false);
+                                addImage(element, idx === 1, idx === 0 && messageOptions.canShare, false);
                             });
                         } else {
                             var gifPath = message.image_data[0].localPath;
-                            var p0img = document.getElementById('p0img');
-                            p0img.src = gifPath;
+                            var p1img = document.getElementById('p1img');
+                            p1img.src = gifPath;
 
-                            paths[0] = gifPath;
+                            paths[1] = gifPath;
                             if (messageOptions.canShare) {
-                                shareForUrl("p0");
+                                shareForUrl("p1");
                             }
                         }
                     } else {
@@ -306,7 +306,7 @@ window.onload = function () {
                     break;
                 case 'snapshotUploadComplete':
                     var isGif = message.image_url.split('.').pop().toLowerCase() === "gif";
-                    appendShareBar(isGif || imageCount === 1 ? "p0" : "p1", message.story_id, isGif);
+                    appendShareBar(isGif ? "p1" : "p0", message.story_id, isGif);
                     break;
                 default:
                     console.log("Unknown message action received in SnapshotReview.js.");
diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js
index d8635dcb03..b2d39484b0 100644
--- a/scripts/system/snapshot.js
+++ b/scripts/system/snapshot.js
@@ -317,14 +317,6 @@ function onButtonClicked() {
             shouldUpload: false
         }
         imageData = [];
-        if (previousAnimatedSnapPath !== "") {
-            imageData.push({
-                localPath: previousAnimatedSnapPath,
-                story_id: previousAnimatedSnapStoryID,
-                blastButtonDisabled: previousAnimatedSnapBlastingDisabled,
-                hifiButtonDisabled: previousAnimatedSnapHifiSharingDisabled
-            });
-        }
         if (previousStillSnapPath !== "") {
             imageData.push({
                 localPath: previousStillSnapPath,
@@ -333,6 +325,14 @@ function onButtonClicked() {
                 hifiButtonDisabled: previousStillSnapHifiSharingDisabled
             });
         }
+        if (previousAnimatedSnapPath !== "") {
+            imageData.push({
+                localPath: previousAnimatedSnapPath,
+                story_id: previousAnimatedSnapStoryID,
+                blastButtonDisabled: previousAnimatedSnapBlastingDisabled,
+                hifiButtonDisabled: previousAnimatedSnapHifiSharingDisabled
+            });
+        }
         tablet.gotoWebScreen(SNAPSHOT_REVIEW_URL);
         tablet.webEventReceived.connect(onMessage);
         HMD.openTablet();

From f453df36cf22974202a6831a20e136e7b516c6d2 Mon Sep 17 00:00:00 2001
From: Zach Fox <fox@highfidelity.io>
Date: Mon, 1 May 2017 16:53:44 -0700
Subject: [PATCH 36/38] Prevent cursor appearance in snaps

---
 scripts/system/snapshot.js | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js
index b2d39484b0..94630d3a72 100644
--- a/scripts/system/snapshot.js
+++ b/scripts/system/snapshot.js
@@ -395,6 +395,7 @@ function takeSnapshot() {
     resetOverlays = Menu.isOptionChecked("Overlays"); // For completeness. Certainly true if the button is visible to be clicked.
     reticleVisible = Reticle.visible;
     Reticle.visible = false;
+    Reticle.allowMouseCapture = false;
     
     var includeAnimated = Settings.getValue("alsoTakeAnimatedSnapshot", true);
     if (includeAnimated) {
@@ -454,6 +455,7 @@ function isDomainOpen(id, callback) {
 function stillSnapshotTaken(pathStillSnapshot, notify) {
     // show hud
     Reticle.visible = reticleVisible;
+    Reticle.allowMouseCapture = true;
     // show overlays if they were on
     if (resetOverlays) {
         Menu.setIsOptionChecked("Overlays", true);
@@ -496,6 +498,7 @@ function processingGifStarted(pathStillSnapshot) {
     Window.processingGifCompleted.connect(processingGifCompleted);
     // show hud
     Reticle.visible = reticleVisible;
+    Reticle.allowMouseCapture = true;
     // show overlays if they were on
     if (resetOverlays) {
         Menu.setIsOptionChecked("Overlays", true);

From c9e0679b70b6d07fd456f33b575cf1625b0e2ec7 Mon Sep 17 00:00:00 2001
From: Zach Fox <fox@highfidelity.io>
Date: Tue, 2 May 2017 10:55:57 -0700
Subject: [PATCH 37/38] Subscribe to changes to snapshotsLocation setter for
 better UX

---
 interface/src/ui/PreferencesDialog.cpp |  2 +-
 interface/src/ui/Snapshot.h            |  3 +++
 scripts/system/snapshot.js             | 10 ++++++++++
 3 files changed, 14 insertions(+), 1 deletion(-)

diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp
index bf4be7fd17..e2e22fe366 100644
--- a/interface/src/ui/PreferencesDialog.cpp
+++ b/interface/src/ui/PreferencesDialog.cpp
@@ -111,7 +111,7 @@ void setupPreferences() {
     static const QString SNAPSHOTS { "Snapshots" };
     {
         auto getter = []()->QString { return Snapshot::snapshotsLocation.get(); };
-        auto setter = [](const QString& value) { Snapshot::snapshotsLocation.set(value); };
+        auto setter = [](const QString& value) { Snapshot::snapshotsLocation.set(value); emit DependencyManager::get<Snapshot>()->snapshotLocationSet(value); };
         auto preference = new BrowsePreference(SNAPSHOTS, "Put my snapshots here", getter, setter);
         preferences->addPreference(preference);
     }
diff --git a/interface/src/ui/Snapshot.h b/interface/src/ui/Snapshot.h
index 93ffbbc7bb..1246e1d004 100644
--- a/interface/src/ui/Snapshot.h
+++ b/interface/src/ui/Snapshot.h
@@ -44,6 +44,9 @@ public:
     static Setting::Handle<QString> snapshotsLocation;
     static void uploadSnapshot(const QString& filename, const QUrl& href = QUrl(""));
 
+signals:
+    void snapshotLocationSet(const QString& value);
+
 public slots:
     Q_INVOKABLE QString getSnapshotsLocation();
     Q_INVOKABLE void setSnapshotsLocation(const QString& location);
diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js
index 94630d3a72..d0d1bd96f5 100644
--- a/scripts/system/snapshot.js
+++ b/scripts/system/snapshot.js
@@ -582,12 +582,21 @@ function onUsernameChanged() {
         shareAfterLogin = false;
     }
 }
+function snapshotLocationSet(location) {
+    if (location !== "") {
+        tablet.emitScriptEvent(JSON.stringify({
+            type: "snapshot",
+            action: "snapshotLocationChosen"
+        }));
+    }
+}
 
 button.clicked.connect(onButtonClicked);
 buttonConnected = true;
 Window.snapshotShared.connect(snapshotUploaded);
 tablet.screenChanged.connect(onTabletScreenChanged);
 Account.usernameChanged.connect(onUsernameChanged);
+Snapshot.snapshotLocationSet.connect(snapshotLocationSet);
 Script.scriptEnding.connect(function () {
     if (buttonConnected) {
         button.clicked.disconnect(onButtonClicked);
@@ -598,6 +607,7 @@ Script.scriptEnding.connect(function () {
     }
     Window.snapshotShared.disconnect(snapshotUploaded);
     tablet.screenChanged.disconnect(onTabletScreenChanged);
+    Snapshot.snapshotLocationSet.disconnect(snapshotLocationSet);
 });
 
 }()); // END LOCAL_SCOPE

From 3a36feacad422a1f7d9238a68acd1d2aa49c171c Mon Sep 17 00:00:00 2001
From: Howard Stearns <howard@highfidelity.io>
Date: Tue, 2 May 2017 11:29:51 -0700
Subject: [PATCH 38/38] On touch, grip is a trigger, not a button, so it can
 have lots of callbacks with slightly different values that we ignore. In that
 case, don't update hand/joint.

---
 scripts/system/makeUserConnection.js | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js
index 47082e882f..33d6b363ab 100644
--- a/scripts/system/makeUserConnection.js
+++ b/scripts/system/makeUserConnection.js
@@ -519,11 +519,9 @@
 
     function updateTriggers(value, fromKeyboard, hand) {
         if (currentHand && hand !== currentHand) {
-            debug("currentHand", currentHand, "ignoring messages from", hand);
+            debug("currentHand", currentHand, "ignoring messages from", hand); // this can be a lot of spam on Touch. Should guard that someday.
             return;
         }
-        currentHand = hand;
-        currentHandJointIndex = getIdealHandJointIndex(MyAvatar, handToString(currentHand)); // Always, in case of changed skeleton.
         // ok now, we are either initiating or quitting...
         var isGripping = value > GRIP_MIN;
         if (isGripping) {
@@ -531,6 +529,8 @@
             if (state !== STATES.INACTIVE) {
                 return;
             }
+            currentHand = hand;
+            currentHandJointIndex = getIdealHandJointIndex(MyAvatar, handToString(currentHand)); // Always, in case of changed skeleton.
             startHandshake(fromKeyboard);
         } else {
             // TODO: should we end handshake even when inactive?  Ponder