From 0d62b10a8f0c0a363f6a7185a4f2b59932b4ff20 Mon Sep 17 00:00:00 2001
From: "Anthony J. Thibault" <tony@highfidelity.io>
Date: Mon, 7 Mar 2016 16:16:14 -0800
Subject: [PATCH 1/8] Added Mat4 support to script

Also, hooked up MyAvatar.sensorToWorldMatrix access to script.
---
 examples/tests/mat4test.js                   | 165 +++++++++++++++++++
 interface/src/avatar/MyAvatar.cpp            |   5 +-
 interface/src/avatar/MyAvatar.h              |   9 +-
 libraries/script-engine/src/Mat4.cpp         |  71 ++++++++
 libraries/script-engine/src/Mat4.h           |  45 +++++
 libraries/script-engine/src/ScriptEngine.cpp |   1 +
 libraries/script-engine/src/ScriptEngine.h   |   4 +-
 libraries/shared/src/GLMHelpers.cpp          |   9 +
 libraries/shared/src/GLMHelpers.h            |   1 +
 libraries/shared/src/RegisteredMetaTypes.cpp |  41 +++++
 libraries/shared/src/RegisteredMetaTypes.h   |   5 +
 11 files changed, 352 insertions(+), 4 deletions(-)
 create mode 100644 examples/tests/mat4test.js
 create mode 100644 libraries/script-engine/src/Mat4.cpp
 create mode 100644 libraries/script-engine/src/Mat4.h

