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