diff --git a/examples/tests/mat4test.js b/examples/tests/mat4test.js
new file mode 100644
index 0000000000..ebce420dcb
--- /dev/null
+++ b/examples/tests/mat4test.js
@@ -0,0 +1,165 @@
+//
+//  mat4test.js
+//  examples/tests
+//
+//  Created by Anthony Thibault on 2016/3/7
+//  Copyright 2016 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+var IDENTITY = {r0c0: 1, r0c1: 0, r0c2: 0, r0c3: 0,
+                r1c0: 0, r1c1: 1, r1c2: 0, r1c3: 0,
+                r2c0: 0, r2c1: 0, r2c2: 1, r2c3: 0,
+                r3c0: 0, r3c1: 0, r3c2: 0, r3c3: 1};
+
+var ROT_ZERO = {x: 0, y: 0, z: 0, w: 1};
+var ROT_Y_180 = {x: 0, y: 1, z: 0, w: 0};
+
+var ONE = {x: 1, y: 1, z: 1};
+var ZERO = {x: 0, y: 0, z: 0};
+var ONE_TWO_THREE = {x: 1, y: 2, z: 3};
+var ONE_HALF = {x: 0.5, y: 0.5, z: 0.5};
+
+var EPSILON = 0.000001;
+
+function mat4FuzzyEqual(a, b) {
+    var r, c;
+    for (r = 0; r < 4; r++) {
+        for (c = 0; c < 4; c++) {
+            if (Math.abs(a["r" + r + "c" + c] - b["r" + r + "c" + c]) > EPSILON) {
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+function vec3FuzzyEqual(a, b) {
+    if (Math.abs(a.x - b.x) > EPSILON ||
+        Math.abs(a.y - b.y) > EPSILON ||
+        Math.abs(a.z - b.z) > EPSILON) {
+        return false;
+    }
+    return true;
+}
+
+function quatFuzzyEqual(a, b) {
+    if (Math.abs(a.x - b.x) > EPSILON ||
+        Math.abs(a.y - b.y) > EPSILON ||
+        Math.abs(a.z - b.z) > EPSILON ||
+        Math.abs(a.w - b.w) > EPSILON) {
+        return false;
+    }
+    return true;
+}
+
+var failureCount = 0;
+var testCount = 0;
+function assert(test) {
+    testCount++;
+    if (!test) {
+        print("MAT4 TEST " + testCount + " failed!");
+        failureCount++;
+    }
+}
+
+function testCreate() {
+    var test0 = Mat4.createFromScaleRotAndTrans(ONE, {x: 0, y: 0, z: 0, w: 1}, ZERO);
+    assert(mat4FuzzyEqual(test0, IDENTITY));
+
+    var test1 = Mat4.createFromRotAndTrans({x: 0, y: 0, z: 0, w: 1}, ZERO);
+    assert(mat4FuzzyEqual(test1, IDENTITY));
+
+    var test2 = Mat4.createFromRotAndTrans(ROT_Y_180, ONE_TWO_THREE);
+    assert(mat4FuzzyEqual(test2, {r0c0: -1, r0c1: 0, r0c2: 0, r0c3: 1,
+                                  r1c0: 0, r1c1: 1, r1c2: 0, r1c3: 2,
+                                  r2c0: 0, r2c1: 0, r2c2: -1, r2c3: 3,
+                                  r3c0: 0, r3c1: 0, r3c2: 0, r3c3: 1}));
+
+    var test3 = Mat4.createFromScaleRotAndTrans(ONE_HALF, ROT_Y_180, ONE_TWO_THREE);
+    assert(mat4FuzzyEqual(test3, {r0c0: -0.5, r0c1: 0, r0c2: 0, r0c3: 1,
+                                  r1c0: 0, r1c1: 0.5, r1c2: 0, r1c3: 2,
+                                  r2c0: 0, r2c1: 0, r2c2: -0.5, r2c3: 3,
+                                  r3c0: 0, r3c1: 0, r3c2: 0, r3c3: 1}));
+}
+
+function testExtractTranslation() {
+    var test0 = Mat4.extractTranslation(IDENTITY);
+    assert(vec3FuzzyEqual(ZERO, test0));
+
+    var test1 = Mat4.extractTranslation(Mat4.createFromRotAndTrans(ROT_Y_180, ONE_TWO_THREE));
+    assert(vec3FuzzyEqual(ONE_TWO_THREE, test1));
+}
+
+function testExtractRotation() {
+    var test0 = Mat4.extractRotation(IDENTITY);
+    assert(quatFuzzyEqual(ROT_ZERO, test0));
+
+    var test1 = Mat4.extractRotation(Mat4.createFromRotAndTrans(ROT_Y_180, ONE_TWO_THREE));
+    assert(quatFuzzyEqual(ROT_Y_180, test1));
+}
+
+function testExtractScale() {
+    var test0 = Mat4.extractScale(IDENTITY);
+    assert(vec3FuzzyEqual(ONE, test0));
+
+    var test1 = Mat4.extractScale(Mat4.createFromScaleRotAndTrans(ONE_HALF, ROT_Y_180, ONE_TWO_THREE));
+    assert(vec3FuzzyEqual(ONE_HALF, test1));
+
+    var test2 = Mat4.extractScale(Mat4.createFromScaleRotAndTrans(ONE_TWO_THREE, ROT_ZERO, ONE_TWO_THREE));
+    assert(vec3FuzzyEqual(ONE_TWO_THREE, test2));
+}
+
+function testTransformPoint() {
+    var test0 = Mat4.transformPoint(IDENTITY, ONE);
+    assert(vec3FuzzyEqual(ONE, test0));
+
+    var m = Mat4.createFromScaleRotAndTrans(ONE_HALF, ROT_Y_180, ONE_TWO_THREE);
+    var test1 = Mat4.transformPoint(m, ONE);
+    assert(vec3FuzzyEqual({x: 0.5, y: 2.5, z: 2.5}, test1));
+}
+
+function testTransformVector() {
+    var test0 = Mat4.transformVector(IDENTITY, ONE);
+    assert(vec3FuzzyEqual(ONE, test0));
+
+    var m = Mat4.createFromScaleRotAndTrans(ONE_HALF, ROT_Y_180, ONE_TWO_THREE);
+    var test1 = Mat4.transformVector(m, ONE);
+    assert(vec3FuzzyEqual({x: -0.5, y: 0.5, z: -0.5}, test1));
+}
+
+function testInverse() {
+    var test0 = IDENTITY;
+    assert(mat4FuzzyEqual(IDENTITY, Mat4.multiply(test0, Mat4.inverse(test0))));
+
+    var test1 = Mat4.createFromScaleRotAndTrans(ONE_HALF, ROT_Y_180, ONE_TWO_THREE);
+    assert(mat4FuzzyEqual(IDENTITY, Mat4.multiply(test1, Mat4.inverse(test1))));
+
+    var test2 = Mat4.extractScale(Mat4.createFromScaleRotAndTrans(ONE_TWO_THREE, ROT_ZERO, ONE_TWO_THREE));
+    assert(mat4FuzzyEqual(IDENTITY, Mat4.multiply(test2, Mat4.inverse(test2))));
+}
+
+function testFront() {
+    var test0 = IDENTITY;
+    assert(mat4FuzzyEqual({x: 0, y: 0, z: -1}, Mat4.getFront(test0)));
+
+    var test1 = Mat4.createFromScaleRotAndTrans(ONE_HALF, ROT_Y_180, ONE_TWO_THREE);
+    assert(mat4FuzzyEqual({x: 0, y: 0, z: 1}, Mat4.getFront(test1)));
+}
+
+function testMat4() {
+    testCreate();
+    testExtractTranslation();
+    testExtractRotation();
+    testExtractScale();
+    testTransformPoint();
+    testTransformVector();
+    testInverse();
+    testFront();
+
+    print("MAT4 TEST complete! (" + (testCount - failureCount) + "/" + testCount + ") tests passed!");
+}
+
+testMat4();
diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index 397627434f..26e0ce56dd 100644
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -416,8 +416,9 @@ void MyAvatar::simulate(float deltaTime) {
     }
 }
 
+// thread-safe
 glm::mat4 MyAvatar::getSensorToWorldMatrix() const {
-    return _sensorToWorldMatrix;
+    return _sensorToWorldMatrixCache.get();
 }
 
 // Pass a recent sample of the HMD to the avatar.
@@ -442,6 +443,8 @@ void MyAvatar::updateSensorToWorldMatrix() {
     _sensorToWorldMatrix = desiredMat * glm::inverse(_bodySensorMatrix);
 
     lateUpdatePalms();
+
+    _sensorToWorldMatrixCache.set(_sensorToWorldMatrix);
 }
 
 //  Update avatar head rotation with sensor data
diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h
index 731cba8153..fd5c2920a9 100644
--- a/interface/src/avatar/MyAvatar.h
+++ b/interface/src/avatar/MyAvatar.h
@@ -22,7 +22,7 @@
 #include "Avatar.h"
 #include "AtRestDetector.h"
 #include "MyCharacterController.h"
-
+#include <ThreadSafeValueCache.h>
 
 class ModelItemID;
 
@@ -78,6 +78,9 @@ class MyAvatar : public Avatar {
     Q_PROPERTY(controller::Pose rightHandPose READ getRightHandPose)
     Q_PROPERTY(controller::Pose leftHandTipPose READ getLeftHandTipPose)
     Q_PROPERTY(controller::Pose rightHandTipPose READ getRightHandTipPose)
+
+    Q_PROPERTY(glm::mat4 sensorToWorldMatrix READ getSensorToWorldMatrix)
+
     Q_PROPERTY(float energy READ getEnergy WRITE setEnergy)
 
 public:
@@ -98,8 +101,9 @@ public:
     const glm::vec3& getHMDSensorPosition() const { return _hmdSensorPosition; }
     const glm::quat& getHMDSensorOrientation() const { return _hmdSensorOrientation; }
     const glm::vec2& getHMDSensorFacingMovingAverage() const { return _hmdSensorFacingMovingAverage; }
-    glm::mat4 getSensorToWorldMatrix() const;
 
+    // thread safe
+    Q_INVOKABLE glm::mat4 getSensorToWorldMatrix() const;
 
     // Pass a recent sample of the HMD to the avatar.
     // This can also update the avatar's position to follow the HMD
@@ -390,6 +394,7 @@ private:
 
     // used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers.
     glm::mat4 _sensorToWorldMatrix;
+    ThreadSafeValueCache<glm::mat4> _sensorToWorldMatrixCache { glm::mat4() };
 
     struct FollowHelper {
         FollowHelper();
diff --git a/libraries/script-engine/src/Mat4.cpp b/libraries/script-engine/src/Mat4.cpp
new file mode 100644
index 0000000000..bb65cb1e26
--- /dev/null
+++ b/libraries/script-engine/src/Mat4.cpp
@@ -0,0 +1,71 @@
+//
+//  Mat4.cpp
+//  libraries/script-engine/src
+//
+//  Created by Anthony Thibault on 3/7/16.
+//  Copyright 2016 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#include <QDebug>
+#include <GLMHelpers.h>
+#include "ScriptEngineLogging.h"
+#include "Mat4.h"
+
+glm::mat4 Mat4::multiply(const glm::mat4& m1, const glm::mat4& m2) const {
+    return m1 * m2;
+}
+
+glm::mat4 Mat4::createFromRotAndTrans(const glm::quat& rot, const glm::vec3& trans) const {
+    return ::createMatFromQuatAndPos(rot, trans);
+}
+
+glm::mat4 Mat4::createFromScaleRotAndTrans(const glm::vec3& scale, const glm::quat& rot, const glm::vec3& trans) const {
+    return createMatFromScaleQuatAndPos(scale, rot, trans);
+}
+
+glm::vec3 Mat4::extractTranslation(const glm::mat4& m) const {
+    return ::extractTranslation(m);
+}
+
+glm::quat Mat4::extractRotation(const glm::mat4& m) const {
+    return ::glmExtractRotation(m);
+}
+
+glm::vec3 Mat4::extractScale(const glm::mat4& m) const {
+    return ::extractScale(m);
+}
+
+glm::vec3 Mat4::transformPoint(const glm::mat4& m, const glm::vec3& point) const {
+    return ::transformPoint(m, point);
+}
+
+glm::vec3 Mat4::transformVector(const glm::mat4& m, const glm::vec3& vector) const {
+    return ::transformVectorFast(m, vector);
+}
+
+glm::mat4 Mat4::inverse(const glm::mat4& m) const {
+    return glm::inverse(m);
+}
+
+glm::vec3 Mat4::getFront(const glm::mat4& m) const {
+    return glm::vec3(-m[0][2], -m[1][2], -m[2][2]);
+}
+
+glm::vec3 Mat4::getRight(const glm::mat4& m) const {
+    return glm::vec3(m[0][0], m[1][0], m[2][0]);
+}
+
+glm::vec3 Mat4::getUp(const glm::mat4& m) const {
+    return glm::vec3(m[0][1], m[1][1], m[2][1]);
+}
+
+void Mat4::print(const QString& label, const glm::mat4& m) const {
+    qCDebug(scriptengine) << qPrintable(label) <<
+        "row0 =" << m[0][0] << "," << m[1][0] << "," << m[2][0] << "," << m[3][0] <<
+        "row1 =" << m[0][1] << "," << m[1][1] << "," << m[2][1] << "," << m[3][1] <<
+        "row2 =" << m[0][2] << "," << m[1][2] << "," << m[2][2] << "," << m[3][2] <<
+        "row3 =" << m[0][3] << "," << m[1][3] << "," << m[2][3] << "," << m[3][3];
+}
diff --git a/libraries/script-engine/src/Mat4.h b/libraries/script-engine/src/Mat4.h
new file mode 100644
index 0000000000..047bf56079
--- /dev/null
+++ b/libraries/script-engine/src/Mat4.h
@@ -0,0 +1,45 @@
+//
+//  Mat4.h
+//  libraries/script-engine/src
+//
+//  Created by Anthony Thibault on 3/7/16.
+//  Copyright 2016 High Fidelity, Inc.
+//
+//  Scriptable 4x4 Matrix class library.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#ifndef hifi_Mat4_h
+#define hifi_Mat4_h
+
+#include <QObject>
+#include <QString>
+
+/// Scriptable Mat4 object.  Used exclusively in the JavaScript API
+class Mat4 : public QObject {
+    Q_OBJECT
+
+public slots:
+    glm::mat4 multiply(const glm::mat4& m1, const glm::mat4& m2) const;
+    glm::mat4 createFromRotAndTrans(const glm::quat& rot, const glm::vec3& trans) const;
+    glm::mat4 createFromScaleRotAndTrans(const glm::vec3& scale, const glm::quat& rot, const glm::vec3& trans) const;
+
+    glm::vec3 extractTranslation(const glm::mat4& m) const;
+    glm::quat extractRotation(const glm::mat4& m) const;
+    glm::vec3 extractScale(const glm::mat4& m) const;
+
+    glm::vec3 transformPoint(const glm::mat4& m, const glm::vec3& point) const;
+    glm::vec3 transformVector(const glm::mat4& m, const glm::vec3& vector) const;
+
+    glm::mat4 inverse(const glm::mat4& m) const;
+
+    glm::vec3 getFront(const glm::mat4& m) const;
+    glm::vec3 getRight(const glm::mat4& m) const;
+    glm::vec3 getUp(const glm::mat4& m) const;
+
+    void print(const QString& label, const glm::mat4& m) const;
+};
+
+#endif // hifi_Mat4_h
diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp
index 9ee2c374c5..61ebfe4515 100644
--- a/libraries/script-engine/src/ScriptEngine.cpp
+++ b/libraries/script-engine/src/ScriptEngine.cpp
@@ -315,6 +315,7 @@ void ScriptEngine::init() {
     registerGlobalObject("Entities", entityScriptingInterface.data());
     registerGlobalObject("Quat", &_quatLibrary);
     registerGlobalObject("Vec3", &_vec3Library);
+    registerGlobalObject("Mat4", &_mat4Library);
     registerGlobalObject("Uuid", &_uuidLibrary);
     registerGlobalObject("AnimationCache", DependencyManager::get<AnimationCache>().data());
     registerGlobalObject("Messages", DependencyManager::get<MessagesClient>().data());
diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h
index 91d4c3f5a5..8056a4f487 100644
--- a/libraries/script-engine/src/ScriptEngine.h
+++ b/libraries/script-engine/src/ScriptEngine.h
@@ -34,6 +34,7 @@
 #include "ArrayBufferClass.h"
 #include "AudioScriptingInterface.h"
 #include "Quat.h"
+#include "Mat4.h"
 #include "ScriptCache.h"
 #include "ScriptUUID.h"
 #include "Vec3.h"
@@ -199,6 +200,7 @@ protected:
     QString _fileNameString;
     Quat _quatLibrary;
     Vec3 _vec3Library;
+    Mat4 _mat4Library;
     ScriptUUID _uuidLibrary;
     std::atomic<bool> _isUserLoaded { false };
     bool _isReloading { false };
@@ -219,4 +221,4 @@ protected:
     static std::atomic<bool> _stoppingAllScripts;
 };
 
-#endif // hifi_ScriptEngine_h
\ No newline at end of file
+#endif // hifi_ScriptEngine_h
diff --git a/libraries/shared/src/GLMHelpers.cpp b/libraries/shared/src/GLMHelpers.cpp
index e9d6591cac..b2294f9ef3 100644
--- a/libraries/shared/src/GLMHelpers.cpp
+++ b/libraries/shared/src/GLMHelpers.cpp
@@ -376,6 +376,15 @@ glm::mat4 createMatFromQuatAndPos(const glm::quat& q, const glm::vec3& p) {
     return m;
 }
 
+// create matrix from a non-uniform scale, orientation and position
+glm::mat4 createMatFromScaleQuatAndPos(const glm::vec3& scale, const glm::quat& rot, const glm::vec3& trans) {
+    glm::vec3 xAxis = rot * glm::vec3(scale.x, 0.0f, 0.0f);
+    glm::vec3 yAxis = rot * glm::vec3(0.0f, scale.y, 0.0f);
+    glm::vec3 zAxis = rot * glm::vec3(0.0f, 0.0f, scale.z);
+    return glm::mat4(glm::vec4(xAxis, 0.0f), glm::vec4(yAxis, 0.0f),
+                     glm::vec4(zAxis, 0.0f), glm::vec4(trans, 1.0f));
+}
+
 // cancel out roll and pitch
 glm::quat cancelOutRollAndPitch(const glm::quat& q) {
     glm::vec3 zAxis = q * glm::vec3(0.0f, 0.0f, 1.0f);
diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h
index 15f3f60cf7..4a37d68240 100644
--- a/libraries/shared/src/GLMHelpers.h
+++ b/libraries/shared/src/GLMHelpers.h
@@ -212,6 +212,7 @@ glm::detail::tvec4<T, P> lerp(const glm::detail::tvec4<T, P>& x, const glm::deta
 }
 
 glm::mat4 createMatFromQuatAndPos(const glm::quat& q, const glm::vec3& p);
+glm::mat4 createMatFromScaleQuatAndPos(const glm::vec3& scale, const glm::quat& rot, const glm::vec3& trans);
 glm::quat cancelOutRollAndPitch(const glm::quat& q);
 glm::mat4 cancelOutRollAndPitch(const glm::mat4& m);
 glm::vec3 transformPoint(const glm::mat4& m, const glm::vec3& p);
diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp
index 94d9b0ae26..953fdb3582 100644
--- a/libraries/shared/src/RegisteredMetaTypes.cpp
+++ b/libraries/shared/src/RegisteredMetaTypes.cpp
@@ -34,6 +34,7 @@ static int collisionMetaTypeId = qRegisterMetaType<Collision>();
 static int qMapURLStringMetaTypeId = qRegisterMetaType<QMap<QUrl,QString>>();
 
 void registerMetaTypes(QScriptEngine* engine) {
+    qScriptRegisterMetaType(engine, mat4toScriptValue, mat4FromScriptValue);
     qScriptRegisterMetaType(engine, vec4toScriptValue, vec4FromScriptValue);
     qScriptRegisterMetaType(engine, vec3toScriptValue, vec3FromScriptValue);
     qScriptRegisterMetaType(engine, qVectorVec3ToScriptValue, qVectorVec3FromScriptValue);
@@ -53,6 +54,46 @@ void registerMetaTypes(QScriptEngine* engine) {
     qScriptRegisterMetaType(engine, aaCubeToScriptValue, aaCubeFromScriptValue);
 }
 
+QScriptValue mat4toScriptValue(QScriptEngine* engine, const glm::mat4& mat4) {
+    QScriptValue obj = engine->newObject();
+    obj.setProperty("r0c0", mat4[0][0]);
+    obj.setProperty("r1c0", mat4[0][1]);
+    obj.setProperty("r2c0", mat4[0][2]);
+    obj.setProperty("r3c0", mat4[0][3]);
+    obj.setProperty("r0c1", mat4[1][0]);
+    obj.setProperty("r1c1", mat4[1][1]);
+    obj.setProperty("r2c1", mat4[1][2]);
+    obj.setProperty("r3c1", mat4[1][3]);
+    obj.setProperty("r0c2", mat4[2][0]);
+    obj.setProperty("r1c2", mat4[2][1]);
+    obj.setProperty("r2c2", mat4[2][2]);
+    obj.setProperty("r3c2", mat4[2][3]);
+    obj.setProperty("r0c3", mat4[3][0]);
+    obj.setProperty("r1c3", mat4[3][1]);
+    obj.setProperty("r2c3", mat4[3][2]);
+    obj.setProperty("r3c3", mat4[3][3]);
+    return obj;
+}
+
+void mat4FromScriptValue(const QScriptValue& object, glm::mat4& mat4) {
+    mat4[0][0] = object.property("r0c0").toVariant().toFloat();
+    mat4[0][1] = object.property("r1c0").toVariant().toFloat();
+    mat4[0][2] = object.property("r2c0").toVariant().toFloat();
+    mat4[0][3] = object.property("r3c0").toVariant().toFloat();
+    mat4[1][0] = object.property("r0c1").toVariant().toFloat();
+    mat4[1][1] = object.property("r1c1").toVariant().toFloat();
+    mat4[1][2] = object.property("r2c1").toVariant().toFloat();
+    mat4[1][3] = object.property("r3c1").toVariant().toFloat();
+    mat4[2][0] = object.property("r0c2").toVariant().toFloat();
+    mat4[2][1] = object.property("r1c2").toVariant().toFloat();
+    mat4[2][2] = object.property("r2c2").toVariant().toFloat();
+    mat4[2][3] = object.property("r3c2").toVariant().toFloat();
+    mat4[3][0] = object.property("r0c3").toVariant().toFloat();
+    mat4[3][1] = object.property("r1c3").toVariant().toFloat();
+    mat4[3][2] = object.property("r2c3").toVariant().toFloat();
+    mat4[3][3] = object.property("r3c3").toVariant().toFloat();
+}
+
 QScriptValue vec4toScriptValue(QScriptEngine* engine, const glm::vec4& vec4) {
     QScriptValue obj = engine->newObject();
     obj.setProperty("x", vec4.x);
diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h
index 0a3e94a5b6..652ec26fe7 100644
--- a/libraries/shared/src/RegisteredMetaTypes.h
+++ b/libraries/shared/src/RegisteredMetaTypes.h
@@ -28,6 +28,7 @@ Q_DECLARE_METATYPE(glm::vec4)
 Q_DECLARE_METATYPE(glm::vec3)
 Q_DECLARE_METATYPE(glm::vec2)
 Q_DECLARE_METATYPE(glm::quat)
+Q_DECLARE_METATYPE(glm::mat4)
 Q_DECLARE_METATYPE(xColor)
 Q_DECLARE_METATYPE(QVector<glm::vec3>)
 Q_DECLARE_METATYPE(QVector<float>)
@@ -35,6 +36,10 @@ Q_DECLARE_METATYPE(AACube)
 
 void registerMetaTypes(QScriptEngine* engine);
 
+// Mat4
+QScriptValue mat4toScriptValue(QScriptEngine* engine, const glm::mat4& mat4);
+void mat4FromScriptValue(const QScriptValue& object, glm::mat4& mat4);
+
 // Vec4
 QScriptValue vec4toScriptValue(QScriptEngine* engine, const glm::vec4& vec4);
 void vec4FromScriptValue(const QScriptValue& object, glm::vec4& vec4);

From 3d71226f72d4e5ca234cfedfe1982ca3cbd0fd4c Mon Sep 17 00:00:00 2001
From: "Anthony J. Thibault" <tony@highfidelity.io>
Date: Mon, 7 Mar 2016 18:33:09 -0800
Subject: [PATCH 2/8] Crash fix for moving reticle via script.

sendMessage is not thread-safe, use invokeMethod to call into application thread instead.
---
 interface/src/Application.cpp              | 4 ++--
 interface/src/Application.h                | 4 ++--
 interface/src/ui/ApplicationCompositor.cpp | 2 +-
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 81b633a1aa..3139c21a1d 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -2173,9 +2173,9 @@ void Application::maybeToggleMenuVisible(QMouseEvent* event) {
 }
 
 /// called by ApplicationCompositor when in HMD mode and we're faking our mouse movement
-void Application::fakeMouseEvent(QMouseEvent* event) {
+void Application::fakeMouseEvent(QMouseEvent event) {
     _fakedMouseEvent = true;
-    sendEvent(_glWidget, event);
+    sendEvent(_glWidget, &event);
     _fakedMouseEvent = false;
 }
 
diff --git a/interface/src/Application.h b/interface/src/Application.h
index c16175a333..9a1f54a266 100644
--- a/interface/src/Application.h
+++ b/interface/src/Application.h
@@ -221,8 +221,6 @@ public:
 
     float getAverageSimsPerSecond();
 
-    void fakeMouseEvent(QMouseEvent* event);
-
 signals:
     void svoImportRequested(const QString& url);
 
@@ -283,6 +281,8 @@ public slots:
 
     void runTests();
 
+    void fakeMouseEvent(QMouseEvent event);
+
 private slots:
     void showDesktop();
     void clearDomainOctreeDetails();
diff --git a/interface/src/ui/ApplicationCompositor.cpp b/interface/src/ui/ApplicationCompositor.cpp
index efd69c7859..04630fbbc8 100644
--- a/interface/src/ui/ApplicationCompositor.cpp
+++ b/interface/src/ui/ApplicationCompositor.cpp
@@ -457,7 +457,7 @@ void ApplicationCompositor::setReticlePosition(glm::vec2 position, bool sendFake
             auto buttons = QApplication::mouseButtons();
             auto modifiers = QApplication::keyboardModifiers();
             QMouseEvent event(QEvent::MouseMove, globalPos, button, buttons, modifiers);
-            qApp->fakeMouseEvent(&event);
+            QMetaObject::invokeMethod(qApp, "fakeMouseEvent", Qt::AutoConnection, Q_ARG(QMouseEvent, event));
         }
     } else {
         // NOTE: This is some debugging code we will leave in while debugging various reticle movement strategies,

From 26b4c904f3b0dc41632c1d35574e50cd909d8b95 Mon Sep 17 00:00:00 2001
From: "Anthony J. Thibault" <tony@highfidelity.io>
Date: Mon, 7 Mar 2016 18:42:52 -0800
Subject: [PATCH 3/8] handControllerMouse.js: transform hand controllers into
 sensor frame

---
 examples/controllers/handControllerMouse.js | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/examples/controllers/handControllerMouse.js b/examples/controllers/handControllerMouse.js
index e53fc34df1..29e7972cf0 100644
--- a/examples/controllers/handControllerMouse.js
+++ b/examples/controllers/handControllerMouse.js
@@ -10,9 +10,9 @@
 //
 
 var DEBUGGING = false;
-var angularVelocityTrailingAverage = 0.0;  //  Global trailing average used to decide whether to move reticle at all 
+var angularVelocityTrailingAverage = 0.0;  //  Global trailing average used to decide whether to move reticle at all
 var lastX = 0;
-var lastY = 0; 
+var lastY = 0;
 
 Math.clamp=function(a,b,c) {
     return Math.max(b,Math.min(c,a));
@@ -57,6 +57,7 @@ var filteredRotatedRight = Vec3.UNIT_NEG_Y;
 
 Script.update.connect(function(deltaTime) {
 
+    // avatar frame
     var poseRight = Controller.getPoseValue(Controller.Standard.RightHand);
     var poseLeft = Controller.getPoseValue(Controller.Standard.LeftHand);
 
@@ -65,15 +66,16 @@ Script.update.connect(function(deltaTime) {
     var screenSizeX = screenSize.x;
     var screenSizeY = screenSize.y;
 
-    var rotatedRight = Vec3.multiplyQbyV(poseRight.rotation, Vec3.UNIT_NEG_Y);
-    var rotatedLeft = Vec3.multiplyQbyV(poseLeft.rotation, Vec3.UNIT_NEG_Y);
+    // transform hand facing vectors from avatar frame into sensor frame.
+    var worldToSensorMatrix = Mat4.inverse(MyAvatar.sensorToWorldMatrix);
+    var rotatedRight = Mat4.transformVector(worldToSensorMatrix, Vec3.multiplyQbyV(MyAvatar.orientation, Vec3.multiplyQbyV(poseRight.rotation, Vec3.UNIT_NEG_Y)));
+    var rotatedLeft = Mat4.transformVector(worldToSensorMatrix, Vec3.multiplyQbyV(MyAvatar.orientation, Vec3.multiplyQbyV(poseLeft.rotation, Vec3.UNIT_NEG_Y)));
 
     lastRotatedRight = rotatedRight;
 
-
     // Decide which hand should be controlling the pointer
-    // by comparing which one is moving more, and by 
-    // tending to stay with the one moving more.  
+    // by comparing which one is moving more, and by
+    // tending to stay with the one moving more.
     var BIAS_ADJUST_RATE = 0.5;
     var BIAS_ADJUST_DEADZONE = 0.05;
     leftRightBias += (Vec3.length(poseRight.angularVelocity) - Vec3.length(poseLeft.angularVelocity)) * BIAS_ADJUST_RATE;
@@ -105,7 +107,7 @@ Script.update.connect(function(deltaTime) {
 
     // don't move the reticle with the hand controllers unless the controllers are actually being moved
     //  take a time average of angular velocity, and don't move mouse at all if it's below threshold
- 
+
     var AVERAGING_INTERVAL = 0.95;
     var MINIMUM_CONTROLLER_ANGULAR_VELOCITY = 0.03;
     var angularVelocityMagnitude = Vec3.length(poseLeft.angularVelocity) * (1.0 - leftRightBias) + Vec3.length(poseRight.angularVelocity) * leftRightBias;
@@ -121,5 +123,3 @@ Script.update.connect(function(deltaTime) {
 Script.scriptEnding.connect(function(){
     mapping.disable();
 });
-
-

From e046ba64ddeb8e94456c0c7c0ebe62d73ad56a47 Mon Sep 17 00:00:00 2001
From: "Anthony J. Thibault" <tony@highfidelity.io>
Date: Tue, 8 Mar 2016 10:58:54 -0800
Subject: [PATCH 4/8] Properly marshal fakeMouseEvent data from script thread
 to main thread

---
 interface/src/Application.cpp              | 3 ++-
 interface/src/Application.h                | 2 +-
 interface/src/ui/ApplicationCompositor.cpp | 4 +++-
 3 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 3139c21a1d..b4003e24c4 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -2173,8 +2173,9 @@ void Application::maybeToggleMenuVisible(QMouseEvent* event) {
 }
 
 /// called by ApplicationCompositor when in HMD mode and we're faking our mouse movement
-void Application::fakeMouseEvent(QMouseEvent event) {
+void Application::fakeMouseEvent(int type, int x, int y, uint32_t button, uint32_t buttons, uint32_t modifiers) {
     _fakedMouseEvent = true;
+    QMouseEvent event(QEvent::MouseMove, QPoint(x, y), (Qt::MouseButton)button, (Qt::MouseButtons)buttons, (Qt::KeyboardModifiers)modifiers);
     sendEvent(_glWidget, &event);
     _fakedMouseEvent = false;
 }
diff --git a/interface/src/Application.h b/interface/src/Application.h
index 9a1f54a266..b1908fbc34 100644
--- a/interface/src/Application.h
+++ b/interface/src/Application.h
@@ -281,7 +281,7 @@ public slots:
 
     void runTests();
 
-    void fakeMouseEvent(QMouseEvent event);
+    void fakeMouseEvent(int type, int x, int y, quint32 button, quint32 buttons, quint32 modifiers);
 
 private slots:
     void showDesktop();
diff --git a/interface/src/ui/ApplicationCompositor.cpp b/interface/src/ui/ApplicationCompositor.cpp
index 04630fbbc8..3420ed07a8 100644
--- a/interface/src/ui/ApplicationCompositor.cpp
+++ b/interface/src/ui/ApplicationCompositor.cpp
@@ -457,7 +457,9 @@ void ApplicationCompositor::setReticlePosition(glm::vec2 position, bool sendFake
             auto buttons = QApplication::mouseButtons();
             auto modifiers = QApplication::keyboardModifiers();
             QMouseEvent event(QEvent::MouseMove, globalPos, button, buttons, modifiers);
-            QMetaObject::invokeMethod(qApp, "fakeMouseEvent", Qt::AutoConnection, Q_ARG(QMouseEvent, event));
+            QMetaObject::invokeMethod(qApp, "fakeMouseEvent", Qt::AutoConnection,
+                                      Q_ARG(int, (int)QEvent::MouseMove), Q_ARG(int, globalPos.x()), Q_ARG(int, globalPos.y()),
+                                      Q_ARG(quint32, (quint32)button), Q_ARG(quint32, (quint32)buttons), Q_ARG(quint32, (quint32)modifiers));
         }
     } else {
         // NOTE: This is some debugging code we will leave in while debugging various reticle movement strategies,

From 27fb7a3143e64471450d6e608192ad92f6c2c2f3 Mon Sep 17 00:00:00 2001
From: "Anthony J. Thibault" <tony@highfidelity.io>
Date: Tue, 8 Mar 2016 15:14:37 -0800
Subject: [PATCH 5/8] handControllerMouse: improve determination of which
 controller is moving the most

That controller will have exclusive control of the mouse reticle.
---
 examples/controllers/handControllerMouse.js | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/examples/controllers/handControllerMouse.js b/examples/controllers/handControllerMouse.js
index 29e7972cf0..a3f315e10e 100644
--- a/examples/controllers/handControllerMouse.js
+++ b/examples/controllers/handControllerMouse.js
@@ -76,20 +76,20 @@ Script.update.connect(function(deltaTime) {
     // Decide which hand should be controlling the pointer
     // by comparing which one is moving more, and by
     // tending to stay with the one moving more.
-    var BIAS_ADJUST_RATE = 0.5;
-    var BIAS_ADJUST_DEADZONE = 0.05;
-    leftRightBias += (Vec3.length(poseRight.angularVelocity) - Vec3.length(poseLeft.angularVelocity)) * BIAS_ADJUST_RATE;
-    if (leftRightBias < BIAS_ADJUST_DEADZONE) {
-        leftRightBias = 0.0;
-    } else if (leftRightBias > (1.0 - BIAS_ADJUST_DEADZONE)) {
-        leftRightBias = 1.0;
+    var BIAS_ADJUST_PERIOD = 1.0;
+    if (deltaTime > 0.001) {
+        var tau = Math.clamp(deltaTime / BIAS_ADJUST_PERIOD, 0, 1);
+        newLeftRightBias = Vec3.length(poseRight.angularVelocity) - Vec3.length(poseLeft.angularVelocity);
+        leftRightBias = (1 - tau) * leftRightBias + tau * newLeftRightBias;
     }
 
+    var alpha = leftRightBias > 0 ? 1 : 0;
+
     // Velocity filter the hand rotation used to position reticle so that it is easier to target small things with the hand controllers
     var VELOCITY_FILTER_GAIN = 0.5;
     filteredRotatedLeft = Vec3.mix(filteredRotatedLeft, rotatedLeft, Math.clamp(Vec3.length(poseLeft.angularVelocity) * VELOCITY_FILTER_GAIN, 0.0, 1.0));
     filteredRotatedRight = Vec3.mix(filteredRotatedRight, rotatedRight, Math.clamp(Vec3.length(poseRight.angularVelocity) * VELOCITY_FILTER_GAIN, 0.0, 1.0));
-    var rotated = Vec3.mix(filteredRotatedLeft, filteredRotatedRight, leftRightBias);
+    var rotated = Vec3.mix(filteredRotatedLeft, filteredRotatedRight, alpha);
 
     var absolutePitch = rotated.y; // from 1 down to -1 up ... but note: if you rotate down "too far" it starts to go up again...
     var absoluteYaw = -rotated.x; // from -1 left to 1 right
@@ -110,7 +110,7 @@ Script.update.connect(function(deltaTime) {
 
     var AVERAGING_INTERVAL = 0.95;
     var MINIMUM_CONTROLLER_ANGULAR_VELOCITY = 0.03;
-    var angularVelocityMagnitude = Vec3.length(poseLeft.angularVelocity) * (1.0 - leftRightBias) + Vec3.length(poseRight.angularVelocity) * leftRightBias;
+    var angularVelocityMagnitude = Vec3.length(poseLeft.angularVelocity) * (1.0 - alpha) + Vec3.length(poseRight.angularVelocity) * alpha;
     angularVelocityTrailingAverage = angularVelocityTrailingAverage * AVERAGING_INTERVAL + angularVelocityMagnitude * (1.0 - AVERAGING_INTERVAL);
 
     if (!(xRatio == 0.5 && yRatio == 0) && (angularVelocityTrailingAverage > MINIMUM_CONTROLLER_ANGULAR_VELOCITY) && ((x != lastX) || (y != lastY))) {

From ce5d83d551fed25bd84467ea401aff4950b12cf4 Mon Sep 17 00:00:00 2001
From: "Anthony J. Thibault" <tony@highfidelity.io>
Date: Tue, 8 Mar 2016 15:32:12 -0800
Subject: [PATCH 6/8] handConttollerMouse.js: improved comments

---
 examples/controllers/handControllerMouse.js | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/examples/controllers/handControllerMouse.js b/examples/controllers/handControllerMouse.js
index a3f315e10e..518735bdc0 100644
--- a/examples/controllers/handControllerMouse.js
+++ b/examples/controllers/handControllerMouse.js
@@ -76,13 +76,15 @@ Script.update.connect(function(deltaTime) {
     // Decide which hand should be controlling the pointer
     // by comparing which one is moving more, and by
     // tending to stay with the one moving more.
-    var BIAS_ADJUST_PERIOD = 1.0;
     if (deltaTime > 0.001) {
+        // leftRightBias is a running average of the difference in angular hand speed.
+        // a positive leftRightBias indicates the right hand is spinning faster then the left hand.
+        // a negative leftRightBias indicates the left hand is spnning faster.
+        var BIAS_ADJUST_PERIOD = 1.0;
         var tau = Math.clamp(deltaTime / BIAS_ADJUST_PERIOD, 0, 1);
         newLeftRightBias = Vec3.length(poseRight.angularVelocity) - Vec3.length(poseLeft.angularVelocity);
         leftRightBias = (1 - tau) * leftRightBias + tau * newLeftRightBias;
     }
-
     var alpha = leftRightBias > 0 ? 1 : 0;
 
     // Velocity filter the hand rotation used to position reticle so that it is easier to target small things with the hand controllers

From f82dc4569eedbbbc0ebb2153891bb78d5a709983 Mon Sep 17 00:00:00 2001
From: "Anthony J. Thibault" <tony@highfidelity.io>
Date: Tue, 8 Mar 2016 15:59:17 -0800
Subject: [PATCH 7/8] handControllerMouse.js: removed non-uniform scaling.

If your controller moves in a circle, the mouse pointer should as well.
---
 examples/controllers/handControllerMouse.js | 14 +++-----------
 1 file changed, 3 insertions(+), 11 deletions(-)

diff --git a/examples/controllers/handControllerMouse.js b/examples/controllers/handControllerMouse.js
index 518735bdc0..abac0ee397 100644
--- a/examples/controllers/handControllerMouse.js
+++ b/examples/controllers/handControllerMouse.js
@@ -96,16 +96,8 @@ Script.update.connect(function(deltaTime) {
     var absolutePitch = rotated.y; // from 1 down to -1 up ... but note: if you rotate down "too far" it starts to go up again...
     var absoluteYaw = -rotated.x; // from -1 left to 1 right
 
-    var ROTATION_BOUND = 0.6;
-    var clampYaw = Math.clamp(absoluteYaw, -ROTATION_BOUND, ROTATION_BOUND);
-    var clampPitch = Math.clamp(absolutePitch, -ROTATION_BOUND, ROTATION_BOUND);
-
-    // using only from -ROTATION_BOUND to ROTATION_BOUND
-    var xRatio = (clampYaw + ROTATION_BOUND) / (2 * ROTATION_BOUND);
-    var yRatio = (clampPitch + ROTATION_BOUND) / (2 * ROTATION_BOUND);
-
-    var x = screenSizeX * xRatio;
-    var y = screenSizeY * yRatio;
+    var x = Math.clamp(screenSizeX * (absoluteYaw + 0.5), 0, screenSizeX);
+    var y = Math.clamp(screenSizeX * (absolutePitch + 0.5), 0, screenSizeY);
 
     // don't move the reticle with the hand controllers unless the controllers are actually being moved
     //  take a time average of angular velocity, and don't move mouse at all if it's below threshold
@@ -115,7 +107,7 @@ Script.update.connect(function(deltaTime) {
     var angularVelocityMagnitude = Vec3.length(poseLeft.angularVelocity) * (1.0 - alpha) + Vec3.length(poseRight.angularVelocity) * alpha;
     angularVelocityTrailingAverage = angularVelocityTrailingAverage * AVERAGING_INTERVAL + angularVelocityMagnitude * (1.0 - AVERAGING_INTERVAL);
 
-    if (!(xRatio == 0.5 && yRatio == 0) && (angularVelocityTrailingAverage > MINIMUM_CONTROLLER_ANGULAR_VELOCITY) && ((x != lastX) || (y != lastY))) {
+    if ((angularVelocityTrailingAverage > MINIMUM_CONTROLLER_ANGULAR_VELOCITY) && ((x != lastX) || (y != lastY))) {
         moveReticleAbsolute(x, y);
         lastX = x;
         lastY = y;

From e1a3c5b618eb42a87da22cbeb0d88e35b2f640c8 Mon Sep 17 00:00:00 2001
From: "Anthony J. Thibault" <tony@highfidelity.io>
Date: Wed, 9 Mar 2016 10:31:58 -0800
Subject: [PATCH 8/8] Fix for vertical pitch offset.

Also, added hysteresis to the controller selection, to prevent flapping back and forth between controllers when they are at rest.
---
 examples/controllers/handControllerMouse.js | 16 ++++++++++++++--
 1 file changed, 14 insertions(+), 2 deletions(-)

diff --git a/examples/controllers/handControllerMouse.js b/examples/controllers/handControllerMouse.js
index abac0ee397..921999f96a 100644
--- a/examples/controllers/handControllerMouse.js
+++ b/examples/controllers/handControllerMouse.js
@@ -54,6 +54,7 @@ function debugPrint(message) {
 var leftRightBias = 0.0;
 var filteredRotatedLeft = Vec3.UNIT_NEG_Y;
 var filteredRotatedRight = Vec3.UNIT_NEG_Y;
+var lastAlpha = 0;
 
 Script.update.connect(function(deltaTime) {
 
@@ -85,7 +86,18 @@ Script.update.connect(function(deltaTime) {
         newLeftRightBias = Vec3.length(poseRight.angularVelocity) - Vec3.length(poseLeft.angularVelocity);
         leftRightBias = (1 - tau) * leftRightBias + tau * newLeftRightBias;
     }
-    var alpha = leftRightBias > 0 ? 1 : 0;
+
+    // add a bit of hysteresis to prevent control flopping back and forth
+    // between hands when they are both mostly stationary.
+    var alpha;
+    var HYSTERESIS_OFFSET = 0.25;
+    if (lastAlpha > 0.5) {
+        // prefer right hand over left
+        alpha = leftRightBias > -HYSTERESIS_OFFSET ? 1 : 0;
+    } else {
+        alpha = leftRightBias > HYSTERESIS_OFFSET ? 1 : 0;
+    }
+    lastAlpha = alpha;
 
     // Velocity filter the hand rotation used to position reticle so that it is easier to target small things with the hand controllers
     var VELOCITY_FILTER_GAIN = 0.5;
@@ -97,7 +109,7 @@ Script.update.connect(function(deltaTime) {
     var absoluteYaw = -rotated.x; // from -1 left to 1 right
 
     var x = Math.clamp(screenSizeX * (absoluteYaw + 0.5), 0, screenSizeX);
-    var y = Math.clamp(screenSizeX * (absolutePitch + 0.5), 0, screenSizeY);
+    var y = Math.clamp(screenSizeX * absolutePitch, 0, screenSizeY);
 
     // don't move the reticle with the hand controllers unless the controllers are actually being moved
     //  take a time average of angular velocity, and don't move mouse at all if it's below threshold