From bab7d1e596ac5975dc73501a59033fa7c450d4fc Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Tue, 11 Apr 2017 12:17:28 -0700 Subject: [PATCH 01/47] 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 #include +#include + #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 +#include "../Pose.h" + class QJsonValue; namespace controller { @@ -34,6 +36,8 @@ namespace controller { using Factory = hifi::SimpleFactory; 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(translate)); + return this; +} + QObject* RouteBuilderProxy::constrainToInteger() { addFilter(std::make_shared()); 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 + #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 +#include + +#include + +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 + +#include + +#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 Date: Tue, 11 Apr 2017 14:06:36 -0700 Subject: [PATCH 02/47] 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(translate)); return this; } +QObject* RouteBuilderProxy::transform(glm::mat4 transform) { + addFilter(std::make_shared(transform)); + return this; +} + +QObject* RouteBuilderProxy::rotate(glm::quat rotation) { + addFilter(std::make_shared(rotation)); + return this; +} + QObject* RouteBuilderProxy::constrainToInteger() { addFilter(std::make_shared()); 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 +#include + +#include + +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 + +#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 +#include + +#include + +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 + +#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 -#include - #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 Date: Tue, 11 Apr 2017 14:18:21 -0700 Subject: [PATCH 03/47] 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; 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 Date: Tue, 11 Apr 2017 17:03:33 -0700 Subject: [PATCH 04/47] 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(transform)); + return this; +} + QObject* RouteBuilderProxy::rotate(glm::quat rotation) { addFilter(std::make_shared(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 + +#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 Date: Wed, 12 Apr 2017 13:43:57 -0700 Subject: [PATCH 05/47] 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 -#include - -#include - -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 -#include - -#include - -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 -#include - -#include - -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 Date: Wed, 12 Apr 2017 16:11:06 -0700 Subject: [PATCH 06/47] 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 0cd2863df452a39b309a298b14c68322b7b73ad7 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 18 Apr 2017 14:42:32 -0700 Subject: [PATCH 07/47] start on routing HMD position and rotation through controller interface (cherry picked from commit bca5241bae7674ff7e41c037d84bb78e5152046a) --- interface/resources/controllers/standard.json | 7 +- interface/src/Application.cpp | 7 ++ interface/src/avatar/MyAvatar.cpp | 68 ++++++++++++++++++- interface/src/avatar/MyAvatar.h | 17 ++++- .../controllers/src/controllers/Actions.cpp | 3 + .../controllers/src/controllers/Actions.h | 3 + 6 files changed, 101 insertions(+), 4 deletions(-) diff --git a/interface/resources/controllers/standard.json b/interface/resources/controllers/standard.json index 53285ea974..62eec9bc3c 100644 --- a/interface/resources/controllers/standard.json +++ b/interface/resources/controllers/standard.json @@ -61,6 +61,11 @@ { "from": "Standard.RightHand", "to": "Actions.RightHand" }, { "from": "Standard.LeftFoot", "to": "Actions.LeftFoot" }, - { "from": "Standard.RightFoot", "to": "Actions.RightFoot" } + { "from": "Standard.RightFoot", "to": "Actions.RightFoot" }, + + { "from": "Standard.Hips", "to": "Actions.Hips" }, + { "from": "Standard.Spine2", "to": "Actions.Spine2" }, + + { "from": "Standard.Head", "to": "Actions.Head" } ] } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c6cd185034..5b014f7009 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4395,6 +4395,13 @@ void Application::update(float deltaTime) { controller::Pose rightFootPose = userInputMapper->getPoseState(controller::Action::RIGHT_FOOT); myAvatar->setFootControllerPosesInSensorFrame(leftFootPose.transform(avatarToSensorMatrix), rightFootPose.transform(avatarToSensorMatrix)); + controller::Pose hipsPose = userInputMapper->getPoseState(controller::Action::HIPS); + controller::Pose spine2Pose = userInputMapper->getPoseState(controller::Action::SPINE2); + myAvatar->setSpineControllerPosesInSensorFrame(hipsPose.transform(avatarToSensorMatrix), spine2Pose.transform(avatarToSensorMatrix)); + + controller::Pose headPose = userInputMapper->getPoseState(controller::Action::HEAD); + myAvatar->setHeadControllerPoseInSensorFrame(headPose.transform(avatarToSensorMatrix)); + updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process... updateDialogs(deltaTime); // update various stats dialogs if present diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index da09bfba7e..0906dc7f7f 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -642,6 +642,7 @@ void MyAvatar::updateSensorToWorldMatrix() { updateJointFromController(controller::Action::RIGHT_HAND, _controllerRightHandMatrixCache); } + // Update avatar head rotation with sensor data void MyAvatar::updateFromTrackers(float deltaTime) { glm::vec3 estimatedPosition, estimatedRotation; @@ -660,8 +661,9 @@ void MyAvatar::updateFromTrackers(float deltaTime) { estimatedPosition.x *= -1.0f; _trackedHeadPosition = estimatedPosition; - const float OCULUS_LEAN_SCALE = 0.05f; - estimatedPosition /= OCULUS_LEAN_SCALE; + // wut + // const float OCULUS_LEAN_SCALE = 0.05f; + // estimatedPosition /= OCULUS_LEAN_SCALE; } else if (inFacetracker) { estimatedPosition = tracker->getHeadTranslation(); _trackedHeadPosition = estimatedPosition; @@ -1384,6 +1386,68 @@ controller::Pose MyAvatar::getRightFootControllerPoseInAvatarFrame() const { return getRightFootControllerPoseInWorldFrame().transform(invAvatarMatrix); } +void MyAvatar::setSpineControllerPosesInSensorFrame(const controller::Pose& hips, const controller::Pose& spine2) { + if (controller::InputDevice::getLowVelocityFilter()) { + auto oldHipsPose = getHipsControllerPoseInSensorFrame(); + auto oldSpine2Pose = getSpine2ControllerPoseInSensorFrame(); + _hipsControllerPoseInSensorFrameCache.set(applyLowVelocityFilter(oldHipsPose, hips)); + _spine2ControllerPoseInSensorFrameCache.set(applyLowVelocityFilter(oldSpine2Pose, spine2)); + } else { + _hipsControllerPoseInSensorFrameCache.set(hips); + _spine2ControllerPoseInSensorFrameCache.set(spine2); + } +} + +controller::Pose MyAvatar::getHipsControllerPoseInSensorFrame() const { + return _hipsControllerPoseInSensorFrameCache.get(); +} + +controller::Pose MyAvatar::getSpine2ControllerPoseInSensorFrame() const { + return _spine2ControllerPoseInSensorFrameCache.get(); +} + +controller::Pose MyAvatar::getHipsControllerPoseInWorldFrame() const { + return _hipsControllerPoseInSensorFrameCache.get().transform(getSensorToWorldMatrix()); +} + +controller::Pose MyAvatar::getSpine2ControllerPoseInWorldFrame() const { + return _spine2ControllerPoseInSensorFrameCache.get().transform(getSensorToWorldMatrix()); +} + +controller::Pose MyAvatar::getHipsControllerPoseInAvatarFrame() const { + glm::mat4 invAvatarMatrix = glm::inverse(createMatFromQuatAndPos(getOrientation(), getPosition())); + return getHipsControllerPoseInWorldFrame().transform(invAvatarMatrix); +} + +controller::Pose MyAvatar::getSpine2ControllerPoseInAvatarFrame() const { + glm::mat4 invAvatarMatrix = glm::inverse(createMatFromQuatAndPos(getOrientation(), getPosition())); + return getSpine2ControllerPoseInWorldFrame().transform(invAvatarMatrix); +} + +void MyAvatar::setHeadControllerPoseInSensorFrame(const controller::Pose& headPose) { + bool inHmd = qApp->isHMDMode(); + Head* head = getHead(); + if (inHmd) { + _headControllerPoseInSensorFrameCache.set(headPose); + _trackedHeadPosition = headPose.translation; + head->setDeltaPitch(headPose.rotation.x); + head->setDeltaYaw(headPose.rotation.y); + head->setDeltaRoll(headPose.rotation.z); + } +} + +controller::Pose MyAvatar::getHeadControllerPoseInSensorFrame() const { + return _headControllerPoseInSensorFrameCache.get(); +} + +controller::Pose MyAvatar::getHeadControllerPoseInWorldFrame() const { + return _headControllerPoseInSensorFrameCache.get().transform(getSensorToWorldMatrix()); +} + +controller::Pose MyAvatar::getHeadControllerPoseInAvatarFrame() const { + glm::mat4 invAvatarMatrix = glm::inverse(createMatFromQuatAndPos(getOrientation(), getPosition())); + return getHeadControllerPoseInWorldFrame().transform(invAvatarMatrix); +} void MyAvatar::updateMotors() { _characterController.clearMotors(); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 0146bd11a4..01b4dc824b 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -453,6 +453,19 @@ public: controller::Pose getLeftFootControllerPoseInAvatarFrame() const; controller::Pose getRightFootControllerPoseInAvatarFrame() const; + void setSpineControllerPosesInSensorFrame(const controller::Pose& hips, const controller::Pose& spine2); + controller::Pose getHipsControllerPoseInSensorFrame() const; + controller::Pose getSpine2ControllerPoseInSensorFrame() const; + controller::Pose getHipsControllerPoseInWorldFrame() const; + controller::Pose getSpine2ControllerPoseInWorldFrame() const; + controller::Pose getHipsControllerPoseInAvatarFrame() const; + controller::Pose getSpine2ControllerPoseInAvatarFrame() const; + + void setHeadControllerPoseInSensorFrame(const controller::Pose& headPose); + controller::Pose getHeadControllerPoseInSensorFrame() const; + controller::Pose getHeadControllerPoseInWorldFrame() const; + controller::Pose getHeadControllerPoseInAvatarFrame() const; + bool hasDriveInput() const; Q_INVOKABLE void setCharacterControllerEnabled(bool enabled); @@ -693,9 +706,11 @@ private: // These are stored in SENSOR frame ThreadSafeValueCache _leftHandControllerPoseInSensorFrameCache { controller::Pose() }; ThreadSafeValueCache _rightHandControllerPoseInSensorFrameCache { controller::Pose() }; - ThreadSafeValueCache _leftFootControllerPoseInSensorFrameCache{ controller::Pose() }; ThreadSafeValueCache _rightFootControllerPoseInSensorFrameCache{ controller::Pose() }; + ThreadSafeValueCache _hipsControllerPoseInSensorFrameCache{ controller::Pose() }; + ThreadSafeValueCache _spine2ControllerPoseInSensorFrameCache{ controller::Pose() }; + ThreadSafeValueCache _headControllerPoseInSensorFrameCache{ controller::Pose() }; bool _hmdLeanRecenterEnabled = true; diff --git a/libraries/controllers/src/controllers/Actions.cpp b/libraries/controllers/src/controllers/Actions.cpp index 300fa684d9..62a10c851f 100644 --- a/libraries/controllers/src/controllers/Actions.cpp +++ b/libraries/controllers/src/controllers/Actions.cpp @@ -53,6 +53,9 @@ namespace controller { makePosePair(Action::RIGHT_HAND, "RightHand"), makePosePair(Action::LEFT_FOOT, "LeftFoot"), makePosePair(Action::RIGHT_FOOT, "RightFoot"), + makePosePair(Action::HIPS, "Hips"), + makePosePair(Action::SPINE2, "Spine2"), + makePosePair(Action::HEAD, "Head"), makeButtonPair(Action::LEFT_HAND_CLICK, "LeftHandClick"), makeButtonPair(Action::RIGHT_HAND_CLICK, "RightHandClick"), diff --git a/libraries/controllers/src/controllers/Actions.h b/libraries/controllers/src/controllers/Actions.h index edf3dee07a..534f01d865 100644 --- a/libraries/controllers/src/controllers/Actions.h +++ b/libraries/controllers/src/controllers/Actions.h @@ -44,6 +44,9 @@ enum class Action { RIGHT_HAND, LEFT_FOOT, RIGHT_FOOT, + HIPS, + SPINE2, + HEAD, LEFT_HAND_CLICK, RIGHT_HAND_CLICK, From 23592f4a53294ff2c24b44a97f87abc053a5beb3 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 19 Apr 2017 07:21:50 -0700 Subject: [PATCH 08/47] get rid of _trackedHeadPosition (cherry picked from commit e909938b232e11400832ae7dd29de16968967668) --- interface/src/avatar/MyAvatar.cpp | 8 -------- interface/src/avatar/MyAvatar.h | 1 - 2 files changed, 9 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 0906dc7f7f..659077188f 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -642,7 +642,6 @@ void MyAvatar::updateSensorToWorldMatrix() { updateJointFromController(controller::Action::RIGHT_HAND, _controllerRightHandMatrixCache); } - // Update avatar head rotation with sensor data void MyAvatar::updateFromTrackers(float deltaTime) { glm::vec3 estimatedPosition, estimatedRotation; @@ -659,14 +658,8 @@ void MyAvatar::updateFromTrackers(float deltaTime) { if (inHmd) { estimatedPosition = extractTranslation(getHMDSensorMatrix()); estimatedPosition.x *= -1.0f; - _trackedHeadPosition = estimatedPosition; - - // wut - // const float OCULUS_LEAN_SCALE = 0.05f; - // estimatedPosition /= OCULUS_LEAN_SCALE; } else if (inFacetracker) { estimatedPosition = tracker->getHeadTranslation(); - _trackedHeadPosition = estimatedPosition; estimatedRotation = glm::degrees(safeEulerAngles(tracker->getHeadRotation())); } @@ -1429,7 +1422,6 @@ void MyAvatar::setHeadControllerPoseInSensorFrame(const controller::Pose& headPo Head* head = getHead(); if (inHmd) { _headControllerPoseInSensorFrameCache.set(headPose); - _trackedHeadPosition = headPose.translation; head->setDeltaPitch(headPose.rotation.x); head->setDeltaYaw(headPose.rotation.y); head->setDeltaRoll(headPose.rotation.z); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 01b4dc824b..cea5962638 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -353,7 +353,6 @@ public: eyeContactTarget getEyeContactTarget(); - Q_INVOKABLE glm::vec3 getTrackedHeadPosition() const { return _trackedHeadPosition; } Q_INVOKABLE glm::vec3 getHeadPosition() const { return getHead()->getPosition(); } Q_INVOKABLE float getHeadFinalYaw() const { return getHead()->getFinalYaw(); } Q_INVOKABLE float getHeadFinalRoll() const { return getHead()->getFinalRoll(); } From dc19f3772643eff3516e19435136c043bf9fb27c Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 25 Apr 2017 15:04:47 -0700 Subject: [PATCH 09/47] input/controller routing thru skeleton model and rig. --- interface/src/avatar/SkeletonModel.cpp | 39 +++++++++++++++++++------- libraries/animation/src/Rig.cpp | 2 +- libraries/animation/src/Rig.h | 12 ++++---- libraries/shared/src/GLMHelpers.cpp | 5 ++++ libraries/shared/src/GLMHelpers.h | 7 +++++ 5 files changed, 49 insertions(+), 16 deletions(-) diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 3cf866fb6b..ccee65d3e6 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -108,8 +108,6 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { Rig::HeadParameters headParams; if (qApp->isHMDMode()) { - headParams.isInHMD = true; - // get HMD position from sensor space into world space, and back into rig space glm::mat4 worldHMDMat = myAvatar->getSensorToWorldMatrix() * myAvatar->getHMDSensorMatrix(); glm::mat4 rigToWorld = createMatFromQuatAndPos(getRotation(), getTranslation()); @@ -119,18 +117,39 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { headParams.rigHeadPosition = extractTranslation(rigHMDMat); headParams.rigHeadOrientation = extractRotation(rigHMDMat); headParams.worldHeadOrientation = extractRotation(worldHMDMat); + headParams.headEnabled = true; + } else { + auto avatarHeadPose = myAvatar->getHeadControllerPoseInAvatarFrame(); + if (avatarHeadPose.isValid()) { + glm::mat4 rigHeadMat = Matrices::Y_180 * createMatFromQuatAndPos(avatarHeadPose.getRotation(), avatarHeadPose.getTranslation()); + headParams.rigHeadPosition = extractTranslation(rigHeadMat); + headParams.rigHeadOrientation = glmExtractRotation(rigHeadMat); + headParams.worldHeadOrientation = myAvatar->getHeadControllerPoseInWorldFrame().getTranslation(); + headParams.headEnabled = true; + } else { + // even though full head IK is disabled, the rig still needs the head orientation to rotate the head up and down in desktop mode. + headParams.rigHeadOrientation = Quaternions::Y_180 * head->getFinalOrientationInLocalFrame(); + headParams.worldHeadOrientation = head->getFinalOrientationInWorldFrame(); + headParams.headEnabled = false; + } + } - // TODO: if hips target sensor is valid. - // Copy it into headParams.hipsMatrix, and set headParams.hipsEnabled to true. - - headParams.hipsEnabled = false; + auto avatarHipsPose = myAvatar->getHipsControllerPoseInAvatarFrame(); + if (avatarHipsPose.isValid()) { + glm::mat4 rigHipsMat = Matrices::Y_180 * createMatFromQuatAndPos(avatarHipsPose.getRotation(), avatarHipsPose.getTranslation()); + headParams.hipsMatrix = rigHipsMat; + headParams.hipsEnabled = true; } else { headParams.hipsEnabled = false; - headParams.isInHMD = false; + } - // We don't have a valid localHeadPosition. - headParams.rigHeadOrientation = Quaternions::Y_180 * head->getFinalOrientationInLocalFrame(); - headParams.worldHeadOrientation = head->getFinalOrientationInWorldFrame(); + auto avatarSpine2Pose = myAvatar->getSpine2ControllerPoseInAvatarFrame(); + if (avatarSpine2Pose.isValid()) { + glm::mat4 rigSpine2Mat = Matrices::Y_180 * createMatFromQuatAndPos(avatarSpine2Pose.getRotation(), avatarSpine2Pose.getTranslation()); + headParams.spine2Matrix = rigSpine2Mat; + headParams.spine2Enabled = true; + } else { + headParams.spine2Enabled = false; } headParams.neckJointIndex = geometry.neckJointIndex; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 116758b1ba..9ef2a554c6 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1082,7 +1082,7 @@ void Rig::computeHeadNeckAnimVars(const AnimPose& hmdPose, glm::vec3& headPositi void Rig::updateNeckJoint(int index, const HeadParameters& params) { if (_animSkeleton && index >= 0 && index < _animSkeleton->getNumJoints()) { glm::quat yFlip180 = glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f)); - if (params.isInHMD) { + if (params.headEnabled) { glm::vec3 headPos, neckPos; glm::quat headRot, neckRot; diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 89f0d624f9..b66ca95042 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -42,12 +42,14 @@ public: }; struct HeadParameters { - glm::quat worldHeadOrientation = glm::quat(); // world space (-z forward) - glm::quat rigHeadOrientation = glm::quat(); // rig space (-z forward) - glm::vec3 rigHeadPosition = glm::vec3(); // rig space - glm::mat4 hipsMatrix = glm::mat4(); // rig space + glm::mat4 hipsMatrix = glm::mat4(); // rig space + glm::mat4 spine2Matrix = glm::mat4(); // rig space + glm::quat worldHeadOrientation = glm::quat(); // world space (-z forward) + glm::quat rigHeadOrientation = glm::quat(); // rig space (-z forward) + glm::vec3 rigHeadPosition = glm::vec3(); // rig space bool hipsEnabled = false; - bool isInHMD = false; + bool headEnabled = false; + bool spine2Enabled = false; int neckJointIndex = -1; bool isTalking = false; }; diff --git a/libraries/shared/src/GLMHelpers.cpp b/libraries/shared/src/GLMHelpers.cpp index ec244553f8..db42fef8bc 100644 --- a/libraries/shared/src/GLMHelpers.cpp +++ b/libraries/shared/src/GLMHelpers.cpp @@ -38,6 +38,11 @@ const quat Quaternions::X_180{ 0.0f, 1.0f, 0.0f, 0.0f }; const quat Quaternions::Y_180{ 0.0f, 0.0f, 1.0f, 0.0f }; const quat Quaternions::Z_180{ 0.0f, 0.0f, 0.0f, 1.0f }; +const mat4 Matrices::IDENTITY { glm::mat4() }; +const mat4 Matrices::X_180 { createMatFromQuatAndPos(Quaternions::X_180, Vectors::ZERO) }; +const mat4 Matrices::Y_180 { createMatFromQuatAndPos(Quaternions::Y_180, Vectors::ZERO) }; +const mat4 Matrices::Z_180 { createMatFromQuatAndPos(Quaternions::Z_180, Vectors::ZERO) }; + // Safe version of glm::mix; based on the code in Nick Bobick's article, // http://www.gamasutra.com/features/19980703/quaternions_01.htm (via Clyde, // https://github.com/threerings/clyde/blob/master/src/main/java/com/threerings/math/Quaternion.java) diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index deb87930fc..fd75fa416c 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -54,6 +54,13 @@ const glm::vec3 IDENTITY_FORWARD = glm::vec3( 0.0f, 0.0f,-1.0f); glm::quat safeMix(const glm::quat& q1, const glm::quat& q2, float alpha); +class Matrices { +public: + static const mat4 IDENTITY; + static const mat4 X_180; + static const mat4 Y_180; + static const mat4 Z_180; +}; class Quaternions { public: From 1931a8d1f6982b9264b650e7166905249799f709 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 25 Apr 2017 15:05:52 -0700 Subject: [PATCH 10/47] viveMotionCapture.js: can now disable puck control, with a second controller squeeze --- scripts/developer/tests/viveMotionCapture.js | 130 +++++++++---------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/scripts/developer/tests/viveMotionCapture.js b/scripts/developer/tests/viveMotionCapture.js index 6cb0f92b9b..5496b475be 100644 --- a/scripts/developer/tests/viveMotionCapture.js +++ b/scripts/developer/tests/viveMotionCapture.js @@ -8,7 +8,7 @@ var TRACKED_OBJECT_POSES = [ "TrackedObject12", "TrackedObject13", "TrackedObject14", "TrackedObject15" ]; -var calibrated = false; +var triggerPressHandled = false; var rightTriggerPressed = false; var leftTriggerPressed = false; @@ -43,21 +43,6 @@ var SENSOR_CONFIG_NAMES = [ "Auto" ]; -var ANIM_VARS = [ - "leftFootType", - "leftFootPosition", - "leftFootRotation", - "rightFootType", - "rightFootPosition", - "rightFootRotation", - "hipsType", - "hipsPosition", - "hipsRotation", - "spine2Type", - "spine2Position", - "spine2Rotation" -]; - var sensorConfig = AUTO; var Y_180 = {x: 0, y: 1, z: 0, w: 0}; @@ -86,7 +71,7 @@ function computeDefaultToReferenceXform() { return defaultToReferenceXform; } else { - return new Xform.ident(); + return Xform.ident(); } } @@ -200,71 +185,86 @@ function computeIKTargetXform(jointInfo) { function update(dt) { if (rightTriggerPressed && leftTriggerPressed) { - if (!calibrated) { - calibrate(); - calibrated = true; - + if (!triggerPressHandled) { + triggerPressHandled = true; if (handlerId) { - MyAvatar.removeAnimationStateHandler(handlerId); - } + print("AJT: UN-CALIBRATE!"); - handlerId = MyAvatar.addAnimationStateHandler(function (props) { - - var result = {}, xform; - if (rightFoot) { - xform = computeIKTargetXform(rightFoot); - result.rightFootType = ikTypes.RotationAndPosition; - result.rightFootPosition = xform.pos; - result.rightFootRotation = xform.rot; - } else { - result.rightFootType = props.rightFootType; - result.rightFootPosition = props.rightFootPosition; - result.rightFootRotation = props.rightFootRotation; + // go back to normal, vive pucks will be ignored. + leftFoot = undefined; + rightFoot = undefined; + hips = undefined; + spine2 = undefined; + if (handlerId) { + print("AJT: un-hooking animation state handler"); + MyAvatar.removeAnimationStateHandler(handlerId); + handlerId = undefined; } + } else { + print("AJT: CALIBRATE!"); + calibrate(); + + var animVars = []; if (leftFoot) { - xform = computeIKTargetXform(leftFoot); - result.leftFootType = ikTypes.RotationAndPosition; - result.leftFootPosition = xform.pos; - result.leftFootRotation = xform.rot; - } else { - result.leftFootType = props.leftFootType; - result.leftFootPosition = props.leftFootPosition; - result.leftFootRotation = props.leftFootRotation; + animVars.push("leftFootType"); + animVars.push("leftFootPosition"); + animVars.push("leftFootRotation"); + } + if (rightFoot) { + animVars.push("rightFootType"); + animVars.push("rightFootPosition"); + animVars.push("rightFootRotation"); } - if (hips) { - xform = computeIKTargetXform(hips); - result.hipsType = ikTypes.RotationAndPosition; - result.hipsPosition = xform.pos; - result.hipsRotation = xform.rot; - } else { - result.hipsType = props.hipsType; - result.hipsPosition = props.hipsPosition; - result.hipsRotation = props.hipsRotation; + animVars.push("hipsType"); + animVars.push("hipsPosition"); + animVars.push("hipsRotation"); } - if (spine2) { - xform = computeIKTargetXform(spine2); - result.spine2Type = ikTypes.RotationAndPosition; - result.spine2Position = xform.pos; - result.spine2Rotation = xform.rot; - } else { - result.spine2Type = ikTypes.Off; + animVars.push("spine2Type"); + animVars.push("spine2Position"); + animVars.push("spine2Rotation"); } - return result; - }, ANIM_VARS); - + // hook up new anim state handler that maps vive pucks to ik system. + handlerId = MyAvatar.addAnimationStateHandler(function (props) { + var result = {}, xform; + if (rightFoot) { + xform = computeIKTargetXform(rightFoot); + result.rightFootType = ikTypes.RotationAndPosition; + result.rightFootPosition = xform.pos; + result.rightFootRotation = xform.rot; + } + if (leftFoot) { + xform = computeIKTargetXform(leftFoot); + result.leftFootType = ikTypes.RotationAndPosition; + result.leftFootPosition = xform.pos; + result.leftFootRotation = xform.rot; + } + if (hips) { + xform = computeIKTargetXform(hips); + result.hipsType = ikTypes.RotationAndPosition; + result.hipsPosition = xform.pos; + result.hipsRotation = xform.rot; + } + if (spine2) { + xform = computeIKTargetXform(spine2); + result.spine2Type = ikTypes.RotationAndPosition; + result.spine2Position = xform.pos; + result.spine2Rotation = xform.rot; + } + return result; + }, animVars); + } } } else { - calibrated = false; + triggerPressHandled = false; } var drawMarkers = false; if (drawMarkers) { var RED = {x: 1, y: 0, z: 0, w: 1}; - var GREEN = {x: 0, y: 1, z: 0, w: 1}; var BLUE = {x: 0, y: 0, z: 1, w: 1}; if (leftFoot) { @@ -304,4 +304,4 @@ Script.scriptEnding.connect(function () { Controller.disableMapping(MAPPING_NAME); Script.update.disconnect(update); }); -var TRIGGER_OFF_VALUE = 0.1; + From 1fc9f4c93dc7b8654653667128ea8ac383875793 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 25 Apr 2017 17:22:47 -0700 Subject: [PATCH 11/47] Hips and Spine are routed thru input system properly --- libraries/animation/src/Rig.cpp | 22 +++++++++++++++---- .../src/controllers/StandardController.cpp | 3 +++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 9ef2a554c6..53f76d82ff 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1028,13 +1028,18 @@ void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) { if (params.hipsEnabled) { _animVars.set("hipsType", (int)IKTarget::Type::RotationAndPosition); _animVars.set("hipsPosition", extractTranslation(params.hipsMatrix)); - _animVars.set("hipsRotation", glmExtractRotation(params.hipsMatrix) * Quaternions::Y_180); + _animVars.set("hipsRotation", glmExtractRotation(params.hipsMatrix)); } else { _animVars.set("hipsType", (int)IKTarget::Type::Unknown); } - // by default this IK target is disabled. - _animVars.set("spine2Type", (int)IKTarget::Type::Unknown); + if (params.spine2Enabled) { + _animVars.set("spine2Type", (int)IKTarget::Type::RotationAndPosition); + _animVars.set("spine2Position", extractTranslation(params.spine2Matrix)); + _animVars.set("spine2Rotation", glmExtractRotation(params.spine2Matrix)); + } else { + _animVars.set("spine2Type", (int)IKTarget::Type::Unknown); + } } void Rig::updateFromEyeParameters(const EyeParameters& params) { @@ -1105,7 +1110,16 @@ void Rig::updateNeckJoint(int index, const HeadParameters& params) { _animVars.set("headPosition", headPos); _animVars.set("headRotation", headRot); - _animVars.set("headType", (int)IKTarget::Type::HmdHead); + + if (params.hipsEnabled) { + // Since there is an explicit hips ik target, switch the head to use the more generic RotationAndPosition IK chain type. + // this will allow the spine to bend more, ensuring that it can reach the head target position. + _animVars.set("headType", (int)IKTarget::Type::RotationAndPosition); + } else { + // When there is no hips IK target, use the HmdHead IK chain type. This will make the spine very stiff, + // but because the IK _hipsOffset is enabled, the hips will naturally follow underneath the head. + _animVars.set("headType", (int)IKTarget::Type::HmdHead); + } _animVars.set("neckPosition", neckPos); _animVars.set("neckRotation", neckRot); _animVars.set("neckType", (int)IKTarget::Type::Unknown); // 'Unknown' disables the target diff --git a/libraries/controllers/src/controllers/StandardController.cpp b/libraries/controllers/src/controllers/StandardController.cpp index cc90ee7b49..d8c98eb63b 100644 --- a/libraries/controllers/src/controllers/StandardController.cpp +++ b/libraries/controllers/src/controllers/StandardController.cpp @@ -104,6 +104,9 @@ Input::NamedVector StandardController::getAvailableInputs() const { makePair(RIGHT_HAND, "RightHand"), makePair(LEFT_FOOT, "LeftFoot"), makePair(RIGHT_FOOT, "RightFoot"), + makePair(HIPS, "Hips"), + makePair(SPINE2, "Spine2"), + makePair(HEAD, "Head"), // Aliases, PlayStation style names makePair(LB, "L1"), From 1b9e60944762b74981a621b5abb8573771a21aaa Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 25 Apr 2017 17:23:15 -0700 Subject: [PATCH 12/47] viveMotionCapture.js: updated to route pucks through input system Removed use of animationStateHandler() to set IK targets. --- scripts/developer/tests/viveMotionCapture.js | 119 +++++++++---------- 1 file changed, 58 insertions(+), 61 deletions(-) diff --git a/scripts/developer/tests/viveMotionCapture.js b/scripts/developer/tests/viveMotionCapture.js index 5496b475be..4aa5cbf86f 100644 --- a/scripts/developer/tests/viveMotionCapture.js +++ b/scripts/developer/tests/viveMotionCapture.js @@ -11,18 +11,20 @@ var TRACKED_OBJECT_POSES = [ var triggerPressHandled = false; var rightTriggerPressed = false; var leftTriggerPressed = false; +var calibrationCount = 0; -var MAPPING_NAME = "com.highfidelity.viveMotionCapture"; - -var mapping = Controller.newMapping(MAPPING_NAME); -mapping.from([Controller.Standard.RTClick]).peek().to(function (value) { +var TRIGGER_MAPPING_NAME = "com.highfidelity.viveMotionCapture.triggers"; +var triggerMapping = Controller.newMapping(TRIGGER_MAPPING_NAME); +triggerMapping.from([Controller.Standard.RTClick]).peek().to(function (value) { rightTriggerPressed = (value !== 0) ? true : false; }); -mapping.from([Controller.Standard.LTClick]).peek().to(function (value) { +triggerMapping.from([Controller.Standard.LTClick]).peek().to(function (value) { leftTriggerPressed = (value !== 0) ? true : false; }); +Controller.enableMapping(TRIGGER_MAPPING_NAME); -Controller.enableMapping(MAPPING_NAME); +var CONTROLLER_MAPPING_NAME = "com.highfidelity.viveMotionCapture.controller"; +var controllerMapping; var leftFoot; var rightFoot; @@ -92,7 +94,8 @@ function calibrate() { if (pose.valid) { poses.push({ channel: channel, - pose: pose + pose: pose, + lastestPose: pose }); } }); @@ -177,85 +180,76 @@ var ikTypes = { var handlerId; -function computeIKTargetXform(jointInfo) { - var pose = Controller.getPoseValue(jointInfo.channel); +function convertJointInfoToPose(jointInfo) { + var latestPose = jointInfo.latestPose; var offsetXform = jointInfo.offsetXform; - return Xform.mul(Y_180_XFORM, Xform.mul(new Xform(pose.rotation, pose.translation), offsetXform)); + var xform = Xform.mul(new Xform(latestPose.rotation, latestPose.translation), offsetXform); + return { + valid: true, + translation: xform.pos, + rotation: xform.rot, + velocity: Vec3.sum(latestPose.velocity, Vec3.cross(latestPose.angularVelocity, Vec3.subtract(xform.pos, latestPose.translation))), + angularVelocity: latestPose.angularVelocity + }; } function update(dt) { if (rightTriggerPressed && leftTriggerPressed) { if (!triggerPressHandled) { triggerPressHandled = true; - if (handlerId) { - print("AJT: UN-CALIBRATE!"); + if (controllerMapping) { // go back to normal, vive pucks will be ignored. + print("AJT: UN-CALIBRATE!"); + leftFoot = undefined; rightFoot = undefined; hips = undefined; spine2 = undefined; - if (handlerId) { - print("AJT: un-hooking animation state handler"); - MyAvatar.removeAnimationStateHandler(handlerId); - handlerId = undefined; - } + + Controller.disableMapping(CONTROLLER_MAPPING_NAME + calibrationCount); + controllerMapping = undefined; + } else { print("AJT: CALIBRATE!"); calibrate(); + calibrationCount++; - var animVars = []; + controllerMapping = Controller.newMapping(CONTROLLER_MAPPING_NAME + calibrationCount); if (leftFoot) { - animVars.push("leftFootType"); - animVars.push("leftFootPosition"); - animVars.push("leftFootRotation"); + controllerMapping.from(leftFoot.channel).to(function (pose) { + leftFoot.latestPose = pose; + }); + controllerMapping.from(function () { + return convertJointInfoToPose(leftFoot); + }).to(Controller.Standard.LeftFoot); } if (rightFoot) { - animVars.push("rightFootType"); - animVars.push("rightFootPosition"); - animVars.push("rightFootRotation"); + controllerMapping.from(rightFoot.channel).to(function (pose) { + rightFoot.latestPose = pose; + }); + controllerMapping.from(function () { + return convertJointInfoToPose(rightFoot); + }).to(Controller.Standard.RightFoot); } if (hips) { - animVars.push("hipsType"); - animVars.push("hipsPosition"); - animVars.push("hipsRotation"); + controllerMapping.from(hips.channel).to(function (pose) { + hips.latestPose = pose; + }); + controllerMapping.from(function () { + return convertJointInfoToPose(hips); + }).to(Controller.Standard.Hips); } if (spine2) { - animVars.push("spine2Type"); - animVars.push("spine2Position"); - animVars.push("spine2Rotation"); + controllerMapping.from(spine2.channel).to(function (pose) { + spine2.latestPose = pose; + }); + controllerMapping.from(function () { + return convertJointInfoToPose(spine2); + }).to(Controller.Standard.Spine2); } - - // hook up new anim state handler that maps vive pucks to ik system. - handlerId = MyAvatar.addAnimationStateHandler(function (props) { - var result = {}, xform; - if (rightFoot) { - xform = computeIKTargetXform(rightFoot); - result.rightFootType = ikTypes.RotationAndPosition; - result.rightFootPosition = xform.pos; - result.rightFootRotation = xform.rot; - } - if (leftFoot) { - xform = computeIKTargetXform(leftFoot); - result.leftFootType = ikTypes.RotationAndPosition; - result.leftFootPosition = xform.pos; - result.leftFootRotation = xform.rot; - } - if (hips) { - xform = computeIKTargetXform(hips); - result.hipsType = ikTypes.RotationAndPosition; - result.hipsPosition = xform.pos; - result.hipsRotation = xform.rot; - } - if (spine2) { - xform = computeIKTargetXform(spine2); - result.spine2Type = ikTypes.RotationAndPosition; - result.spine2Position = xform.pos; - result.spine2Rotation = xform.rot; - } - return result; - }, animVars); + Controller.enableMapping(CONTROLLER_MAPPING_NAME + calibrationCount); } } } else { @@ -301,7 +295,10 @@ function update(dt) { Script.update.connect(update); Script.scriptEnding.connect(function () { - Controller.disableMapping(MAPPING_NAME); + Controller.disableMapping(TRIGGER_MAPPING_NAME); + if (controllerMapping) { + Controller.disableMapping(CONTROLLER_MAPPING_NAME + calibrationCount); + } Script.update.disconnect(update); }); From ef43ff4b99565a9fb4d485b5ece1d47536a3331f Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 26 Apr 2017 11:55:24 -0700 Subject: [PATCH 13/47] Add default pose matrices to InputCalibrationData structure --- interface/src/Application.cpp | 8 +- interface/src/avatar/MyAvatar.cpp | 100 ++++++++++++++++-- interface/src/avatar/MyAvatar.h | 8 ++ libraries/controllers/src/controllers/Input.h | 12 ++- 4 files changed, 116 insertions(+), 12 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 5b014f7009..a9f882f73f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4347,7 +4347,13 @@ void Application::update(float deltaTime) { controller::InputCalibrationData calibrationData = { myAvatar->getSensorToWorldMatrix(), createMatFromQuatAndPos(myAvatar->getOrientation(), myAvatar->getPosition()), - myAvatar->getHMDSensorMatrix() + myAvatar->getHMDSensorMatrix(), + myAvatar->getCenterEyeCalibrationMat(), + myAvatar->getHeadCalibrationMat(), + myAvatar->getSpine2CalibrationMat(), + myAvatar->getHipsCalibrationMat(), + myAvatar->getLeftFootCalibrationMat(), + myAvatar->getRightFootCalibrationMat() }; InputPluginPointer keyboardMousePlugin; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 659077188f..9acbf14242 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -83,6 +83,22 @@ const float MyAvatar::ZOOM_MIN = 0.5f; const float MyAvatar::ZOOM_MAX = 25.0f; const float MyAvatar::ZOOM_DEFAULT = 1.5f; +// 2 meter tall dude (in avatar coordinates) +static const glm::vec3 DEFAULT_AVATAR_MIDDLE_EYE_POS { 0.0f, 0.9f, 0.0f }; +static const glm::quat DEFAULT_AVATAR_MIDDLE_EYE_ROT { Quaternions::Y_180 }; +static const glm::vec3 DEFAULT_AVATAR_HEAD_POS { 0.0f, 0.8f, 0.0f }; +static const glm::quat DEFAULT_AVATAR_HEAD_ROT { Quaternions::Y_180 }; +static const glm::vec3 DEFAULT_AVATAR_NECK_POS { 0.0f, 0.7f, 0.0f }; +static const glm::quat DEFAULT_AVATAR_NECK_ROT { Quaternions::Y_180 }; +static const glm::vec3 DEFAULT_AVATAR_SPINE2_POS { 0.0f, 0.5f, 0.0f }; +static const glm::quat DEFAULT_AVATAR_SPINE2_ROT { Quaternions::Y_180}; +static const glm::vec3 DEFAULT_AVATAR_HIPS_POS { 0.0f, 0.05f, 0.0f }; +static const glm::quat DEFAULT_AVATAR_HIPS_ROT { Quaternions::Y_180 }; +static const glm::vec3 DEFAULT_AVATAR_LEFTFOOT_POS { -0.1f, -0.9f, 0.0f }; // AJT: TODO: WRONG FIX ME +static const glm::quat DEFAULT_AVATAR_LEFTFOOT_ROT { Quaternions::IDENTITY }; // AJT: TODO: WRONG FIX ME +static const glm::vec3 DEFAULT_AVATAR_RIGHTFOOT_POS { 0.1f, -0.9f, 0.0f }; // AJT: TODO: WRONG FIX ME +static const glm::quat DEFAULT_AVATAR_RIGHTFOOT_ROT { Quaternions::IDENTITY }; // AJT: TODO: WRONG FIX ME + MyAvatar::MyAvatar(RigPointer rig) : Avatar(rig), _wasPushing(false), @@ -2283,22 +2299,17 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const { const glm::quat hmdOrientation = getHMDSensorOrientation(); const glm::quat hmdOrientationYawOnly = cancelOutRollAndPitch(hmdOrientation); - // 2 meter tall dude (in rig coordinates) - const glm::vec3 DEFAULT_RIG_MIDDLE_EYE_POS(0.0f, 0.9f, 0.0f); - const glm::vec3 DEFAULT_RIG_NECK_POS(0.0f, 0.70f, 0.0f); - const glm::vec3 DEFAULT_RIG_HIPS_POS(0.0f, 0.05f, 0.0f); - int rightEyeIndex = _rig->indexOfJoint("RightEye"); int leftEyeIndex = _rig->indexOfJoint("LeftEye"); int neckIndex = _rig->indexOfJoint("Neck"); int hipsIndex = _rig->indexOfJoint("Hips"); - glm::vec3 rigMiddleEyePos = DEFAULT_RIG_MIDDLE_EYE_POS; + glm::vec3 rigMiddleEyePos = DEFAULT_AVATAR_MIDDLE_EYE_POS; if (leftEyeIndex >= 0 && rightEyeIndex >= 0) { rigMiddleEyePos = (_rig->getAbsoluteDefaultPose(leftEyeIndex).trans() + _rig->getAbsoluteDefaultPose(rightEyeIndex).trans()) / 2.0f; } - glm::vec3 rigNeckPos = neckIndex != -1 ? _rig->getAbsoluteDefaultPose(neckIndex).trans() : DEFAULT_RIG_NECK_POS; - glm::vec3 rigHipsPos = hipsIndex != -1 ? _rig->getAbsoluteDefaultPose(hipsIndex).trans() : DEFAULT_RIG_HIPS_POS; + glm::vec3 rigNeckPos = neckIndex != -1 ? _rig->getAbsoluteDefaultPose(neckIndex).trans() : DEFAULT_AVATAR_NECK_POS; + glm::vec3 rigHipsPos = hipsIndex != -1 ? _rig->getAbsoluteDefaultPose(hipsIndex).trans() : DEFAULT_AVATAR_HIPS_POS; glm::vec3 localEyes = (rigMiddleEyePos - rigHipsPos); glm::vec3 localNeck = (rigNeckPos - rigHipsPos); @@ -2662,6 +2673,79 @@ glm::vec3 MyAvatar::getAbsoluteJointTranslationInObjectFrame(int index) const { } } +glm::mat4 MyAvatar::getCenterEyeCalibrationMat() const { + // TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed. + int rightEyeIndex = _rig->indexOfJoint("RightEye"); + int leftEyeIndex = _rig->indexOfJoint("LeftEye"); + if (rightEyeIndex >= 0 && leftEyeIndex >= 0) { + auto centerEyePos = (getAbsoluteDefaultJointTranslationInObjectFrame(rightEyeIndex) + getAbsoluteDefaultJointTranslationInObjectFrame(leftEyeIndex)) * 0.5f; + auto centerEyeRot = Quaternions::Y_180; + return createMatFromQuatAndPos(centerEyeRot, centerEyePos); + } else { + return createMatFromQuatAndPos(DEFAULT_AVATAR_MIDDLE_EYE_POS, DEFAULT_AVATAR_MIDDLE_EYE_POS); + } +} + +glm::mat4 MyAvatar::getHeadCalibrationMat() const { + // TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed. + int headIndex = _rig->indexOfJoint("Head"); + if (headIndex >= 0) { + auto headPos = getAbsoluteDefaultJointTranslationInObjectFrame(headIndex); + auto headRot = getAbsoluteDefaultJointRotationInObjectFrame(headIndex); + return createMatFromQuatAndPos(headRot, headPos); + } else { + return createMatFromQuatAndPos(DEFAULT_AVATAR_HEAD_POS, DEFAULT_AVATAR_HEAD_POS); + } +} + +glm::mat4 MyAvatar::getSpine2CalibrationMat() const { + // TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed. + int spine2Index = _rig->indexOfJoint("Spine2"); + if (spine2Index >= 0) { + auto spine2Pos = getAbsoluteDefaultJointTranslationInObjectFrame(spine2Index); + auto spine2Rot = getAbsoluteDefaultJointRotationInObjectFrame(spine2Index); + return createMatFromQuatAndPos(spine2Rot, spine2Pos); + } else { + return createMatFromQuatAndPos(DEFAULT_AVATAR_SPINE2_POS, DEFAULT_AVATAR_SPINE2_POS); + } +} + +glm::mat4 MyAvatar::getHipsCalibrationMat() const { + // TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed. + int hipsIndex = _rig->indexOfJoint("Hips"); + if (hipsIndex >= 0) { + auto hipsPos = getAbsoluteDefaultJointTranslationInObjectFrame(hipsIndex); + auto hipsRot = getAbsoluteDefaultJointRotationInObjectFrame(hipsIndex); + return createMatFromQuatAndPos(hipsRot, hipsPos); + } else { + return createMatFromQuatAndPos(DEFAULT_AVATAR_HIPS_POS, DEFAULT_AVATAR_HIPS_POS); + } +} + +glm::mat4 MyAvatar::getLeftFootCalibrationMat() const { + // TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed. + int leftFootIndex = _rig->indexOfJoint("LeftFoot"); + if (leftFootIndex >= 0) { + auto leftFootPos = getAbsoluteDefaultJointTranslationInObjectFrame(leftFootIndex); + auto leftFootRot = getAbsoluteDefaultJointRotationInObjectFrame(leftFootIndex); + return createMatFromQuatAndPos(leftFootRot, leftFootPos); + } else { + return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTFOOT_POS, DEFAULT_AVATAR_LEFTFOOT_POS); + } +} + +glm::mat4 MyAvatar::getRightFootCalibrationMat() const { + // TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed. + int rightFootIndex = _rig->indexOfJoint("RightFoot"); + if (rightFootIndex >= 0) { + auto rightFootPos = getAbsoluteDefaultJointTranslationInObjectFrame(rightFootIndex); + auto rightFootRot = getAbsoluteDefaultJointRotationInObjectFrame(rightFootIndex); + return createMatFromQuatAndPos(rightFootRot, rightFootPos); + } else { + return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTFOOT_POS, DEFAULT_AVATAR_RIGHTFOOT_POS); + } +} + bool MyAvatar::pinJoint(int index, const glm::vec3& position, const glm::quat& orientation) { auto hipsIndex = getJointIndex("Hips"); if (index != hipsIndex) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index cea5962638..01b6496e12 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -473,6 +473,14 @@ public: virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override; virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override; + // all calibration matrices are in absolute avatar space. + glm::mat4 getCenterEyeCalibrationMat() const; + glm::mat4 getHeadCalibrationMat() const; + glm::mat4 getSpine2CalibrationMat() const; + glm::mat4 getHipsCalibrationMat() const; + glm::mat4 getLeftFootCalibrationMat() const; + glm::mat4 getRightFootCalibrationMat() const; + void addHoldAction(AvatarActionHold* holdAction); // thread-safe void removeHoldAction(AvatarActionHold* holdAction); // thread-safe void updateHoldActions(const AnimPose& prePhysicsPose, const AnimPose& postUpdatePose); diff --git a/libraries/controllers/src/controllers/Input.h b/libraries/controllers/src/controllers/Input.h index 9c7f09d526..65c78cd6ea 100644 --- a/libraries/controllers/src/controllers/Input.h +++ b/libraries/controllers/src/controllers/Input.h @@ -16,9 +16,15 @@ namespace controller { struct InputCalibrationData { - glm::mat4 sensorToWorldMat; - glm::mat4 avatarMat; - glm::mat4 hmdSensorMat; + glm::mat4 sensorToWorldMat; // sensor to world + glm::mat4 avatarMat; // avatar to world + glm::mat4 hmdSensorMat; // hmd pos and orientation in sensor space + glm::mat4 defaultCenterEyeMat; // default pose for the center of the eyes in avatar space. + glm::mat4 defaultHeadMat; // default pose for head joint in avatar space + glm::mat4 defaultSpine2; // default pose for spine2 joint in avatar space + glm::mat4 defaultHips; // default pose for hips joint in avatar space + glm::mat4 defaultLeftFoot; // default pose for leftFoot joint in avatar space + glm::mat4 defaultRightFoot; // default pose for leftFoot joint in avatar space }; enum class ChannelType { From 764f6c69eaa4dbed7a06f0104b93864bb28c9367 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 27 Apr 2017 17:39:25 -0700 Subject: [PATCH 14/47] Head input action will override the HMD for IK. --- interface/src/avatar/MyAvatar.cpp | 34 +++++----- interface/src/avatar/MyAvatar.h | 2 +- interface/src/avatar/SkeletonModel.cpp | 37 +++++----- libraries/animation/src/Rig.cpp | 71 ++++---------------- libraries/animation/src/Rig.h | 13 ++-- scripts/developer/tests/viveMotionCapture.js | 44 ++++++++++++ 6 files changed, 97 insertions(+), 104 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 9acbf14242..70d650e7a5 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -83,21 +83,21 @@ const float MyAvatar::ZOOM_MIN = 0.5f; const float MyAvatar::ZOOM_MAX = 25.0f; const float MyAvatar::ZOOM_DEFAULT = 1.5f; -// 2 meter tall dude (in avatar coordinates) -static const glm::vec3 DEFAULT_AVATAR_MIDDLE_EYE_POS { 0.0f, 0.9f, 0.0f }; +// default values, used when avatar is missing joints... (avatar space) static const glm::quat DEFAULT_AVATAR_MIDDLE_EYE_ROT { Quaternions::Y_180 }; -static const glm::vec3 DEFAULT_AVATAR_HEAD_POS { 0.0f, 0.8f, 0.0f }; +static const glm::vec3 DEFAULT_AVATAR_MIDDLE_EYE_POS { 0.0f, 0.6f, 0.0f }; static const glm::quat DEFAULT_AVATAR_HEAD_ROT { Quaternions::Y_180 }; -static const glm::vec3 DEFAULT_AVATAR_NECK_POS { 0.0f, 0.7f, 0.0f }; +static const glm::vec3 DEFAULT_AVATAR_HEAD_POS { 0.0f, 0.53f, 0.0f }; +static const glm::vec3 DEFAULT_AVATAR_NECK_POS { 0.0f, 0.445f, 0.025f }; static const glm::quat DEFAULT_AVATAR_NECK_ROT { Quaternions::Y_180 }; -static const glm::vec3 DEFAULT_AVATAR_SPINE2_POS { 0.0f, 0.5f, 0.0f }; +static const glm::vec3 DEFAULT_AVATAR_SPINE2_POS { 0.0f, 0.32f, 0.02f }; static const glm::quat DEFAULT_AVATAR_SPINE2_ROT { Quaternions::Y_180}; -static const glm::vec3 DEFAULT_AVATAR_HIPS_POS { 0.0f, 0.05f, 0.0f }; +static const glm::vec3 DEFAULT_AVATAR_HIPS_POS { 0.0f, 0.0f, 0.0f }; static const glm::quat DEFAULT_AVATAR_HIPS_ROT { Quaternions::Y_180 }; -static const glm::vec3 DEFAULT_AVATAR_LEFTFOOT_POS { -0.1f, -0.9f, 0.0f }; // AJT: TODO: WRONG FIX ME -static const glm::quat DEFAULT_AVATAR_LEFTFOOT_ROT { Quaternions::IDENTITY }; // AJT: TODO: WRONG FIX ME -static const glm::vec3 DEFAULT_AVATAR_RIGHTFOOT_POS { 0.1f, -0.9f, 0.0f }; // AJT: TODO: WRONG FIX ME -static const glm::quat DEFAULT_AVATAR_RIGHTFOOT_ROT { Quaternions::IDENTITY }; // AJT: TODO: WRONG FIX ME +static const glm::vec3 DEFAULT_AVATAR_LEFTFOOT_POS { -0.08f, -0.96f, 0.029f}; +static const glm::quat DEFAULT_AVATAR_LEFTFOOT_ROT { -0.40167322754859924f, 0.9154590368270874f, -0.005437685176730156f, -0.023744143545627594f }; +static const glm::vec3 DEFAULT_AVATAR_RIGHTFOOT_POS { 0.08f, -0.96f, 0.029f }; +static const glm::quat DEFAULT_AVATAR_RIGHTFOOT_ROT { -0.4016716778278351f, 0.9154615998268127f, 0.0053307069465518f, 0.023696165531873703f }; MyAvatar::MyAvatar(RigPointer rig) : Avatar(rig), @@ -1433,14 +1433,12 @@ controller::Pose MyAvatar::getSpine2ControllerPoseInAvatarFrame() const { return getSpine2ControllerPoseInWorldFrame().transform(invAvatarMatrix); } -void MyAvatar::setHeadControllerPoseInSensorFrame(const controller::Pose& headPose) { - bool inHmd = qApp->isHMDMode(); - Head* head = getHead(); - if (inHmd) { - _headControllerPoseInSensorFrameCache.set(headPose); - head->setDeltaPitch(headPose.rotation.x); - head->setDeltaYaw(headPose.rotation.y); - head->setDeltaRoll(headPose.rotation.z); +void MyAvatar::setHeadControllerPoseInSensorFrame(const controller::Pose& head) { + if (controller::InputDevice::getLowVelocityFilter()) { + auto oldHeadPose = getHeadControllerPoseInSensorFrame(); + _headControllerPoseInSensorFrameCache.set(applyLowVelocityFilter(oldHeadPose, head)); + } else { + _headControllerPoseInSensorFrameCache.set(head); } } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 01b6496e12..dc249e0411 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -460,7 +460,7 @@ public: controller::Pose getHipsControllerPoseInAvatarFrame() const; controller::Pose getSpine2ControllerPoseInAvatarFrame() const; - void setHeadControllerPoseInSensorFrame(const controller::Pose& headPose); + void setHeadControllerPoseInSensorFrame(const controller::Pose& head); controller::Pose getHeadControllerPoseInSensorFrame() const; controller::Pose getHeadControllerPoseInWorldFrame() const; controller::Pose getHeadControllerPoseInAvatarFrame() const; diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index ccee65d3e6..9fad9e09fb 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -107,29 +107,27 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { Rig::HeadParameters headParams; - if (qApp->isHMDMode()) { - // get HMD position from sensor space into world space, and back into rig space - glm::mat4 worldHMDMat = myAvatar->getSensorToWorldMatrix() * myAvatar->getHMDSensorMatrix(); - glm::mat4 rigToWorld = createMatFromQuatAndPos(getRotation(), getTranslation()); - glm::mat4 worldToRig = glm::inverse(rigToWorld); - glm::mat4 rigHMDMat = worldToRig * worldHMDMat; - - headParams.rigHeadPosition = extractTranslation(rigHMDMat); - headParams.rigHeadOrientation = extractRotation(rigHMDMat); - headParams.worldHeadOrientation = extractRotation(worldHMDMat); + // input action is the highest priority source for head orientation. + auto avatarHeadPose = myAvatar->getHeadControllerPoseInAvatarFrame(); + if (avatarHeadPose.isValid()) { + glm::mat4 rigHeadMat = Matrices::Y_180 * createMatFromQuatAndPos(avatarHeadPose.getRotation(), avatarHeadPose.getTranslation()); + headParams.rigHeadPosition = extractTranslation(rigHeadMat); + headParams.rigHeadOrientation = glmExtractRotation(rigHeadMat); headParams.headEnabled = true; } else { - auto avatarHeadPose = myAvatar->getHeadControllerPoseInAvatarFrame(); - if (avatarHeadPose.isValid()) { - glm::mat4 rigHeadMat = Matrices::Y_180 * createMatFromQuatAndPos(avatarHeadPose.getRotation(), avatarHeadPose.getTranslation()); - headParams.rigHeadPosition = extractTranslation(rigHeadMat); - headParams.rigHeadOrientation = glmExtractRotation(rigHeadMat); - headParams.worldHeadOrientation = myAvatar->getHeadControllerPoseInWorldFrame().getTranslation(); + if (qApp->isHMDMode()) { + // get HMD position from sensor space into world space, and back into rig space + glm::mat4 worldHMDMat = myAvatar->getSensorToWorldMatrix() * myAvatar->getHMDSensorMatrix(); + glm::mat4 rigToWorld = createMatFromQuatAndPos(getRotation(), getTranslation()); + glm::mat4 worldToRig = glm::inverse(rigToWorld); + glm::mat4 rigHMDMat = worldToRig * worldHMDMat; + _rig->computeHeadFromHMD(AnimPose(rigHMDMat), headParams.rigHeadPosition, headParams.rigHeadOrientation); headParams.headEnabled = true; } else { // even though full head IK is disabled, the rig still needs the head orientation to rotate the head up and down in desktop mode. - headParams.rigHeadOrientation = Quaternions::Y_180 * head->getFinalOrientationInLocalFrame(); - headParams.worldHeadOrientation = head->getFinalOrientationInWorldFrame(); + // preMult 180 is necessary to convert from avatar to rig coordinates. + // postMult 180 is necessary to convert head from -z forward to z forward. + headParams.rigHeadOrientation = Quaternions::Y_180 * head->getFinalOrientationInLocalFrame() * Quaternions::Y_180; headParams.headEnabled = false; } } @@ -152,7 +150,6 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { headParams.spine2Enabled = false; } - headParams.neckJointIndex = geometry.neckJointIndex; headParams.isTalking = head->getTimeWithoutTalking() <= 1.5f; _rig->updateFromHeadParameters(headParams, deltaTime); @@ -212,7 +209,6 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { Model::updateRig(deltaTime, parentTransform); Rig::EyeParameters eyeParams; - eyeParams.worldHeadOrientation = headParams.worldHeadOrientation; eyeParams.eyeLookAt = lookAt; eyeParams.eyeSaccade = head->getSaccade(); eyeParams.modelRotation = getRotation(); @@ -244,7 +240,6 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { head->setBaseRoll(glm::degrees(-eulers.z)); Rig::EyeParameters eyeParams; - eyeParams.worldHeadOrientation = head->getFinalOrientationInWorldFrame(); eyeParams.eyeLookAt = lookAt; eyeParams.eyeSaccade = glm::vec3(0.0f); eyeParams.modelRotation = getRotation(); diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 53f76d82ff..700761b248 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -46,7 +46,6 @@ static bool isEqual(const glm::quat& p, const glm::quat& q) { const glm::vec3 DEFAULT_RIGHT_EYE_POS(-0.3f, 0.9f, 0.0f); const glm::vec3 DEFAULT_LEFT_EYE_POS(0.3f, 0.9f, 0.0f); const glm::vec3 DEFAULT_HEAD_POS(0.0f, 0.75f, 0.0f); -const glm::vec3 DEFAULT_NECK_POS(0.0f, 0.70f, 0.0f); void Rig::overrideAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame) { @@ -1020,7 +1019,7 @@ glm::quat Rig::getJointDefaultRotationInParentFrame(int jointIndex) { } void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) { - updateNeckJoint(params.neckJointIndex, params); + updateHeadAnimVars(params); _animVars.set("isTalking", params.isTalking); _animVars.set("notIsTalking", !params.isTalking); @@ -1043,74 +1042,40 @@ void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) { } void Rig::updateFromEyeParameters(const EyeParameters& params) { - updateEyeJoint(params.leftEyeJointIndex, params.modelTranslation, params.modelRotation, - params.worldHeadOrientation, params.eyeLookAt, params.eyeSaccade); - updateEyeJoint(params.rightEyeJointIndex, params.modelTranslation, params.modelRotation, - params.worldHeadOrientation, params.eyeLookAt, params.eyeSaccade); + updateEyeJoint(params.leftEyeJointIndex, params.modelTranslation, params.modelRotation, params.eyeLookAt, params.eyeSaccade); + updateEyeJoint(params.rightEyeJointIndex, params.modelTranslation, params.modelRotation, params.eyeLookAt, params.eyeSaccade); } -void Rig::computeHeadNeckAnimVars(const AnimPose& hmdPose, glm::vec3& headPositionOut, glm::quat& headOrientationOut, - glm::vec3& neckPositionOut, glm::quat& neckOrientationOut) const { +void Rig::computeHeadFromHMD(const AnimPose& hmdPose, glm::vec3& headPositionOut, glm::quat& headOrientationOut) const { // the input hmd values are in avatar/rig space const glm::vec3& hmdPosition = hmdPose.trans(); - const glm::quat& hmdOrientation = hmdPose.rot(); + + // the HMD looks down the negative z axis, but the head bone looks down the z axis, so apply a 180 degree rotation. + const glm::quat& hmdOrientation = hmdPose.rot() * Quaternions::Y_180; // TODO: cache jointIndices int rightEyeIndex = indexOfJoint("RightEye"); int leftEyeIndex = indexOfJoint("LeftEye"); int headIndex = indexOfJoint("Head"); - int neckIndex = indexOfJoint("Neck"); glm::vec3 absRightEyePos = rightEyeIndex != -1 ? getAbsoluteDefaultPose(rightEyeIndex).trans() : DEFAULT_RIGHT_EYE_POS; glm::vec3 absLeftEyePos = leftEyeIndex != -1 ? getAbsoluteDefaultPose(leftEyeIndex).trans() : DEFAULT_LEFT_EYE_POS; glm::vec3 absHeadPos = headIndex != -1 ? getAbsoluteDefaultPose(headIndex).trans() : DEFAULT_HEAD_POS; - glm::vec3 absNeckPos = neckIndex != -1 ? getAbsoluteDefaultPose(neckIndex).trans() : DEFAULT_NECK_POS; glm::vec3 absCenterEyePos = (absRightEyePos + absLeftEyePos) / 2.0f; glm::vec3 eyeOffset = absCenterEyePos - absHeadPos; - glm::vec3 headOffset = absHeadPos - absNeckPos; - // apply simplistic head/neck model - - // head headPositionOut = hmdPosition - hmdOrientation * eyeOffset; + headOrientationOut = hmdOrientation; - - // neck - neckPositionOut = hmdPosition - hmdOrientation * (headOffset + eyeOffset); - - // slerp between default orientation and hmdOrientation - neckOrientationOut = safeMix(hmdOrientation, _animSkeleton->getRelativeDefaultPose(neckIndex).rot(), 0.5f); } -void Rig::updateNeckJoint(int index, const HeadParameters& params) { - if (_animSkeleton && index >= 0 && index < _animSkeleton->getNumJoints()) { - glm::quat yFlip180 = glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f)); +void Rig::updateHeadAnimVars(const HeadParameters& params) { + if (_animSkeleton) { if (params.headEnabled) { - glm::vec3 headPos, neckPos; - glm::quat headRot, neckRot; - - AnimPose hmdPose(glm::vec3(1.0f), params.rigHeadOrientation * yFlip180, params.rigHeadPosition); - computeHeadNeckAnimVars(hmdPose, headPos, headRot, neckPos, neckRot); - - // debug rendering -#ifdef DEBUG_RENDERING - const glm::vec4 red(1.0f, 0.0f, 0.0f, 1.0f); - const glm::vec4 green(0.0f, 1.0f, 0.0f, 1.0f); - - // transform from bone into avatar space - AnimPose headPose(glm::vec3(1), headRot, headPos); - DebugDraw::getInstance().addMyAvatarMarker("headTarget", headPose.rot, headPose.trans, red); - - // transform from bone into avatar space - AnimPose neckPose(glm::vec3(1), neckRot, neckPos); - DebugDraw::getInstance().addMyAvatarMarker("neckTarget", neckPose.rot, neckPose.trans, green); -#endif - - _animVars.set("headPosition", headPos); - _animVars.set("headRotation", headRot); - + _animVars.set("headPosition", params.rigHeadPosition); + _animVars.set("headRotation", params.rigHeadOrientation); if (params.hipsEnabled) { // Since there is an explicit hips ik target, switch the head to use the more generic RotationAndPosition IK chain type. // this will allow the spine to bend more, ensuring that it can reach the head target position. @@ -1120,23 +1085,15 @@ void Rig::updateNeckJoint(int index, const HeadParameters& params) { // but because the IK _hipsOffset is enabled, the hips will naturally follow underneath the head. _animVars.set("headType", (int)IKTarget::Type::HmdHead); } - _animVars.set("neckPosition", neckPos); - _animVars.set("neckRotation", neckRot); - _animVars.set("neckType", (int)IKTarget::Type::Unknown); // 'Unknown' disables the target - } else { _animVars.unset("headPosition"); - _animVars.set("headRotation", params.rigHeadOrientation * yFlip180); - _animVars.set("headAndNeckType", (int)IKTarget::Type::RotationOnly); + _animVars.set("headRotation", params.rigHeadOrientation); _animVars.set("headType", (int)IKTarget::Type::RotationOnly); - _animVars.unset("neckPosition"); - _animVars.unset("neckRotation"); - _animVars.set("neckType", (int)IKTarget::Type::RotationOnly); } } } -void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::quat& worldHeadOrientation, const glm::vec3& lookAtSpot, const glm::vec3& saccade) { +void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::vec3& lookAtSpot, const glm::vec3& saccade) { // TODO: does not properly handle avatar scale. diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index b66ca95042..2d024628f5 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -44,18 +44,15 @@ public: struct HeadParameters { glm::mat4 hipsMatrix = glm::mat4(); // rig space glm::mat4 spine2Matrix = glm::mat4(); // rig space - glm::quat worldHeadOrientation = glm::quat(); // world space (-z forward) glm::quat rigHeadOrientation = glm::quat(); // rig space (-z forward) glm::vec3 rigHeadPosition = glm::vec3(); // rig space bool hipsEnabled = false; bool headEnabled = false; bool spine2Enabled = false; - int neckJointIndex = -1; bool isTalking = false; }; struct EyeParameters { - glm::quat worldHeadOrientation = glm::quat(); glm::vec3 eyeLookAt = glm::vec3(); // world space glm::vec3 eyeSaccade = glm::vec3(); // world space glm::vec3 modelTranslation = glm::vec3(); @@ -232,6 +229,9 @@ public: void setEnableDebugDrawIKTargets(bool enableDebugDrawIKTargets) { _enableDebugDrawIKTargets = enableDebugDrawIKTargets; } + // input assumed to be in rig space + void computeHeadFromHMD(const AnimPose& hmdPose, glm::vec3& headPositionOut, glm::quat& headOrientationOut) const; + signals: void onLoadComplete(); @@ -241,10 +241,9 @@ protected: void applyOverridePoses(); void buildAbsoluteRigPoses(const AnimPoseVec& relativePoses, AnimPoseVec& absolutePosesOut); - void updateNeckJoint(int index, const HeadParameters& params); - void computeHeadNeckAnimVars(const AnimPose& hmdPose, glm::vec3& headPositionOut, glm::quat& headOrientationOut, - glm::vec3& neckPositionOut, glm::quat& neckOrientationOut) const; - void updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::quat& worldHeadOrientation, const glm::vec3& lookAt, const glm::vec3& saccade); + void updateHeadAnimVars(const HeadParameters& params); + + void updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::vec3& lookAt, const glm::vec3& saccade); void calcAnimAlpha(float speed, const std::vector& referenceSpeeds, float* alphaOut) const; AnimPose _modelOffset; // model to rig space diff --git a/scripts/developer/tests/viveMotionCapture.js b/scripts/developer/tests/viveMotionCapture.js index 4aa5cbf86f..e7fb8566dc 100644 --- a/scripts/developer/tests/viveMotionCapture.js +++ b/scripts/developer/tests/viveMotionCapture.js @@ -26,6 +26,7 @@ Controller.enableMapping(TRIGGER_MAPPING_NAME); var CONTROLLER_MAPPING_NAME = "com.highfidelity.viveMotionCapture.controller"; var controllerMapping; +var head; var leftFoot; var rightFoot; var hips; @@ -77,8 +78,29 @@ function computeDefaultToReferenceXform() { } } +function computeHeadOffsetXform() { + var leftEyeIndex = MyAvatar.getJointIndex("LeftEye"); + var rightEyeIndex = MyAvatar.getJointIndex("RightEye"); + var headIndex = MyAvatar.getJointIndex("Head"); + if (leftEyeIndex > 0 && rightEyeIndex > 0 && headIndex > 0) { + var defaultHeadXform = new Xform(MyAvatar.getAbsoluteDefaultJointRotationInObjectFrame(headIndex), + MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(headIndex)); + var defaultLeftEyeXform = new Xform(MyAvatar.getAbsoluteDefaultJointRotationInObjectFrame(leftEyeIndex), + MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(leftEyeIndex)); + var defaultRightEyeXform = new Xform(MyAvatar.getAbsoluteDefaultJointRotationInObjectFrame(rightEyeIndex), + MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(rightEyeIndex)); + var defaultCenterEyePos = Vec3.multiply(0.5, Vec3.sum(defaultLeftEyeXform.pos, defaultRightEyeXform.pos)); + var defaultCenterEyeXform = new Xform(defaultLeftEyeXform.rot, defaultCenterEyePos); + + return Xform.mul(defaultCenterEyeXform.inv(), defaultHeadXform); + } else { + return undefined; + } +} + function calibrate() { + head = undefined; leftFoot = undefined; rightFoot = undefined; hips = undefined; @@ -86,6 +108,13 @@ function calibrate() { var defaultToReferenceXform = computeDefaultToReferenceXform(); + var headOffsetXform = computeHeadOffsetXform(); + print("AJT: computed headOffsetXform " + (headOffsetXform ? JSON.stringify(headOffsetXform) : "undefined")); + + if (headOffsetXform) { + head = { offsetXform: headOffsetXform }; + } + var poses = []; if (Controller.Hardware.Vive) { TRACKED_OBJECT_POSES.forEach(function (key) { @@ -202,6 +231,7 @@ function update(dt) { // go back to normal, vive pucks will be ignored. print("AJT: UN-CALIBRATE!"); + head = undefined; leftFoot = undefined; rightFoot = undefined; hips = undefined; @@ -217,6 +247,20 @@ function update(dt) { controllerMapping = Controller.newMapping(CONTROLLER_MAPPING_NAME + calibrationCount); + if (head) { + controllerMapping.from(function () { + var worldToAvatarXform = (new Xform(MyAvatar.orientation, MyAvatar.position)).inv(); + head.latestPose = { + valid: true, + translation: worldToAvatarXform.xformPoint(HMD.position), + rotation: Quat.multiply(worldToAvatarXform.rot, Quat.multiply(HMD.orientation, Y_180)), // postMult 180 rot flips head direction + velocity: {x: 0, y: 0, z: 0}, // TODO: currently this is unused anyway... + angularVelocity: {x: 0, y: 0, z: 0} + }; + return convertJointInfoToPose(head); + }).to(Controller.Standard.Head); + } + if (leftFoot) { controllerMapping.from(leftFoot.channel).to(function (pose) { leftFoot.latestPose = pose; From 4b0e7ceef5360827aae25c7a72eb97db70b34359 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 28 Apr 2017 10:24:04 -0700 Subject: [PATCH 15/47] show hand lasers when edit is enabled, but not if there are not hand-controllers --- scripts/system/controllers/handControllerGrab.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 6a7ed55417..ec70b0b1c8 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -1801,15 +1801,15 @@ function MyController(hand) { } this.processStylus(); - - if (isInEditMode() && !this.isNearStylusTarget) { + + if (isInEditMode() && !this.isNearStylusTarget && HMD.isHandControllerAvailable()) { // Always showing lasers while in edit mode and hands/stylus is not active. var rayPickInfo = this.calcRayPickInfo(this.hand); this.intersectionDistance = (rayPickInfo.entityID || rayPickInfo.overlayID) ? rayPickInfo.distance : 0; this.searchIndicatorOn(rayPickInfo.searchRay); } else { this.searchIndicatorOff(); - } + } }; this.handleLaserOnHomeButton = function(rayPickInfo) { From f3cc1c2d86d7bcb5e568ac3dca7bedd6544ab906 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 28 Apr 2017 11:30:06 -0700 Subject: [PATCH 16/47] Added hipsContorllerTest.js for QA Testing --- scripts/developer/tests/hipsControllerTest.js | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 scripts/developer/tests/hipsControllerTest.js diff --git a/scripts/developer/tests/hipsControllerTest.js b/scripts/developer/tests/hipsControllerTest.js new file mode 100644 index 0000000000..5c6a4811e5 --- /dev/null +++ b/scripts/developer/tests/hipsControllerTest.js @@ -0,0 +1,105 @@ +// +// hipsControllerTest.js +// +// Created by Anthony Thibault on 4/24/17 +// Copyright 2017 High Fidelity, Inc. +// +// Test procedural manipulation of the Avatar hips via the controller system. +// Pull the left and right triggers on your hand controllers, you hips should begin to gyrate in an amusing mannor. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +/* global Xform */ +Script.include("/~/system/libraries/Xform.js"); + +var triggerPressHandled = false; +var rightTriggerPressed = false; +var leftTriggerPressed = false; + +var MAPPING_NAME = "com.highfidelity.hipsIkTest"; + +var mapping = Controller.newMapping(MAPPING_NAME); +mapping.from([Controller.Standard.RTClick]).peek().to(function (value) { + rightTriggerPressed = (value !== 0) ? true : false; +}); +mapping.from([Controller.Standard.LTClick]).peek().to(function (value) { + leftTriggerPressed = (value !== 0) ? true : false; +}); + +Controller.enableMapping(MAPPING_NAME); + +var CONTROLLER_MAPPING_NAME = "com.highfidelity.hipsIkTest.controller"; +var controllerMapping; + +var ZERO = {x: 0, y: 0, z: 0}; +var X_AXIS = {x: 1, y: 0, z: 0}; +var Y_AXIS = {x: 0, y: 1, z: 0}; +var Y_180 = {x: 0, y: 1, z: 0, w: 0}; +var Y_180_XFORM = new Xform(Y_180, {x: 0, y: 0, z: 0}); + +var hips = undefined; + +function computeCurrentXform(jointIndex) { + var currentXform = new Xform(MyAvatar.getAbsoluteJointRotationInObjectFrame(jointIndex), + MyAvatar.getAbsoluteJointTranslationInObjectFrame(jointIndex)); + return currentXform; +} + +function calibrate() { + hips = computeCurrentXform(MyAvatar.getJointIndex("Hips")); +} + +function circleOffset(radius, theta, normal) { + var pos = {x: radius * Math.cos(theta), y: radius * Math.sin(theta), z: 0}; + var lookAtRot = Quat.lookAt(normal, ZERO, X_AXIS); + return Vec3.multiplyQbyV(lookAtRot, pos); +} + +var calibrationCount = 0; + +function update(dt) { + if (rightTriggerPressed && leftTriggerPressed) { + if (!triggerPressHandled) { + triggerPressHandled = true; + if (controllerMapping) { + hips = undefined; + Controller.disableMapping(CONTROLLER_MAPPING_NAME + calibrationCount); + controllerMapping = undefined; + } else { + calibrate(); + calibrationCount++; + controllerMapping = Controller.newMapping(CONTROLLER_MAPPING_NAME + calibrationCount); + + var n = Y_AXIS; + var t = 0; + if (hips) { + controllerMapping.from(function () { + t += (1 / 60) * 4; + return { + valid: true, + translation: Vec3.sum(hips.pos, circleOffset(0.1, t, n)), + rotation: hips.rot, + velocity: ZERO, + angularVelocity: ZERO + }; + }).to(Controller.Standard.Hips); + } + Controller.enableMapping(CONTROLLER_MAPPING_NAME + calibrationCount); + } + } + } else { + triggerPressHandled = false; + } +} + +Script.update.connect(update); + +Script.scriptEnding.connect(function () { + Controller.disableMapping(MAPPING_NAME); + if (controllerMapping) { + Controller.disableMapping(CONTROLLER_MAPPING_NAME + calibrationCount); + } + Script.update.disconnect(update); +}); + From 58de67ba119268b45280b4a0fad83b91183e2d9b Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 28 Apr 2017 14:12:00 -0700 Subject: [PATCH 17/47] Fix tests/controllers/src/main.cpp clang/gcc warnings --- tests/controllers/src/main.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/controllers/src/main.cpp b/tests/controllers/src/main.cpp index 2c8f361fac..e697bd501f 100644 --- a/tests/controllers/src/main.cpp +++ b/tests/controllers/src/main.cpp @@ -114,6 +114,12 @@ int main(int argc, char** argv) { last = now; InputCalibrationData calibrationData = { + glm::mat4(), + glm::mat4(), + glm::mat4(), + glm::mat4(), + glm::mat4(), + glm::mat4(), glm::mat4(), glm::mat4(), glm::mat4() @@ -130,6 +136,12 @@ int main(int argc, char** argv) { { InputCalibrationData calibrationData = { + glm::mat4(), + glm::mat4(), + glm::mat4(), + glm::mat4(), + glm::mat4(), + glm::mat4(), glm::mat4(), glm::mat4(), glm::mat4() From 59f84a60c548233d4f7757a93fe2ae256816e93b Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Sat, 29 Apr 2017 00:31:09 +0100 Subject: [PATCH 18/47] saving work --- .../qml/controls/TabletWebScreen.qml | 132 ++++++++++++++++++ .../resources/qml/controls/TabletWebView.qml | 12 ++ interface/resources/qml/controls/WebView.qml | 2 +- .../resources/qml/hifi/tablet/TabletRoot.qml | 21 +++ .../qml/hifi/tablet/TabletWebView.qml | 2 +- .../qml/hifi/tablet/WindowWebView.qml | 10 ++ 6 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 interface/resources/qml/controls/TabletWebScreen.qml create mode 100644 interface/resources/qml/hifi/tablet/WindowWebView.qml diff --git a/interface/resources/qml/controls/TabletWebScreen.qml b/interface/resources/qml/controls/TabletWebScreen.qml new file mode 100644 index 0000000000..fec91046d8 --- /dev/null +++ b/interface/resources/qml/controls/TabletWebScreen.qml @@ -0,0 +1,132 @@ +import QtQuick 2.5 +import QtWebEngine 1.1 +import QtWebChannel 1.0 +import "../controls-uit" as HiFiControls +import HFTabletWebEngineProfile 1.0 + +Item { + property alias url: root.url + property alias scriptURL: root.userScriptUrl + property alias eventBridge: eventBridgeWrapper.eventBridge + property alias canGoBack: root.canGoBack; + property var goBack: root.goBack; + property alias urlTag: root.urlTag + property bool keyboardEnabled: true // FIXME - Keyboard HMD only: Default to false + property bool keyboardRaised: false + property bool punctuationMode: false + + // FIXME - Keyboard HMD only: Make Interface either set keyboardRaised property directly in OffscreenQmlSurface + // or provide HMDinfo object to QML in RenderableWebEntityItem and do the following. + /* + onKeyboardRaisedChanged: { + keyboardEnabled = HMDinfo.active; + } + */ + + QtObject { + id: eventBridgeWrapper + WebChannel.id: "eventBridgeWrapper" + property var eventBridge; + } + + property alias viewProfile: root.profile + + WebEngineView { + id: root + objectName: "webEngineView" + x: 0 + y: 0 + width: parent.width + height: keyboardEnabled && keyboardRaised ? parent.height - keyboard.height : parent.height + + profile: HFTabletWebEngineProfile { + id: webviewProfile + storageName: "qmlTabletWebEngine" + } + + property string userScriptUrl: "" + + // creates a global EventBridge object. + WebEngineScript { + id: createGlobalEventBridge + sourceCode: eventBridgeJavaScriptToInject + injectionPoint: WebEngineScript.DocumentCreation + worldId: WebEngineScript.MainWorld + } + + // detects when to raise and lower virtual keyboard + WebEngineScript { + id: raiseAndLowerKeyboard + injectionPoint: WebEngineScript.Deferred + sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js" + worldId: WebEngineScript.MainWorld + } + + // User script. + WebEngineScript { + id: userScript + sourceUrl: root.userScriptUrl + injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished. + worldId: WebEngineScript.MainWorld + } + + property string urlTag: "noDownload=false"; + + userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ] + + property string newUrl: "" + + webChannel.registeredObjects: [eventBridgeWrapper] + + Component.onCompleted: { + // Ensure the JS from the web-engine makes it to our logging + root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { + console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); + }); + + root.profile.httpUserAgent = "Mozilla/5.0 Chrome (HighFidelityInterface)"; + } + + onFeaturePermissionRequested: { + grantFeaturePermission(securityOrigin, feature, true); + } + + onLoadingChanged: { + keyboardRaised = false; + punctuationMode = false; + keyboard.resetShiftMode(false); + + // Required to support clicking on "hifi://" links + if (WebEngineView.LoadStartedStatus == loadRequest.status) { + var url = loadRequest.url.toString(); + url = (url.indexOf("?") >= 0) ? url + urlTag : url + "?" + urlTag; + if (urlHandler.canHandleUrl(url)) { + if (urlHandler.handleUrl(url)) { + root.stop(); + } + } + } + } + + onNewViewRequested:{ + // desktop is not defined for web-entities or tablet + if (typeof desktop !== "undefined") { + desktop.openBrowserWindow(request, profile); + } else { + tabletRoot.openBrowserWindow(request, profile); + } + } + } + + HiFiControls.Keyboard { + id: keyboard + raised: parent.keyboardEnabled && parent.keyboardRaised + numeric: parent.punctuationMode + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + } + +} diff --git a/interface/resources/qml/controls/TabletWebView.qml b/interface/resources/qml/controls/TabletWebView.qml index 41cea625e2..e43441045f 100644 --- a/interface/resources/qml/controls/TabletWebView.qml +++ b/interface/resources/qml/controls/TabletWebView.qml @@ -25,6 +25,9 @@ Item { property bool isDesktop: false property bool removingPage: false property bool loadingPage: false + property alias webView: webview + property alias profile: webview.profile + property bool remove: false property int currentPage: -1 // used as a model for repeater @@ -110,6 +113,10 @@ Item { function closeWebEngine() { + if (remove) { + web.destroy(); + return; + } if (parentStackItem) { parentStackItem.pop(); } else { @@ -263,8 +270,13 @@ Item { } onNewViewRequested: { + console.log("--------------> new window opened <-------------"); request.openIn(webview); } + + onWindowCloseRequested: { + console.log("-------------> requested to cloes window <---------------"); + } } HiFiControls.Keyboard { diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index 52f277520f..04ff731a25 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -113,7 +113,7 @@ Item { if (typeof desktop !== "undefined") { desktop.openBrowserWindow(request, profile); } else { - console.log("onNewViewRequested: desktop not defined"); + tabletRoot.openBrowserWindow(request, profile); } } } diff --git a/interface/resources/qml/hifi/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml index 31e6174563..b19525d21a 100644 --- a/interface/resources/qml/hifi/tablet/TabletRoot.qml +++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml @@ -1,7 +1,9 @@ import QtQuick 2.0 import Hifi 1.0 import QtQuick.Controls 1.4 +import HFTabletWebEngineProfile 1.0 import "../../dialogs" +import "../../controls" Item { id: tabletRoot @@ -11,6 +13,7 @@ Item { property var rootMenu; property var openModal: null; property var openMessage: null; + property var openBrowser: null; property string subMenu: "" signal showDesktop(); property bool shown: true @@ -21,6 +24,7 @@ Item { option = value; } + Component { id: profileCreator; HFTabletWebEngineProfile {} } Component { id: inputDialogBuilder; TabletQueryDialog { } } function inputDialog(properties) { openModal = inputDialogBuilder.createObject(tabletRoot, properties); @@ -101,8 +105,20 @@ Item { } } + function openBrowserWindow(request, profile) { + var component = Qt.createComponent("../../controls/TabletWebView.qml"); + var newWindow = component.createObject(tabletRoot); + newWindow.eventBridge = tabletRoot.eventBridge; + newWindow.remove = true; + newWindow.profile = profile; + request.openIn(newWindow.webView); + tabletRoot.openBrowser = newWindow; + } + function loadWebUrl(url, injectedJavaScriptUrl) { tabletApps.clear(); + var newProfile = profileCreator.createObject(); + loader.item.viewProfile = newProfile; loader.item.url = url; loader.item.scriptURL = injectedJavaScriptUrl; tabletApps.append({"appUrl": "TabletWebView.qml", "isWebUrl": true, "scriptUrl": injectedJavaScriptUrl, "appWebUrl": url}); @@ -180,6 +196,11 @@ Item { openModal.destroy(); openModal = null; } + + if (openBrowser) { + openBrowser.destroy(); + openBrowser = null; + } } } diff --git a/interface/resources/qml/hifi/tablet/TabletWebView.qml b/interface/resources/qml/hifi/tablet/TabletWebView.qml index 0f697d634e..ff6be0480f 100644 --- a/interface/resources/qml/hifi/tablet/TabletWebView.qml +++ b/interface/resources/qml/hifi/tablet/TabletWebView.qml @@ -3,7 +3,7 @@ import QtWebEngine 1.2 import "../../controls" as Controls -Controls.WebView { +Controls.TabletWebScreen { } diff --git a/interface/resources/qml/hifi/tablet/WindowWebView.qml b/interface/resources/qml/hifi/tablet/WindowWebView.qml new file mode 100644 index 0000000000..0f697d634e --- /dev/null +++ b/interface/resources/qml/hifi/tablet/WindowWebView.qml @@ -0,0 +1,10 @@ +import QtQuick 2.0 +import QtWebEngine 1.2 + +import "../../controls" as Controls + +Controls.WebView { + +} + + From 5e1bc0d908d63f81e852891c2efc27b1d6596fd7 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 28 Apr 2017 14:28:03 -0700 Subject: [PATCH 19/47] Remove faceshift --- cmake/externals/faceshift/CMakeLists.txt | 47 --- cmake/macros/TargetFaceshift.cmake | 14 - cmake/modules/FindFaceshift.cmake | 26 -- interface/CMakeLists.txt | 5 - .../resources/icons/tablet-icons/goto-msg.svg | 36 +- interface/src/Application.cpp | 25 +- interface/src/Menu.cpp | 12 +- interface/src/Menu.h | 1 - interface/src/avatar/Head.cpp | 17 +- interface/src/avatar/MyAvatar.cpp | 4 +- interface/src/devices/DeviceTracker.cpp | 3 - interface/src/devices/DeviceTracker.h | 3 - interface/src/devices/EyeTracker.cpp | 9 +- interface/src/devices/EyeTracker.h | 3 - interface/src/devices/FaceTracker.cpp | 40 ++- interface/src/devices/FaceTracker.h | 38 ++- interface/src/devices/Faceshift.cpp | 310 ------------------ interface/src/devices/Faceshift.h | 155 --------- interface/src/devices/Logging.cpp | 11 + interface/src/devices/Logging.h | 16 + interface/src/devices/MotionTracker.cpp | 5 - interface/src/devices/MotionTracker.h | 18 +- interface/src/ui/PreferencesDialog.cpp | 8 - libraries/avatars/src/AvatarData.cpp | 6 +- libraries/avatars/src/AvatarData.h | 4 +- libraries/ui/src/ui/Menu.cpp | 12 + libraries/ui/src/ui/Menu.h | 10 + 27 files changed, 150 insertions(+), 688 deletions(-) delete mode 100644 cmake/externals/faceshift/CMakeLists.txt delete mode 100644 cmake/macros/TargetFaceshift.cmake delete mode 100644 cmake/modules/FindFaceshift.cmake delete mode 100644 interface/src/devices/Faceshift.cpp delete mode 100644 interface/src/devices/Faceshift.h create mode 100644 interface/src/devices/Logging.cpp create mode 100644 interface/src/devices/Logging.h diff --git a/cmake/externals/faceshift/CMakeLists.txt b/cmake/externals/faceshift/CMakeLists.txt deleted file mode 100644 index c4f2055435..0000000000 --- a/cmake/externals/faceshift/CMakeLists.txt +++ /dev/null @@ -1,47 +0,0 @@ -set(EXTERNAL_NAME faceshift) - -include(ExternalProject) -ExternalProject_Add( - ${EXTERNAL_NAME} - URL https://hifi-public.s3.amazonaws.com/dependencies/faceshift.zip - CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DCMAKE_INSTALL_PREFIX:PATH= - BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build - LOG_DOWNLOAD 1 - LOG_CONFIGURE 1 - LOG_BUILD 1 -) - -# URL_MD5 1bdcb8a0b8d5b1ede434cc41efade41d - -# Hide this external target (for ide users) -set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") - -ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) - -string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) -set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE FILEPATH "Path to Faceshift include directory") - -set(LIBRARY_DEBUG_PATH "lib/Debug") -set(LIBRARY_RELEASE_PATH "lib/Release") - -if (WIN32) - set(LIBRARY_PREFIX "") - set(LIBRARY_EXT "lib") - # use selected configuration in release path when building on Windows - set(LIBRARY_RELEASE_PATH "$<$:build/RelWithDebInfo>") - set(LIBRARY_RELEASE_PATH "${LIBRARY_RELEASE_PATH}$<$:build/MinSizeRel>") - set(LIBRARY_RELEASE_PATH "${LIBRARY_RELEASE_PATH}$<$,$>:lib/Release>") -elseif (APPLE) - set(LIBRARY_EXT "a") - set(LIBRARY_PREFIX "lib") - - if (CMAKE_GENERATOR STREQUAL "Unix Makefiles") - set(LIBRARY_DEBUG_PATH "build") - set(LIBRARY_RELEASE_PATH "build") - endif () -endif() - -set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG - ${INSTALL_DIR}/${LIBRARY_DEBUG_PATH}/${LIBRARY_PREFIX}faceshift.${LIBRARY_EXT} CACHE FILEPATH "Faceshift libraries") -set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE -${INSTALL_DIR}/${LIBRARY_RELEASE_PATH}/${LIBRARY_PREFIX}faceshift.${LIBRARY_EXT} CACHE FILEPATH "Faceshift libraries") diff --git a/cmake/macros/TargetFaceshift.cmake b/cmake/macros/TargetFaceshift.cmake deleted file mode 100644 index 99f65d942a..0000000000 --- a/cmake/macros/TargetFaceshift.cmake +++ /dev/null @@ -1,14 +0,0 @@ -# -# Copyright 2015 High Fidelity, Inc. -# Created by Bradley Austin Davis on 2015/10/10 -# -# Distributed under the Apache License, Version 2.0. -# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -# -macro(TARGET_FACESHIFT) - add_dependency_external_projects(faceshift) - find_package(Faceshift REQUIRED) - target_include_directories(${TARGET_NAME} PRIVATE ${FACESHIFT_INCLUDE_DIRS}) - target_link_libraries(${TARGET_NAME} ${FACESHIFT_LIBRARIES}) - add_definitions(-DHAVE_FACESHIFT) -endmacro() \ No newline at end of file diff --git a/cmake/modules/FindFaceshift.cmake b/cmake/modules/FindFaceshift.cmake deleted file mode 100644 index bd77951273..0000000000 --- a/cmake/modules/FindFaceshift.cmake +++ /dev/null @@ -1,26 +0,0 @@ -# -# FindFaceshift.cmake -# -# Try to find the Faceshift networking library -# -# You must provide a FACESHIFT_ROOT_DIR which contains lib and include directories -# -# Once done this will define -# -# FACESHIFT_FOUND - system found Faceshift -# FACESHIFT_INCLUDE_DIRS - the Faceshift include directory -# FACESHIFT_LIBRARIES - Link this to use Faceshift -# -# Created on 8/30/2013 by Andrzej Kapolka -# Copyright 2013 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(SelectLibraryConfigurations) -select_library_configurations(FACESHIFT) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Faceshift DEFAULT_MSG FACESHIFT_INCLUDE_DIRS FACESHIFT_LIBRARIES) -mark_as_advanced(FACESHIFT_INCLUDE_DIRS FACESHIFT_LIBRARIES FACESHIFT_SEARCH_DIRS) \ No newline at end of file diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 4d58d70075..e3d01826b3 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -202,7 +202,6 @@ link_hifi_libraries( # include the binary directory of render-utils for shader includes target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/libraries/render-utils") -#fixme find a way to express faceshift as a plugin target_bullet() target_opengl() @@ -210,10 +209,6 @@ if (NOT ANDROID) target_glew() endif () -if (WIN32 OR APPLE) - target_faceshift() -endif() - # perform standard include and linking for found externals foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) diff --git a/interface/resources/icons/tablet-icons/goto-msg.svg b/interface/resources/icons/tablet-icons/goto-msg.svg index 9b576ab1bf..ef905b6066 100644 --- a/interface/resources/icons/tablet-icons/goto-msg.svg +++ b/interface/resources/icons/tablet-icons/goto-msg.svg @@ -1,18 +1,18 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 39a4b8ee7c..306fa99852 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -137,7 +137,6 @@ #include "CrashHandler.h" #include "devices/DdeFaceTracker.h" #include "devices/EyeTracker.h" -#include "devices/Faceshift.h" #include "devices/Leapmotion.h" #include "DiscoverabilityManager.h" #include "GLCanvas.h" @@ -480,7 +479,6 @@ bool setupEssentials(int& argc, char** argv) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); - DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -1209,10 +1207,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo this->installEventFilter(this); - // initialize our face trackers after loading the menu settings - auto faceshiftTracker = DependencyManager::get(); - faceshiftTracker->init(); - connect(faceshiftTracker.data(), &FaceTracker::muteToggled, this, &Application::faceTrackerMuteToggled); #ifdef HAVE_DDE auto ddeTracker = DependencyManager::get(); ddeTracker->init(); @@ -3624,20 +3618,13 @@ ivec2 Application::getMouse() const { } FaceTracker* Application::getActiveFaceTracker() { - auto faceshift = DependencyManager::get(); auto dde = DependencyManager::get(); - return (dde->isActive() ? static_cast(dde.data()) : - (faceshift->isActive() ? static_cast(faceshift.data()) : nullptr)); + return dde->isActive() ? static_cast(dde.data()) : nullptr; } FaceTracker* Application::getSelectedFaceTracker() { FaceTracker* faceTracker = nullptr; -#ifdef HAVE_FACESHIFT - if (Menu::getInstance()->isOptionChecked(MenuOption::Faceshift)) { - faceTracker = DependencyManager::get().data(); - } -#endif #ifdef HAVE_DDE if (Menu::getInstance()->isOptionChecked(MenuOption::UseCamera)) { faceTracker = DependencyManager::get().data(); @@ -3647,15 +3634,8 @@ FaceTracker* Application::getSelectedFaceTracker() { } void Application::setActiveFaceTracker() const { -#if defined(HAVE_FACESHIFT) || defined(HAVE_DDE) - bool isMuted = Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking); -#endif -#ifdef HAVE_FACESHIFT - auto faceshiftTracker = DependencyManager::get(); - faceshiftTracker->setIsMuted(isMuted); - faceshiftTracker->setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Faceshift) && !isMuted); -#endif #ifdef HAVE_DDE + bool isMuted = Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking); bool isUsingDDE = Menu::getInstance()->isOptionChecked(MenuOption::UseCamera); Menu::getInstance()->getActionForOption(MenuOption::BinaryEyelidControl)->setVisible(isUsingDDE); Menu::getInstance()->getActionForOption(MenuOption::CoupleEyelids)->setVisible(isUsingDDE); @@ -5131,7 +5111,6 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se } void Application::resetSensors(bool andReload) { - DependencyManager::get()->reset(); DependencyManager::get()->reset(); DependencyManager::get()->reset(); getActiveDisplayPlugin()->resetSensors(); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 8754951317..fcd539ca7d 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -34,7 +34,6 @@ #include "avatar/AvatarManager.h" #include "AvatarBookmarks.h" #include "devices/DdeFaceTracker.h" -#include "devices/Faceshift.h" #include "MainWindow.h" #include "render/DrawStatus.h" #include "scripting/MenuScriptingInterface.h" @@ -451,12 +450,6 @@ Menu::Menu() { qApp, SLOT(setActiveFaceTracker())); faceTrackerGroup->addAction(noFaceTracker); -#ifdef HAVE_FACESHIFT - QAction* faceshiftFaceTracker = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::Faceshift, - 0, false, - qApp, SLOT(setActiveFaceTracker())); - faceTrackerGroup->addAction(faceshiftFaceTracker); -#endif #ifdef HAVE_DDE QAction* ddeFaceTracker = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::UseCamera, 0, true, @@ -477,11 +470,10 @@ Menu::Menu() { QAction* ddeCalibrate = addActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::CalibrateCamera, 0, DependencyManager::get().data(), SLOT(calibrate())); ddeCalibrate->setVisible(true); // DDE face tracking is on by default -#endif -#if defined(HAVE_FACESHIFT) || defined(HAVE_DDE) faceTrackingMenu->addSeparator(); addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::MuteFaceTracking, - Qt::CTRL | Qt::SHIFT | Qt::Key_F, true); // DDE face tracking is on by default + [](bool mute) { FaceTracker::setIsMuted(mute); }, + Qt::CTRL | Qt::SHIFT | Qt::Key_F, FaceTracker::isMuted()); addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::AutoMuteAudio, 0, false); #endif diff --git a/interface/src/Menu.h b/interface/src/Menu.h index eeffcac7ca..479a78e7c2 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -105,7 +105,6 @@ namespace MenuOption { const QString ExpandPaintGLTiming = "Expand /paintGL"; const QString ExpandPhysicsSimulationTiming = "Expand /physics"; const QString ExpandUpdateTiming = "Expand /update"; - const QString Faceshift = "Faceshift"; const QString FirstPerson = "First Person"; const QString FivePointCalibration = "5 Point Calibration"; const QString FixGaze = "Fix Gaze (no saccade)"; diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index 282acf6bf5..f2eeba9d60 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -23,7 +23,6 @@ #include "Util.h" #include "devices/DdeFaceTracker.h" #include "devices/EyeTracker.h" -#include "devices/Faceshift.h" #include using namespace std; @@ -209,14 +208,14 @@ void Head::simulate(float deltaTime, bool isMine) { // use data to update fake Faceshift blendshape coefficients calculateMouthShapes(deltaTime); - DependencyManager::get()->updateFakeCoefficients(_leftEyeBlink, - _rightEyeBlink, - _browAudioLift, - _audioJawOpen, - _mouth2, - _mouth3, - _mouth4, - _blendshapeCoefficients); + FaceTracker::updateFakeCoefficients(_leftEyeBlink, + _rightEyeBlink, + _browAudioLift, + _audioJawOpen, + _mouth2, + _mouth3, + _mouth4, + _blendshapeCoefficients); applyEyelidOffset(getOrientation()); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 7bc961c654..b8c7656839 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -41,9 +41,9 @@ #include #include #include +#include #include "Application.h" -#include "devices/Faceshift.h" #include "AvatarManager.h" #include "AvatarActionHold.h" #include "Menu.h" @@ -650,7 +650,7 @@ void MyAvatar::updateFromTrackers(float deltaTime) { } FaceTracker* tracker = qApp->getActiveFaceTracker(); - bool inFacetracker = tracker && !tracker->isMuted(); + bool inFacetracker = tracker && !FaceTracker::isMuted(); if (inHmd) { estimatedPosition = extractTranslation(getHMDSensorMatrix()); diff --git a/interface/src/devices/DeviceTracker.cpp b/interface/src/devices/DeviceTracker.cpp index 2cd4950064..93aeb607bc 100644 --- a/interface/src/devices/DeviceTracker.cpp +++ b/interface/src/devices/DeviceTracker.cpp @@ -1,7 +1,4 @@ // -// DeviceTracker.cpp -// interface/src/devices -// // Created by Sam Cake on 6/20/14. // Copyright 2014 High Fidelity, Inc. // diff --git a/interface/src/devices/DeviceTracker.h b/interface/src/devices/DeviceTracker.h index 543e9bab1a..8a7f509cb3 100644 --- a/interface/src/devices/DeviceTracker.h +++ b/interface/src/devices/DeviceTracker.h @@ -1,7 +1,4 @@ // -// DeviceTracker.h -// interface/src/devices -// // Created by Sam Cake on 6/20/14. // Copyright 2014 High Fidelity, Inc. // diff --git a/interface/src/devices/EyeTracker.cpp b/interface/src/devices/EyeTracker.cpp index 367aa52aae..8733461dbb 100644 --- a/interface/src/devices/EyeTracker.cpp +++ b/interface/src/devices/EyeTracker.cpp @@ -1,7 +1,4 @@ // -// EyeTracker.cpp -// interface/src/devices -// // Created by David Rowe on 27 Jul 2015. // Copyright 2015 High Fidelity, Inc. // @@ -17,8 +14,8 @@ #include -#include "InterfaceLogging.h" -#include "OctreeConstants.h" +#include "Logging.h" +#include #ifdef HAVE_IVIEWHMD char* HIGH_FIDELITY_EYE_TRACKER_CALIBRATION = "HighFidelityEyeTrackerCalibration"; @@ -115,7 +112,7 @@ void EyeTracker::processData(smi_CallbackDataStruct* data) { void EyeTracker::init() { if (_isInitialized) { - qCWarning(interfaceapp) << "Eye Tracker: Already initialized"; + qCWarning(trackers) << "Eye Tracker: Already initialized"; return; } } diff --git a/interface/src/devices/EyeTracker.h b/interface/src/devices/EyeTracker.h index 0e760d9454..9cf35d0f2a 100644 --- a/interface/src/devices/EyeTracker.h +++ b/interface/src/devices/EyeTracker.h @@ -1,7 +1,4 @@ // -// EyeTracker.h -// interface/src/devices -// // Created by David Rowe on 27 Jul 2015. // Copyright 2015 High Fidelity, Inc. // diff --git a/interface/src/devices/FaceTracker.cpp b/interface/src/devices/FaceTracker.cpp index 76a4534952..034787f19a 100644 --- a/interface/src/devices/FaceTracker.cpp +++ b/interface/src/devices/FaceTracker.cpp @@ -1,7 +1,4 @@ // -// FaceTracker.cpp -// interface/src/devices -// // Created by Andrzej Kapolka on 4/9/14. // Copyright 2014 High Fidelity, Inc. // @@ -9,22 +6,21 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include - -#include - #include "FaceTracker.h" -#include "InterfaceLogging.h" -#include "Menu.h" + +#include +#include +#include "Logging.h" +//#include "Menu.h" const int FPS_TIMER_DELAY = 2000; // ms const int FPS_TIMER_DURATION = 2000; // ms const float DEFAULT_EYE_DEFLECTION = 0.25f; Setting::Handle FaceTracker::_eyeDeflection("faceshiftEyeDeflection", DEFAULT_EYE_DEFLECTION); +bool FaceTracker::_isMuted { true }; void FaceTracker::init() { - _isMuted = Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking); _isInitialized = true; // FaceTracker can be used now } @@ -101,7 +97,7 @@ void FaceTracker::countFrame() { } void FaceTracker::finishFPSTimer() { - qCDebug(interfaceapp) << "Face tracker FPS =" << (float)_frameCount / ((float)FPS_TIMER_DURATION / 1000.0f); + qCDebug(trackers) << "Face tracker FPS =" << (float)_frameCount / ((float)FPS_TIMER_DURATION / 1000.0f); _isCalculatingFPS = false; } @@ -113,3 +109,25 @@ void FaceTracker::toggleMute() { void FaceTracker::setEyeDeflection(float eyeDeflection) { _eyeDeflection.set(eyeDeflection); } + +void FaceTracker::updateFakeCoefficients(float leftBlink, float rightBlink, float browUp, + float jawOpen, float mouth2, float mouth3, float mouth4, QVector& coefficients) { + const int MMMM_BLENDSHAPE = 34; + const int FUNNEL_BLENDSHAPE = 40; + const int SMILE_LEFT_BLENDSHAPE = 28; + const int SMILE_RIGHT_BLENDSHAPE = 29; + const int MAX_FAKE_BLENDSHAPE = 40; // Largest modified blendshape from above and below + + coefficients.resize(std::max((int)coefficients.size(), MAX_FAKE_BLENDSHAPE + 1)); + qFill(coefficients.begin(), coefficients.end(), 0.0f); + coefficients[_leftBlinkIndex] = leftBlink; + coefficients[_rightBlinkIndex] = rightBlink; + coefficients[_browUpCenterIndex] = browUp; + coefficients[_browUpLeftIndex] = browUp; + coefficients[_browUpRightIndex] = browUp; + coefficients[_jawOpenIndex] = jawOpen; + coefficients[SMILE_LEFT_BLENDSHAPE] = coefficients[SMILE_RIGHT_BLENDSHAPE] = mouth4; + coefficients[MMMM_BLENDSHAPE] = mouth2; + coefficients[FUNNEL_BLENDSHAPE] = mouth3; +} + diff --git a/interface/src/devices/FaceTracker.h b/interface/src/devices/FaceTracker.h index 7126d19ca8..58d5c5e574 100644 --- a/interface/src/devices/FaceTracker.h +++ b/interface/src/devices/FaceTracker.h @@ -1,7 +1,4 @@ // -// FaceTracker.h -// interface/src/devices -// // Created by Andrzej Kapolka on 4/9/14. // Copyright 2014 High Fidelity, Inc. // @@ -20,7 +17,7 @@ #include -/// Base class for face trackers (Faceshift, DDE). +/// Base class for face trackers (DDE, BinaryVR). class FaceTracker : public QObject { Q_OBJECT @@ -45,12 +42,21 @@ public: const QVector& getBlendshapeCoefficients() const; float getBlendshapeCoefficient(int index) const; - bool isMuted() const { return _isMuted; } - void setIsMuted(bool isMuted) { _isMuted = isMuted; } + static bool isMuted() { return _isMuted; } + static void setIsMuted(bool isMuted) { _isMuted = isMuted; } static float getEyeDeflection() { return _eyeDeflection.get(); } static void setEyeDeflection(float eyeDeflection); + static void updateFakeCoefficients(float leftBlink, + float rightBlink, + float browUp, + float jawOpen, + float mouth2, + float mouth3, + float mouth4, + QVector& coefficients); + signals: void muteToggled(); @@ -63,7 +69,7 @@ protected: virtual ~FaceTracker() {}; bool _isInitialized = false; - bool _isMuted = true; + static bool _isMuted; glm::vec3 _headTranslation = glm::vec3(0.0f); glm::quat _headRotation = glm::quat(); @@ -84,6 +90,24 @@ private: bool _isCalculatingFPS = false; int _frameCount = 0; + // see http://support.faceshift.com/support/articles/35129-export-of-blendshapes + static const int _leftBlinkIndex = 0; + static const int _rightBlinkIndex = 1; + static const int _leftEyeOpenIndex = 8; + static const int _rightEyeOpenIndex = 9; + + // Brows + static const int _browDownLeftIndex = 14; + static const int _browDownRightIndex = 15; + static const int _browUpCenterIndex = 16; + static const int _browUpLeftIndex = 17; + static const int _browUpRightIndex = 18; + + static const int _mouthSmileLeftIndex = 28; + static const int _mouthSmileRightIndex = 29; + + static const int _jawOpenIndex = 21; + static Setting::Handle _eyeDeflection; }; diff --git a/interface/src/devices/Faceshift.cpp b/interface/src/devices/Faceshift.cpp deleted file mode 100644 index 81c099c740..0000000000 --- a/interface/src/devices/Faceshift.cpp +++ /dev/null @@ -1,310 +0,0 @@ -// -// Faceshift.cpp -// interface/src/devices -// -// Created by Andrzej Kapolka on 9/3/13. -// Copyright 2013 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 - -#include -#include -#include - -#include "Faceshift.h" -#include "Menu.h" -#include "Util.h" -#include "InterfaceLogging.h" - -#ifdef HAVE_FACESHIFT -using namespace fs; -#endif - -using namespace std; - -const QString DEFAULT_FACESHIFT_HOSTNAME = "localhost"; -const quint16 FACESHIFT_PORT = 33433; - -Faceshift::Faceshift() : - _hostname("faceshiftHostname", DEFAULT_FACESHIFT_HOSTNAME) -{ -#ifdef HAVE_FACESHIFT - connect(&_tcpSocket, SIGNAL(connected()), SLOT(noteConnected())); - connect(&_tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(noteError(QAbstractSocket::SocketError))); - connect(&_tcpSocket, SIGNAL(readyRead()), SLOT(readFromSocket())); - connect(&_tcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), SIGNAL(connectionStateChanged())); - connect(&_tcpSocket, SIGNAL(disconnected()), SLOT(noteDisconnected())); - - connect(&_udpSocket, SIGNAL(readyRead()), SLOT(readPendingDatagrams())); - - _udpSocket.bind(FACESHIFT_PORT); -#endif -} - -#ifdef HAVE_FACESHIFT -void Faceshift::init() { - FaceTracker::init(); - setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Faceshift) && !_isMuted); -} - -void Faceshift::update(float deltaTime) { - if (!isActive()) { - return; - } - FaceTracker::update(deltaTime); - - // get the euler angles relative to the window - glm::vec3 eulers = glm::degrees(safeEulerAngles(_headRotation * glm::quat(glm::radians(glm::vec3( - (_eyeGazeLeftPitch + _eyeGazeRightPitch) / 2.0f, (_eyeGazeLeftYaw + _eyeGazeRightYaw) / 2.0f, 0.0f))))); - - // compute and subtract the long term average - const float LONG_TERM_AVERAGE_SMOOTHING = 0.999f; - if (!_longTermAverageInitialized) { - _longTermAverageEyePitch = eulers.x; - _longTermAverageEyeYaw = eulers.y; - _longTermAverageInitialized = true; - - } else { - _longTermAverageEyePitch = glm::mix(eulers.x, _longTermAverageEyePitch, LONG_TERM_AVERAGE_SMOOTHING); - _longTermAverageEyeYaw = glm::mix(eulers.y, _longTermAverageEyeYaw, LONG_TERM_AVERAGE_SMOOTHING); - } - _estimatedEyePitch = eulers.x - _longTermAverageEyePitch; - _estimatedEyeYaw = eulers.y - _longTermAverageEyeYaw; -} - -void Faceshift::reset() { - if (_tcpSocket.state() == QAbstractSocket::ConnectedState) { - qCDebug(interfaceapp, "Faceshift: Reset"); - - FaceTracker::reset(); - - string message; - fsBinaryStream::encode_message(message, fsMsgCalibrateNeutral()); - send(message); - } - _longTermAverageInitialized = false; -} - -bool Faceshift::isActive() const { - const quint64 ACTIVE_TIMEOUT_USECS = 1000000; - return (usecTimestampNow() - _lastReceiveTimestamp) < ACTIVE_TIMEOUT_USECS; -} - -bool Faceshift::isTracking() const { - return isActive() && _tracking; -} -#endif - - -bool Faceshift::isConnectedOrConnecting() const { - return _tcpSocket.state() == QAbstractSocket::ConnectedState || - (_tcpRetryCount == 0 && _tcpSocket.state() != QAbstractSocket::UnconnectedState); -} - -void Faceshift::updateFakeCoefficients(float leftBlink, float rightBlink, float browUp, - float jawOpen, float mouth2, float mouth3, float mouth4, QVector& coefficients) const { - const int MMMM_BLENDSHAPE = 34; - const int FUNNEL_BLENDSHAPE = 40; - const int SMILE_LEFT_BLENDSHAPE = 28; - const int SMILE_RIGHT_BLENDSHAPE = 29; - const int MAX_FAKE_BLENDSHAPE = 40; // Largest modified blendshape from above and below - - coefficients.resize(max((int)coefficients.size(), MAX_FAKE_BLENDSHAPE + 1)); - qFill(coefficients.begin(), coefficients.end(), 0.0f); - coefficients[_leftBlinkIndex] = leftBlink; - coefficients[_rightBlinkIndex] = rightBlink; - coefficients[_browUpCenterIndex] = browUp; - coefficients[_browUpLeftIndex] = browUp; - coefficients[_browUpRightIndex] = browUp; - coefficients[_jawOpenIndex] = jawOpen; - coefficients[SMILE_LEFT_BLENDSHAPE] = coefficients[SMILE_RIGHT_BLENDSHAPE] = mouth4; - coefficients[MMMM_BLENDSHAPE] = mouth2; - coefficients[FUNNEL_BLENDSHAPE] = mouth3; -} - -void Faceshift::setEnabled(bool enabled) { - // Don't enable until have explicitly initialized - if (!_isInitialized) { - return; - } -#ifdef HAVE_FACESHIFT - if ((_tcpEnabled = enabled)) { - connectSocket(); - } else { - qCDebug(interfaceapp, "Faceshift: Disconnecting..."); - _tcpSocket.disconnectFromHost(); - } -#endif -} - -void Faceshift::connectSocket() { - if (_tcpEnabled) { - if (!_tcpRetryCount) { - qCDebug(interfaceapp, "Faceshift: Connecting..."); - } - - _tcpSocket.connectToHost(_hostname.get(), FACESHIFT_PORT); - _tracking = false; - } -} - -void Faceshift::noteConnected() { -#ifdef HAVE_FACESHIFT - qCDebug(interfaceapp, "Faceshift: Connected"); - // request the list of blendshape names - string message; - fsBinaryStream::encode_message(message, fsMsgSendBlendshapeNames()); - send(message); -#endif -} - -void Faceshift::noteDisconnected() { -#ifdef HAVE_FACESHIFT - qCDebug(interfaceapp, "Faceshift: Disconnected"); -#endif -} - -void Faceshift::noteError(QAbstractSocket::SocketError error) { - if (!_tcpRetryCount) { - // Only spam log with fail to connect the first time, so that we can keep waiting for server - qCWarning(interfaceapp) << "Faceshift: " << _tcpSocket.errorString(); - } - // retry connection after a 2 second delay - if (_tcpEnabled) { - _tcpRetryCount++; - QTimer::singleShot(2000, this, SLOT(connectSocket())); - } -} - -void Faceshift::readPendingDatagrams() { - QByteArray buffer; - while (_udpSocket.hasPendingDatagrams()) { - buffer.resize(_udpSocket.pendingDatagramSize()); - _udpSocket.readDatagram(buffer.data(), buffer.size()); - receive(buffer); - } -} - -void Faceshift::readFromSocket() { - receive(_tcpSocket.readAll()); -} - -void Faceshift::send(const std::string& message) { - _tcpSocket.write(message.data(), message.size()); -} - -void Faceshift::receive(const QByteArray& buffer) { -#ifdef HAVE_FACESHIFT - _lastReceiveTimestamp = usecTimestampNow(); - - _stream.received(buffer.size(), buffer.constData()); - fsMsgPtr msg; - for (fsMsgPtr msg; (msg = _stream.get_message()); ) { - switch (msg->id()) { - case fsMsg::MSG_OUT_TRACKING_STATE: { - const fsTrackingData& data = static_pointer_cast(msg)->tracking_data(); - if ((_tracking = data.m_trackingSuccessful)) { - glm::quat newRotation = glm::quat(data.m_headRotation.w, -data.m_headRotation.x, - data.m_headRotation.y, -data.m_headRotation.z); - // Compute angular velocity of the head - glm::quat r = glm::normalize(newRotation * glm::inverse(_headRotation)); - float theta = 2 * acos(r.w); - if (theta > EPSILON) { - float rMag = glm::length(glm::vec3(r.x, r.y, r.z)); - _headAngularVelocity = theta / _averageFrameTime * glm::vec3(r.x, r.y, r.z) / rMag; - } else { - _headAngularVelocity = glm::vec3(0,0,0); - } - const float ANGULAR_VELOCITY_FILTER_STRENGTH = 0.3f; - _headRotation = safeMix(_headRotation, newRotation, glm::clamp(glm::length(_headAngularVelocity) * - ANGULAR_VELOCITY_FILTER_STRENGTH, 0.0f, 1.0f)); - - const float TRANSLATION_SCALE = 0.02f; - glm::vec3 newHeadTranslation = glm::vec3(data.m_headTranslation.x, data.m_headTranslation.y, - -data.m_headTranslation.z) * TRANSLATION_SCALE; - - _headLinearVelocity = (newHeadTranslation - _lastHeadTranslation) / _averageFrameTime; - - const float LINEAR_VELOCITY_FILTER_STRENGTH = 0.3f; - float velocityFilter = glm::clamp(1.0f - glm::length(_headLinearVelocity) * - LINEAR_VELOCITY_FILTER_STRENGTH, 0.0f, 1.0f); - _filteredHeadTranslation = velocityFilter * _filteredHeadTranslation + (1.0f - velocityFilter) * newHeadTranslation; - - _lastHeadTranslation = newHeadTranslation; - _headTranslation = _filteredHeadTranslation; - - _eyeGazeLeftPitch = -data.m_eyeGazeLeftPitch; - _eyeGazeLeftYaw = data.m_eyeGazeLeftYaw; - _eyeGazeRightPitch = -data.m_eyeGazeRightPitch; - _eyeGazeRightYaw = data.m_eyeGazeRightYaw; - _blendshapeCoefficients = QVector::fromStdVector(data.m_coeffs); - - const float FRAME_AVERAGING_FACTOR = 0.99f; - quint64 usecsNow = usecTimestampNow(); - if (_lastMessageReceived != 0) { - _averageFrameTime = FRAME_AVERAGING_FACTOR * _averageFrameTime + - (1.0f - FRAME_AVERAGING_FACTOR) * (float)(usecsNow - _lastMessageReceived) / 1000000.0f; - } - _lastMessageReceived = usecsNow; - } - break; - } - case fsMsg::MSG_OUT_BLENDSHAPE_NAMES: { - const vector& names = static_pointer_cast(msg)->blendshape_names(); - for (int i = 0; i < (int)names.size(); i++) { - if (names[i] == "EyeBlink_L") { - _leftBlinkIndex = i; - - } else if (names[i] == "EyeBlink_R") { - _rightBlinkIndex = i; - - } else if (names[i] == "EyeOpen_L") { - _leftEyeOpenIndex = i; - - } else if (names[i] == "EyeOpen_R") { - _rightEyeOpenIndex = i; - - } else if (names[i] == "BrowsD_L") { - _browDownLeftIndex = i; - - } else if (names[i] == "BrowsD_R") { - _browDownRightIndex = i; - - } else if (names[i] == "BrowsU_C") { - _browUpCenterIndex = i; - - } else if (names[i] == "BrowsU_L") { - _browUpLeftIndex = i; - - } else if (names[i] == "BrowsU_R") { - _browUpRightIndex = i; - - } else if (names[i] == "JawOpen") { - _jawOpenIndex = i; - - } else if (names[i] == "MouthSmile_L") { - _mouthSmileLeftIndex = i; - - } else if (names[i] == "MouthSmile_R") { - _mouthSmileRightIndex = i; - } - } - break; - } - default: - break; - } - } -#endif - - FaceTracker::countFrame(); -} - -void Faceshift::setHostname(const QString& hostname) { - _hostname.set(hostname); -} - diff --git a/interface/src/devices/Faceshift.h b/interface/src/devices/Faceshift.h deleted file mode 100644 index 2c5889857c..0000000000 --- a/interface/src/devices/Faceshift.h +++ /dev/null @@ -1,155 +0,0 @@ -// -// Faceshift.h -// interface/src/devices -// -// Created by Andrzej Kapolka on 9/3/13. -// Copyright 2013 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 -// - -#ifndef hifi_Faceshift_h -#define hifi_Faceshift_h - -#include -#include - -#ifdef HAVE_FACESHIFT -#include -#endif - -#include -#include - -#include "FaceTracker.h" - -const float STARTING_FACESHIFT_FRAME_TIME = 0.033f; - -/// Handles interaction with the Faceshift software, which provides head position/orientation and facial features. -class Faceshift : public FaceTracker, public Dependency { - Q_OBJECT - SINGLETON_DEPENDENCY - -public: -#ifdef HAVE_FACESHIFT - // If we don't have faceshift, use the base class' methods - virtual void init() override; - virtual void update(float deltaTime) override; - virtual void reset() override; - - virtual bool isActive() const override; - virtual bool isTracking() const override; -#endif - - bool isConnectedOrConnecting() const; - - const glm::vec3& getHeadAngularVelocity() const { return _headAngularVelocity; } - - // these pitch/yaw angles are in degrees - float getEyeGazeLeftPitch() const { return _eyeGazeLeftPitch; } - float getEyeGazeLeftYaw() const { return _eyeGazeLeftYaw; } - - float getEyeGazeRightPitch() const { return _eyeGazeRightPitch; } - float getEyeGazeRightYaw() const { return _eyeGazeRightYaw; } - - float getLeftBlink() const { return getBlendshapeCoefficient(_leftBlinkIndex); } - float getRightBlink() const { return getBlendshapeCoefficient(_rightBlinkIndex); } - float getLeftEyeOpen() const { return getBlendshapeCoefficient(_leftEyeOpenIndex); } - float getRightEyeOpen() const { return getBlendshapeCoefficient(_rightEyeOpenIndex); } - - float getBrowDownLeft() const { return getBlendshapeCoefficient(_browDownLeftIndex); } - float getBrowDownRight() const { return getBlendshapeCoefficient(_browDownRightIndex); } - float getBrowUpCenter() const { return getBlendshapeCoefficient(_browUpCenterIndex); } - float getBrowUpLeft() const { return getBlendshapeCoefficient(_browUpLeftIndex); } - float getBrowUpRight() const { return getBlendshapeCoefficient(_browUpRightIndex); } - - float getMouthSize() const { return getBlendshapeCoefficient(_jawOpenIndex); } - float getMouthSmileLeft() const { return getBlendshapeCoefficient(_mouthSmileLeftIndex); } - float getMouthSmileRight() const { return getBlendshapeCoefficient(_mouthSmileRightIndex); } - - QString getHostname() { return _hostname.get(); } - void setHostname(const QString& hostname); - - void updateFakeCoefficients(float leftBlink, - float rightBlink, - float browUp, - float jawOpen, - float mouth2, - float mouth3, - float mouth4, - QVector& coefficients) const; - -signals: - void connectionStateChanged(); - -public slots: - void setEnabled(bool enabled) override; - -private slots: - void connectSocket(); - void noteConnected(); - void noteError(QAbstractSocket::SocketError error); - void readPendingDatagrams(); - void readFromSocket(); - void noteDisconnected(); - -private: - Faceshift(); - virtual ~Faceshift() {} - - void send(const std::string& message); - void receive(const QByteArray& buffer); - - QTcpSocket _tcpSocket; - QUdpSocket _udpSocket; - -#ifdef HAVE_FACESHIFT - fs::fsBinaryStream _stream; -#endif - - bool _tcpEnabled = true; - int _tcpRetryCount = 0; - bool _tracking = false; - quint64 _lastReceiveTimestamp = 0; - quint64 _lastMessageReceived = 0; - float _averageFrameTime = STARTING_FACESHIFT_FRAME_TIME; - - glm::vec3 _headAngularVelocity = glm::vec3(0.0f); - glm::vec3 _headLinearVelocity = glm::vec3(0.0f); - glm::vec3 _lastHeadTranslation = glm::vec3(0.0f); - glm::vec3 _filteredHeadTranslation = glm::vec3(0.0f); - - // degrees - float _eyeGazeLeftPitch = 0.0f; - float _eyeGazeLeftYaw = 0.0f; - float _eyeGazeRightPitch = 0.0f; - float _eyeGazeRightYaw = 0.0f; - - // degrees - float _longTermAverageEyePitch = 0.0f; - float _longTermAverageEyeYaw = 0.0f; - bool _longTermAverageInitialized = false; - - Setting::Handle _hostname; - - // see http://support.faceshift.com/support/articles/35129-export-of-blendshapes - int _leftBlinkIndex = 0; - int _rightBlinkIndex = 1; - int _leftEyeOpenIndex = 8; - int _rightEyeOpenIndex = 9; - - // Brows - int _browDownLeftIndex = 14; - int _browDownRightIndex = 15; - int _browUpCenterIndex = 16; - int _browUpLeftIndex = 17; - int _browUpRightIndex = 18; - - int _mouthSmileLeftIndex = 28; - int _mouthSmileRightIndex = 29; - - int _jawOpenIndex = 21; -}; - -#endif // hifi_Faceshift_h diff --git a/interface/src/devices/Logging.cpp b/interface/src/devices/Logging.cpp new file mode 100644 index 0000000000..a4dcf1b711 --- /dev/null +++ b/interface/src/devices/Logging.cpp @@ -0,0 +1,11 @@ +// +// Created by Bradley Austin Davis on 2017/04/25 +// Copyright 2013-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 "Logging.h" + +Q_LOGGING_CATEGORY(trackers, "hifi.trackers") diff --git a/interface/src/devices/Logging.h b/interface/src/devices/Logging.h new file mode 100644 index 0000000000..554429b61d --- /dev/null +++ b/interface/src/devices/Logging.h @@ -0,0 +1,16 @@ +// +// Created by Bradley Austin Davis on 2017/04/25 +// Copyright 2013-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 +// + +#ifndef hifi_TrackersLogging_h +#define hifi_TrackersLogging_h + +#include + +Q_DECLARE_LOGGING_CATEGORY(trackers) + +#endif // hifi_TrackersLogging_h diff --git a/interface/src/devices/MotionTracker.cpp b/interface/src/devices/MotionTracker.cpp index 234a8d0c0c..c6012c0422 100644 --- a/interface/src/devices/MotionTracker.cpp +++ b/interface/src/devices/MotionTracker.cpp @@ -1,7 +1,4 @@ // -// MotionTracker.cpp -// interface/src/devices -// // Created by Sam Cake on 6/20/14. // Copyright 2014 High Fidelity, Inc. // @@ -10,8 +7,6 @@ // #include "MotionTracker.h" -#include "GLMHelpers.h" - // glm::mult(mat43, mat43) just the composition of the 2 matrices assuming they are in fact mat44 with the last raw = { 0, 0, 0, 1 } namespace glm { diff --git a/interface/src/devices/MotionTracker.h b/interface/src/devices/MotionTracker.h index a4b5e6735e..26c8dcea2c 100644 --- a/interface/src/devices/MotionTracker.h +++ b/interface/src/devices/MotionTracker.h @@ -1,7 +1,4 @@ // -// MotionTracker.h -// interface/src/devices -// // Created by Sam Cake on 6/20/14. // Copyright 2014 High Fidelity, Inc. // @@ -14,20 +11,7 @@ #include "DeviceTracker.h" -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wsign-compare" -#endif - -#include - -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif - - -#include -#include +#include /// Base class for device trackers. class MotionTracker : public DeviceTracker { diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index a12d9020ae..89fffbab22 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -207,13 +206,6 @@ void setupPreferences() { auto setter = [](float value) { FaceTracker::setEyeDeflection(value); }; preferences->addPreference(new SliderPreference(AVATAR_TUNING, "Face tracker eye deflection", getter, setter)); } - { - auto getter = []()->QString { return DependencyManager::get()->getHostname(); }; - auto setter = [](const QString& value) { DependencyManager::get()->setHostname(value); }; - auto preference = new EditPreference(AVATAR_TUNING, "Faceshift hostname", getter, setter); - preference->setPlaceholderText("localhost"); - preferences->addPreference(preference); - } { auto getter = [=]()->QString { return myAvatar->getAnimGraphOverrideUrl().toString(); }; auto setter = [=](const QString& value) { myAvatar->setAnimGraphOverrideUrl(QUrl(value)); }; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 1427ce6359..6c265ef1b6 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -393,9 +393,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (isFingerPointing) { setAtBit(flags, HAND_STATE_FINGER_POINTING_BIT); } - // faceshift state + // face tracker state if (_headData->_isFaceTrackerConnected) { - setAtBit(flags, IS_FACESHIFT_CONNECTED); + setAtBit(flags, IS_FACE_TRACKER_CONNECTED); } // eye tracker state if (_headData->_isEyeTrackerConnected) { @@ -883,7 +883,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { auto newHandState = getSemiNibbleAt(bitItems, HAND_STATE_START_BIT) + (oneAtBit(bitItems, HAND_STATE_FINGER_POINTING_BIT) ? IS_FINGER_POINTING_FLAG : 0); - auto newFaceTrackerConnected = oneAtBit(bitItems, IS_FACESHIFT_CONNECTED); + auto newFaceTrackerConnected = oneAtBit(bitItems, IS_FACE_TRACKER_CONNECTED); auto newEyeTrackerConnected = oneAtBit(bitItems, IS_EYE_TRACKER_CONNECTED); bool keyStateChanged = (_keyState != newKeyState); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 545a5f1f8c..b2cc912007 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -99,7 +99,7 @@ const quint32 AVATAR_MOTION_SCRIPTABLE_BITS = // Referential Data - R is found in the 7th bit const int KEY_STATE_START_BIT = 0; // 1st and 2nd bits const int HAND_STATE_START_BIT = 2; // 3rd and 4th bits -const int IS_FACESHIFT_CONNECTED = 4; // 5th bit +const int IS_FACE_TRACKER_CONNECTED = 4; // 5th bit const int IS_EYE_TRACKER_CONNECTED = 5; // 6th bit (was CHAT_CIRCLING) const int HAS_REFERENTIAL = 6; // 7th bit const int HAND_STATE_FINGER_POINTING_BIT = 7; // 8th bit @@ -218,7 +218,7 @@ namespace AvatarDataPacket { } PACKED_END; const size_t AVATAR_LOCAL_POSITION_SIZE = 12; - // only present if IS_FACESHIFT_CONNECTED flag is set in AvatarInfo.flags + // only present if IS_FACE_TRACKER_CONNECTED flag is set in AvatarInfo.flags PACKED_BEGIN struct FaceTrackerInfo { float leftEyeBlink; float rightEyeBlink; diff --git a/libraries/ui/src/ui/Menu.cpp b/libraries/ui/src/ui/Menu.cpp index 50833e90fc..7511448c38 100644 --- a/libraries/ui/src/ui/Menu.cpp +++ b/libraries/ui/src/ui/Menu.cpp @@ -223,6 +223,18 @@ QAction* Menu::addCheckableActionToQMenuAndActionHash(MenuWrapper* destinationMe return action; } +QAction* Menu::addCheckableActionToQMenuAndActionHash(MenuWrapper* destinationMenu, + const QString& actionName, + const std::function& handler, + const QKeySequence& shortcut, + const bool checked, + int menuItemLocation, + const QString& grouping) { + auto action = addCheckableActionToQMenuAndActionHash(destinationMenu, actionName, shortcut, checked, nullptr, nullptr, menuItemLocation, grouping); + connect(action, &QAction::triggered, handler); + return action; +} + void Menu::removeAction(MenuWrapper* menu, const QString& actionName) { auto action = _actionHash.value(actionName); menu->removeAction(action); diff --git a/libraries/ui/src/ui/Menu.h b/libraries/ui/src/ui/Menu.h index 9839bd1eb6..25f8f74063 100644 --- a/libraries/ui/src/ui/Menu.h +++ b/libraries/ui/src/ui/Menu.h @@ -9,6 +9,8 @@ #ifndef hifi_ui_Menu_h #define hifi_ui_Menu_h +#include + #include #include #include @@ -90,6 +92,14 @@ public: int menuItemLocation = UNSPECIFIED_POSITION, const QString& grouping = QString()); + QAction* addCheckableActionToQMenuAndActionHash(MenuWrapper* destinationMenu, + const QString& actionName, + const std::function& handler, + const QKeySequence& shortcut = 0, + const bool checked = false, + int menuItemLocation = UNSPECIFIED_POSITION, + const QString& grouping = QString()); + void removeAction(MenuWrapper* menu, const QString& actionName); public slots: From f9debf13886f451eea64173f36b3c2dc17741971 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 28 Apr 2017 16:53:08 -0700 Subject: [PATCH 20/47] checkpoint --- interface/resources/qml/hifi/Feed.qml | 46 ++++++-- .../qml/hifi/tablet/TabletAddressDialog.qml | 73 +++++-------- scripts/system/pal.js | 16 ++- scripts/system/tablet-goto.js | 103 ++++++++++++------ 4 files changed, 150 insertions(+), 88 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index b03144644a..ee0db29cc7 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -34,12 +34,12 @@ Column { property string metaverseServerUrl: ''; property string actions: 'snapshot'; - onActionsChanged: fillDestinations(); Component.onCompleted: fillDestinations(); property string labelText: actions; property string filter: ''; onFilterChanged: filterChoicesByText(); property var goFunction: null; + property var rpc: null; HifiConstants { id: hifi } ListModel { id: suggestions; } @@ -81,10 +81,24 @@ Column { property var allStories: []; property var placeMap: ({}); // Used for making stacks. property int requestId: 0; + function handleError(url, error, data, cb) { // cb(error) and answer truthy if needed, else falsey + if (!error && (data.status === 'success')) { + return; + } + if (!error) { // Create a message from the data + error = data.status + ': ' + data.error; + } + if (typeof(error) === 'string') { // Make a proper Error object + error = new Error(error); + } + error.message += ' in ' + url; // Include the url. + cb(error); + return true; + } function getUserStoryPage(pageNumber, cb, cb1) { // cb(error) after all pages of domain data have been added to model // If supplied, cb1 will be run after the first page IFF it is not the last, for responsiveness. var options = [ - 'now=' + new Date().toISOString(), + //'now=' + new Date().toISOString(), 'include_actions=' + actions, 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'), 'require_online=true', @@ -93,8 +107,17 @@ Column { ]; var url = metaverseBase + 'user_stories?' + options.join('&'); var thisRequestId = ++requestId; - getRequest(url, function (error, data) { - if ((thisRequestId !== requestId) || handleError(url, error, data, cb)) { + rpc('request', { + uri: url + }, function (error, data) { + console.log('fixme response', url, JSON.stringify(error), JSON.stringify(data)); + data.total_pages = 1; // fixme remove after testing + if (thisRequestId !== requestId) { + error = 'stale'; + } + //console.log('fixme', actions, pageNumber, thisRequestId, requestId, url, error) + //console.log('fixme data', actions, pageNumber, JSON.stringify(data)); + if (handleError(url, error, data, cb)) { return; // abandon stale requests } allStories = allStories.concat(data.user_stories.map(makeModelData)); @@ -108,21 +131,28 @@ Column { }); } function fillDestinations() { // Public + function report(label, error) { + console.log(label, actions, error || 'ok', allStories.length, 'filtered to', suggestions.count); + } var filter = makeFilteredStoryProcessor(), counter = 0; allStories = []; suggestions.clear(); placeMap = {}; getUserStoryPage(1, function (error) { allStories.slice(counter).forEach(filter); - console.log('user stories query', actions, error || 'ok', allStories.length, 'filtered to', suggestions.count); + report('user stories update', error); root.visible = !!suggestions.count; - }, function () { // If there's more than a page, put what we have in the model right away, keeping track of how many are processed. + }/*, function () { // If there's more than a page, put what we have in the model right away, keeping track of how many are processed. allStories.forEach(function (story) { counter++; filter(story); root.visible = !!suggestions.count; }); - }); + report('user stories'); + }*/); + } + function identity(x) { + return x; } function makeFilteredStoryProcessor() { // answer a function(storyData) that adds it to suggestions if it matches var words = filter.toUpperCase().split(/\s+/).filter(identity); @@ -130,7 +160,7 @@ Column { if (story.action === 'snapshot') { return true; } - return (story.place_name !== AddressManager.placename); // Not our entry, but do show other entry points to current domain. + return true; // fixme restore (story.place_name !== AddressManager.placename); // Not our entry, but do show other entry points to current domain. } function matches(story) { if (!words.length) { diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 9689583649..0893c51517 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -33,9 +33,33 @@ StackView { property int cardWidth: 212; property int cardHeight: 152; property string metaverseBase: addressBarDialog.metaverseServerUrl + "/api/v1/"; - property var tablet: null; + // This version only implements rpc(method, parameters, callback(error, result)) calls initiated from here, not initiated from .js, nor "notifications". + property var rpcCalls: ({}); + property var rpcCounter: 0; + signal sendToScript(var message); + function rpc(method, parameters, callback) { + console.log('fixme rpc', method); + sendToScript('foo'); + console.log('fixme sent to script'); + /*rpcCalls[rpcCounter] = callback; + var message = {method: method, params: parameters, id: rpcCounter++, jsonrpc: "2.0"}; + console.log('fixme sending rpc', JSON.stringify(message)); + sendToScript(message); + console.log('fixme sent rpc', message.id);*/ + } + function fromScript(message) { + console.log('fixme got message from script:', JSON.stringify(message)); + var callback = rpcCalls[message.id]; + if (!callback) { + console.log('No callback for message fromScript', JSON.stringify(message)); + return; + } + delete rpcCalls[message.id]; + callback(message.error, message.result); + } + Component { id: tabletWebView; TabletWebView {} } Component.onCompleted: { updateLocationText(false); @@ -266,6 +290,7 @@ StackView { actions: 'announcement'; filter: addressLine.text; goFunction: goCard; + rpc: root.rpc; } Feed { id: places; @@ -278,6 +303,7 @@ StackView { actions: 'concurrency'; filter: addressLine.text; goFunction: goCard; + rpc: root.rpc; } Feed { id: snapshots; @@ -291,6 +317,7 @@ StackView { actions: 'snapshot'; filter: addressLine.text; goFunction: goCard; + rpc: root.rpc; } } } @@ -330,50 +357,6 @@ StackView { } - function getRequest(url, cb) { // cb(error, responseOfCorrectContentType) of url. General for 'get' text/html/json, but without redirects. - // TODO: make available to other .qml. - var request = new XMLHttpRequest(); - // QT bug: apparently doesn't handle onload. Workaround using readyState. - request.onreadystatechange = function () { - var READY_STATE_DONE = 4; - var HTTP_OK = 200; - if (request.readyState >= READY_STATE_DONE) { - var error = (request.status !== HTTP_OK) && request.status.toString() + ':' + request.statusText, - response = !error && request.responseText, - contentType = !error && request.getResponseHeader('content-type'); - if (!error && contentType.indexOf('application/json') === 0) { - try { - response = JSON.parse(response); - } catch (e) { - error = e; - } - } - cb(error, response); - } - }; - request.open("GET", url, true); - request.send(); - } - - function identity(x) { - return x; - } - - function handleError(url, error, data, cb) { // cb(error) and answer truthy if needed, else falsey - if (!error && (data.status === 'success')) { - return; - } - if (!error) { // Create a message from the data - error = data.status + ': ' + data.error; - } - if (typeof(error) === 'string') { // Make a proper Error object - error = new Error(error); - } - error.message += ' in ' + url; // Include the url. - cb(error); - return true; - } - function updateLocationText(enteringAddress) { if (enteringAddress) { notice.text = "Go To a place, @user, path, or network address:"; diff --git a/scripts/system/pal.js b/scripts/system/pal.js index ae64065216..4a6b8d4142 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -723,7 +723,6 @@ function startup() { activeIcon: "icons/tablet-icons/people-a.svg", sortOrder: 7 }); - tablet.fromQml.connect(fromQml); button.clicked.connect(onTabletButtonClicked); tablet.screenChanged.connect(onTabletScreenChanged); Users.usernameFromIDReply.connect(usernameFromIDReply); @@ -789,8 +788,23 @@ function onTabletButtonClicked() { audioTimer = createAudioInterval(conserveResources ? AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS : AUDIO_LEVEL_UPDATE_INTERVAL_MS); } } +var hasEventBridge = false; +function wireEventBridge(on) { + if (on) { + if (!hasEventBridge) { + tablet.fromQml.connect(fromQml); + hasEventBridge = true; + } + } else { + if (hasEventBridge) { + tablet.fromQml.disconnect(fromQml); + hasEventBridge = false; + } + } +} function onTabletScreenChanged(type, url) { + wireEventBridge(shouldActivateButton); // for toolbar mode: change button to active when window is first openend, false otherwise. button.editProperties({isActive: shouldActivateButton}); shouldActivateButton = false; diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index 8ba19d18a8..c32f5d9e50 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -30,40 +30,6 @@ text: buttonName, sortOrder: 8 }); - function messagesWaiting(isWaiting) { - button.editProperties({ - icon: isWaiting ? WAITING_ICON : NORMAL_ICON - // No need for a different activeIcon, because we issue messagesWaiting(false) when the button goes active anyway. - }); - } - - function onClicked() { - if (onGotoScreen) { - // for toolbar-mode: go back to home screen, this will close the window. - tablet.gotoHomeScreen(); - } else { - shouldActivateButton = true; - tablet.loadQMLSource(gotoQmlSource); - onGotoScreen = true; - } - } - - function onScreenChanged(type, url) { - ignore(type); - if (url === gotoQmlSource) { - onGotoScreen = true; - shouldActivateButton = true; - button.editProperties({isActive: shouldActivateButton}); - messagesWaiting(false); - } else { - shouldActivateButton = false; - onGotoScreen = false; - button.editProperties({isActive: shouldActivateButton}); - } - } - button.clicked.connect(onClicked); - tablet.screenChanged.connect(onScreenChanged); - function request(options, callback) { // cb(error, responseOfCorrectContentType) of url. A subset of npm request. var httpRequest = new XMLHttpRequest(), key; // QT bug: apparently doesn't handle onload. Workaround using readyState. @@ -112,6 +78,74 @@ httpRequest.open(options.method, options.uri, true); httpRequest.send(options.body); } + function fromQmlXX(message) { + print('fixme got fromQml', JSON.stringify(message)); + /*var response = {id: message.id, jsonrpc: "2.0"}; + switch (message.method) { + case 'request': + request(message.params, function (error, data) { + response.error = error; + response.result = data; + tablet.sendToQml(response); + }); + return; + default: + response.error = {message: 'Unrecognized message', data: message}; + } + tablet.sendToQml(response);*/ + } + function messagesWaiting(isWaiting) { + button.editProperties({ + icon: isWaiting ? WAITING_ICON : NORMAL_ICON + // No need for a different activeIcon, because we issue messagesWaiting(false) when the button goes active anyway. + }); + } + var hasEventBridge = false; + function wireEventBridge(on) { + print('fixme wireEventBridge', on, hasEventBridge); + if (on) { + if (!hasEventBridge) { + tablet.fromQml.connect(fromQmlXX); + print('fixme wired', tablet); + hasEventBridge = true; + } + } else { + if (hasEventBridge) { + tablet.fromQml.disconnect(fromQmlXX); + hasEventBridge = false; + } + } + } + wireEventBridge(true); + + function onClicked() { + if (onGotoScreen) { + // for toolbar-mode: go back to home screen, this will close the window. + tablet.gotoHomeScreen(); + } else { + shouldActivateButton = true; + tablet.loadQMLSource(gotoQmlSource); + onGotoScreen = true; + } + } + + function onScreenChanged(type, url) { + ignore(type); + if (url === gotoQmlSource) { + onGotoScreen = true; + shouldActivateButton = true; + button.editProperties({isActive: shouldActivateButton}); + wireEventBridge(true); + messagesWaiting(false); + } else { + shouldActivateButton = false; + onGotoScreen = false; + button.editProperties({isActive: shouldActivateButton}); + wireEventBridge(false); + } + } + button.clicked.connect(onClicked); + tablet.screenChanged.connect(onScreenChanged); var stories = {}; var DEBUG = false; @@ -135,6 +169,7 @@ return; } var didNotify = false; + print('fixme poll', url, JSON.stringify(data.user_stories)); data.user_stories.forEach(function (story) { if (stories[story.id]) { // already seen return; From b6ae0a5bde0705ab6e99ee8fbe696d7be4e5fc54 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Fri, 28 Apr 2017 20:06:13 -0700 Subject: [PATCH 21/47] add nextTick, and cleanup --- interface/resources/qml/hifi/Feed.qml | 23 +++++++++---------- .../qml/hifi/tablet/TabletAddressDialog.qml | 10 ++------ scripts/system/tablet-goto.js | 15 ++++-------- 3 files changed, 18 insertions(+), 30 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index ee0db29cc7..fd3472b7be 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -34,7 +34,8 @@ Column { property string metaverseServerUrl: ''; property string actions: 'snapshot'; - Component.onCompleted: fillDestinations(); + // sendToScript doesn't get wired until after everything gets created. So we have to queue fillDestinations on nextTick. + Component.onCompleted: delay.start(); property string labelText: actions; property string filter: ''; onFilterChanged: filterChoicesByText(); @@ -98,7 +99,7 @@ Column { function getUserStoryPage(pageNumber, cb, cb1) { // cb(error) after all pages of domain data have been added to model // If supplied, cb1 will be run after the first page IFF it is not the last, for responsiveness. var options = [ - //'now=' + new Date().toISOString(), + 'now=' + new Date().toISOString(), 'include_actions=' + actions, 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'), 'require_online=true', @@ -107,16 +108,10 @@ Column { ]; var url = metaverseBase + 'user_stories?' + options.join('&'); var thisRequestId = ++requestId; - rpc('request', { - uri: url - }, function (error, data) { - console.log('fixme response', url, JSON.stringify(error), JSON.stringify(data)); - data.total_pages = 1; // fixme remove after testing + rpc('request', url, function (error, data) { if (thisRequestId !== requestId) { error = 'stale'; } - //console.log('fixme', actions, pageNumber, thisRequestId, requestId, url, error) - //console.log('fixme data', actions, pageNumber, JSON.stringify(data)); if (handleError(url, error, data, cb)) { return; // abandon stale requests } @@ -130,6 +125,10 @@ Column { cb(); }); } + property var delay: Timer { // No setTimeout or nextTick in QML. + interval: 0; + onTriggered: fillDestinations(); + } function fillDestinations() { // Public function report(label, error) { console.log(label, actions, error || 'ok', allStories.length, 'filtered to', suggestions.count); @@ -142,14 +141,14 @@ Column { allStories.slice(counter).forEach(filter); report('user stories update', error); root.visible = !!suggestions.count; - }/*, function () { // If there's more than a page, put what we have in the model right away, keeping track of how many are processed. + }, function () { // If there's more than a page, put what we have in the model right away, keeping track of how many are processed. allStories.forEach(function (story) { counter++; filter(story); root.visible = !!suggestions.count; }); report('user stories'); - }*/); + }); } function identity(x) { return x; @@ -160,7 +159,7 @@ Column { if (story.action === 'snapshot') { return true; } - return true; // fixme restore (story.place_name !== AddressManager.placename); // Not our entry, but do show other entry points to current domain. + return story.place_name !== AddressManager.placename; // Not our entry, but do show other entry points to current domain. } function matches(story) { if (!words.length) { diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 0893c51517..b7c0d24b24 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -40,17 +40,11 @@ StackView { property var rpcCounter: 0; signal sendToScript(var message); function rpc(method, parameters, callback) { - console.log('fixme rpc', method); - sendToScript('foo'); - console.log('fixme sent to script'); - /*rpcCalls[rpcCounter] = callback; + rpcCalls[rpcCounter] = callback; var message = {method: method, params: parameters, id: rpcCounter++, jsonrpc: "2.0"}; - console.log('fixme sending rpc', JSON.stringify(message)); sendToScript(message); - console.log('fixme sent rpc', message.id);*/ } function fromScript(message) { - console.log('fixme got message from script:', JSON.stringify(message)); var callback = rpcCalls[message.id]; if (!callback) { console.log('No callback for message fromScript', JSON.stringify(message)); @@ -78,7 +72,7 @@ StackView { } - function resetAfterTeleport() { + function resetAfterTeleport() { //storyCardFrame.shown = root.shown = false; } function goCard(targetString) { diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index c32f5d9e50..fec7a6de90 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -78,9 +78,8 @@ httpRequest.open(options.method, options.uri, true); httpRequest.send(options.body); } - function fromQmlXX(message) { - print('fixme got fromQml', JSON.stringify(message)); - /*var response = {id: message.id, jsonrpc: "2.0"}; + function fromQml(message) { + var response = {id: message.id, jsonrpc: "2.0"}; switch (message.method) { case 'request': request(message.params, function (error, data) { @@ -92,7 +91,7 @@ default: response.error = {message: 'Unrecognized message', data: message}; } - tablet.sendToQml(response);*/ + tablet.sendToQml(response); } function messagesWaiting(isWaiting) { button.editProperties({ @@ -102,21 +101,18 @@ } var hasEventBridge = false; function wireEventBridge(on) { - print('fixme wireEventBridge', on, hasEventBridge); if (on) { if (!hasEventBridge) { - tablet.fromQml.connect(fromQmlXX); - print('fixme wired', tablet); + tablet.fromQml.connect(fromQml); hasEventBridge = true; } } else { if (hasEventBridge) { - tablet.fromQml.disconnect(fromQmlXX); + tablet.fromQml.disconnect(fromQml); hasEventBridge = false; } } } - wireEventBridge(true); function onClicked() { if (onGotoScreen) { @@ -169,7 +165,6 @@ return; } var didNotify = false; - print('fixme poll', url, JSON.stringify(data.user_stories)); data.user_stories.forEach(function (story) { if (stories[story.id]) { // already seen return; From 2b43a1989f629662bb09f83b1644b7b88c543eab Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 28 Apr 2017 16:40:48 -0700 Subject: [PATCH 22/47] Moving face and eye trackers out of interface --- interface/CMakeLists.txt | 2 +- .../resources/icons/tablet-icons/goto-msg.svg | 36 +++++++++---------- interface/src/Application.cpp | 2 +- interface/src/avatar/Head.cpp | 2 +- interface/src/avatar/MyAvatar.cpp | 2 +- interface/src/devices/DdeFaceTracker.h | 2 +- interface/src/devices/Leapmotion.h | 2 +- .../ControllerScriptingInterface.cpp | 2 +- interface/src/ui/AvatarInputs.cpp | 2 +- libraries/trackers/CMakeLists.txt | 6 ++++ .../trackers/src/trackers}/DeviceTracker.cpp | 0 .../trackers/src/trackers}/DeviceTracker.h | 0 .../trackers/src/trackers}/EyeTracker.cpp | 0 .../trackers/src/trackers}/EyeTracker.h | 0 .../trackers/src/trackers}/FaceTracker.cpp | 0 .../trackers/src/trackers}/FaceTracker.h | 0 .../trackers/src/trackers}/Logging.cpp | 0 .../trackers/src/trackers}/Logging.h | 0 .../trackers/src/trackers}/MotionTracker.cpp | 0 .../trackers/src/trackers}/MotionTracker.h | 0 20 files changed, 32 insertions(+), 26 deletions(-) create mode 100644 libraries/trackers/CMakeLists.txt rename {interface/src/devices => libraries/trackers/src/trackers}/DeviceTracker.cpp (100%) rename {interface/src/devices => libraries/trackers/src/trackers}/DeviceTracker.h (100%) rename {interface/src/devices => libraries/trackers/src/trackers}/EyeTracker.cpp (100%) rename {interface/src/devices => libraries/trackers/src/trackers}/EyeTracker.h (100%) rename {interface/src/devices => libraries/trackers/src/trackers}/FaceTracker.cpp (100%) rename {interface/src/devices => libraries/trackers/src/trackers}/FaceTracker.h (100%) rename {interface/src/devices => libraries/trackers/src/trackers}/Logging.cpp (100%) rename {interface/src/devices => libraries/trackers/src/trackers}/Logging.h (100%) rename {interface/src/devices => libraries/trackers/src/trackers}/MotionTracker.cpp (100%) rename {interface/src/devices => libraries/trackers/src/trackers}/MotionTracker.h (100%) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index e3d01826b3..d7e4b1ae7c 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -194,7 +194,7 @@ link_hifi_libraries( recording fbx networking model-networking entities avatars audio audio-client animation script-engine physics render-utils entities-renderer avatars-renderer ui auto-updater - controllers plugins image + controllers plugins image trackers ui-plugins display-plugins input-plugins ${NON_ANDROID_LIBRARIES} ) diff --git a/interface/resources/icons/tablet-icons/goto-msg.svg b/interface/resources/icons/tablet-icons/goto-msg.svg index ef905b6066..9b576ab1bf 100644 --- a/interface/resources/icons/tablet-icons/goto-msg.svg +++ b/interface/resources/icons/tablet-icons/goto-msg.svg @@ -1,18 +1,18 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 306fa99852..1d90c523b5 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -128,6 +128,7 @@ #include #include #include +#include #include "AudioClient.h" @@ -136,7 +137,6 @@ #include "avatar/ScriptAvatar.h" #include "CrashHandler.h" #include "devices/DdeFaceTracker.h" -#include "devices/EyeTracker.h" #include "devices/Leapmotion.h" #include "DiscoverabilityManager.h" #include "GLCanvas.h" diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index f2eeba9d60..16e5776d87 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -13,6 +13,7 @@ #include #include +#include #include "Application.h" #include "Avatar.h" @@ -22,7 +23,6 @@ #include "Menu.h" #include "Util.h" #include "devices/DdeFaceTracker.h" -#include "devices/EyeTracker.h" #include using namespace std; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index b8c7656839..f2726930b5 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -41,7 +41,7 @@ #include #include #include -#include +#include #include "Application.h" #include "AvatarManager.h" diff --git a/interface/src/devices/DdeFaceTracker.h b/interface/src/devices/DdeFaceTracker.h index 973c3b224e..f125dfc3cf 100644 --- a/interface/src/devices/DdeFaceTracker.h +++ b/interface/src/devices/DdeFaceTracker.h @@ -22,7 +22,7 @@ #include #include -#include "FaceTracker.h" +#include class DdeFaceTracker : public FaceTracker, public Dependency { Q_OBJECT diff --git a/interface/src/devices/Leapmotion.h b/interface/src/devices/Leapmotion.h index d7981a65e8..6ecec8ccf9 100644 --- a/interface/src/devices/Leapmotion.h +++ b/interface/src/devices/Leapmotion.h @@ -14,7 +14,7 @@ #include -#include "MotionTracker.h" +#include #ifdef HAVE_LEAPMOTION #include diff --git a/interface/src/scripting/ControllerScriptingInterface.cpp b/interface/src/scripting/ControllerScriptingInterface.cpp index 0d0c2ef668..f3ec3cd79d 100644 --- a/interface/src/scripting/ControllerScriptingInterface.cpp +++ b/interface/src/scripting/ControllerScriptingInterface.cpp @@ -17,7 +17,7 @@ #include #include "Application.h" -#include "devices/MotionTracker.h" +#include void ControllerScriptingInterface::handleMetaEvent(HFMetaEvent* event) { if (event->type() == HFActionEvent::startType()) { diff --git a/interface/src/ui/AvatarInputs.cpp b/interface/src/ui/AvatarInputs.cpp index 341915e57f..2b715eac9d 100644 --- a/interface/src/ui/AvatarInputs.cpp +++ b/interface/src/ui/AvatarInputs.cpp @@ -11,9 +11,9 @@ #include #include +#include #include "Application.h" -#include "devices/FaceTracker.h" #include "Menu.h" HIFI_QML_DEF(AvatarInputs) diff --git a/libraries/trackers/CMakeLists.txt b/libraries/trackers/CMakeLists.txt new file mode 100644 index 0000000000..0999a45b59 --- /dev/null +++ b/libraries/trackers/CMakeLists.txt @@ -0,0 +1,6 @@ +set(TARGET_NAME trackers) +setup_hifi_library() +GroupSources("src") +link_hifi_libraries(shared) + +target_bullet() diff --git a/interface/src/devices/DeviceTracker.cpp b/libraries/trackers/src/trackers/DeviceTracker.cpp similarity index 100% rename from interface/src/devices/DeviceTracker.cpp rename to libraries/trackers/src/trackers/DeviceTracker.cpp diff --git a/interface/src/devices/DeviceTracker.h b/libraries/trackers/src/trackers/DeviceTracker.h similarity index 100% rename from interface/src/devices/DeviceTracker.h rename to libraries/trackers/src/trackers/DeviceTracker.h diff --git a/interface/src/devices/EyeTracker.cpp b/libraries/trackers/src/trackers/EyeTracker.cpp similarity index 100% rename from interface/src/devices/EyeTracker.cpp rename to libraries/trackers/src/trackers/EyeTracker.cpp diff --git a/interface/src/devices/EyeTracker.h b/libraries/trackers/src/trackers/EyeTracker.h similarity index 100% rename from interface/src/devices/EyeTracker.h rename to libraries/trackers/src/trackers/EyeTracker.h diff --git a/interface/src/devices/FaceTracker.cpp b/libraries/trackers/src/trackers/FaceTracker.cpp similarity index 100% rename from interface/src/devices/FaceTracker.cpp rename to libraries/trackers/src/trackers/FaceTracker.cpp diff --git a/interface/src/devices/FaceTracker.h b/libraries/trackers/src/trackers/FaceTracker.h similarity index 100% rename from interface/src/devices/FaceTracker.h rename to libraries/trackers/src/trackers/FaceTracker.h diff --git a/interface/src/devices/Logging.cpp b/libraries/trackers/src/trackers/Logging.cpp similarity index 100% rename from interface/src/devices/Logging.cpp rename to libraries/trackers/src/trackers/Logging.cpp diff --git a/interface/src/devices/Logging.h b/libraries/trackers/src/trackers/Logging.h similarity index 100% rename from interface/src/devices/Logging.h rename to libraries/trackers/src/trackers/Logging.h diff --git a/interface/src/devices/MotionTracker.cpp b/libraries/trackers/src/trackers/MotionTracker.cpp similarity index 100% rename from interface/src/devices/MotionTracker.cpp rename to libraries/trackers/src/trackers/MotionTracker.cpp diff --git a/interface/src/devices/MotionTracker.h b/libraries/trackers/src/trackers/MotionTracker.h similarity index 100% rename from interface/src/devices/MotionTracker.h rename to libraries/trackers/src/trackers/MotionTracker.h From 84ac863a327e28342b4cae01e257643295f710d5 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Sat, 29 Apr 2017 13:58:20 -0700 Subject: [PATCH 23/47] lint --- scripts/system/makeUserConnection.js | 422 ++++++++++++++------------- 1 file changed, 212 insertions(+), 210 deletions(-) diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index 0ffea0c568..eda461f541 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -1,4 +1,6 @@ "use strict"; +/*jslint vars:true, plusplus:true, forin:true*/ +/*global Window, Script, Controller, MyAvatar, AvatarList, Entities, Messages, Audio, SoundCache, Account, UserActivityLogger, Vec3, Quat, XMLHttpRequest, location, print*/ // // makeUserConnection.js // scripts/system @@ -9,7 +11,7 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -(function() { // BEGIN LOCAL_SCOPE +(function () { // BEGIN LOCAL_SCOPE var LABEL = "makeUserConnection"; var MAX_AVATAR_DISTANCE = 0.2; // m @@ -27,7 +29,7 @@ var MAKING_CONNECTION_TIMEOUT = 800; // ms var CONNECTING_TIME = 1600; // ms var PARTICLE_RADIUS = 0.15; // m - var PARTICLE_ANGLE_INCREMENT = 360/45; // 1hz + var PARTICLE_ANGLE_INCREMENT = 360 / 45; // 1hz var HANDSHAKE_SOUND_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/davidkelly/production/audio/4beat_sweep.wav"; var SUCCESSFUL_HANDSHAKE_SOUND_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/davidkelly/production/audio/3rdbeat_success_bell.wav"; var PREFERRER_HAND_JOINT_POSTFIX_ORDER = ['Middle1', 'Index1', '']; @@ -39,7 +41,7 @@ var PARTICLE_EFFECT_PROPS = { "alpha": 0.8, "azimuthFinish": Math.PI, - "azimuthStart": -1*Math.PI, + "azimuthStart": -1 * Math.PI, "emitRate": 500, "emitSpeed": 0.0, "emitterShouldTrail": 1, @@ -56,10 +58,10 @@ "color": {"red": 255, "green": 255, "blue": 255}, "colorFinish": {"red": 0, "green": 164, "blue": 255}, "colorStart": {"red": 255, "green": 255, "blue": 255}, - "emitOrientation": {"w": -0.71, "x":0.0, "y":0.0, "z": 0.71}, + "emitOrientation": {"w": -0.71, "x": 0.0, "y": 0.0, "z": 0.71}, "emitAcceleration": {"x": 0.0, "y": 0.0, "z": 0.0}, "accelerationSpread": {"x": 0.0, "y": 0.0, "z": 0.0}, - "dimensions": {"x":0.05, "y": 0.05, "z": 0.05}, + "dimensions": {"x": 0.05, "y": 0.05, "z": 0.05}, "type": "ParticleEffect" }; var MAKING_CONNECTION_PARTICLE_PROPS = { @@ -68,7 +70,7 @@ "alphaSpread": 0, "alphaFinish": 0, "azimuthFinish": Math.PI, - "azimuthStart": -1*Math.PI, + "azimuthStart": -1 * Math.PI, "emitRate": 2000, "emitSpeed": 0.0, "emitterShouldTrail": 1, @@ -86,14 +88,14 @@ "color": {"red": 200, "green": 170, "blue": 255}, "colorFinish": {"red": 0, "green": 134, "blue": 255}, "colorStart": {"red": 185, "green": 222, "blue": 255}, - "emitOrientation": {"w": -0.71, "x":0.0, "y":0.0, "z": 0.71}, + "emitOrientation": {"w": -0.71, "x": 0.0, "y": 0.0, "z": 0.71}, "emitAcceleration": {"x": 0.0, "y": 0.0, "z": 0.0}, "accelerationSpread": {"x": 0.0, "y": 0.0, "z": 0.0}, - "dimensions": {"x":0.05, "y": 0.05, "z": 0.05}, + "dimensions": {"x": 0.05, "y": 0.05, "z": 0.05}, "type": "ParticleEffect" }; - var currentHand = undefined; + var currentHand; var currentHandJointIndex = -1; var state = STATES.INACTIVE; var connectingInterval; @@ -183,7 +185,8 @@ function handToString(hand) { if (hand === Controller.Standard.RightHand) { return "RightHand"; - } else if (hand === Controller.Standard.LeftHand) { + } + if (hand === Controller.Standard.LeftHand) { return "LeftHand"; } debug("handToString called without valid hand! value: ", hand); @@ -193,7 +196,8 @@ function stringToHand(hand) { if (hand === "RightHand") { return Controller.Standard.RightHand; - } else if (hand === "LeftHand") { + } + if (hand === "LeftHand") { return Controller.Standard.LeftHand; } debug("stringToHand called with bad hand string:", hand); @@ -203,7 +207,8 @@ function handToHaptic(hand) { if (hand === Controller.Standard.RightHand) { return 1; - } else if (hand === Controller.Standard.LeftHand) { + } + if (hand === Controller.Standard.LeftHand) { return 0; } debug("handToHaptic called without a valid hand!"); @@ -231,11 +236,11 @@ // This returns the ideal hand joint index for the avatar. // [hand]middle1 -> [hand]index1 -> [hand] function getIdealHandJointIndex(avatar, hand) { - debug("got hand " + hand + " for avatar " + avatar.sessionUUID); - var handString = handToString(hand); - for (var i = 0; i < PREFERRER_HAND_JOINT_POSTFIX_ORDER.length; i++) { - var jointName = handString + PREFERRER_HAND_JOINT_POSTFIX_ORDER[i]; - var jointIndex = avatar.getJointIndex(jointName); + debug("get hand " + hand + " for avatar " + avatar.sessionUUID); + var suffixIndex, jointName, jointIndex, handString = handToString(hand); + for (suffixIndex = 0; suffixIndex < PREFERRER_HAND_JOINT_POSTFIX_ORDER.length; suffixIndex++) { + jointName = handString + PREFERRER_HAND_JOINT_POSTFIX_ORDER[suffixIndex]; + jointIndex = avatar.getJointIndex(jointName); if (jointIndex !== -1) { debug('found joint ' + jointName + ' (' + jointIndex + ')'); return jointIndex; @@ -255,7 +260,7 @@ return avatar.getJointPosition(handJointIndex); } - function shakeHandsAnimation(animationProperties) { + function shakeHandsAnimation() { // all we are doing here is moving the right hand to a spot // that is in front of and a bit above the hips. Basing how // far in front as scaling with the avatar's height (say hips @@ -325,58 +330,58 @@ } switch (state) { - case STATES.WAITING: - // no visualization while waiting - deleteParticleEffect(); - deleteMakeConnectionParticleEffect(); - stopHandshakeSound(); - break; - case STATES.CONNECTING: - var particleProps = {}; - // put the position between the 2 hands, if we have a connectingId. This - // helps define the plane in which the particles move. - positionFractionallyTowards(myHandPosition, otherHand, 0.5); - // now manage the rest of the entity - if (!particleEffect) { - particleRotationAngle = 0.0; - particleEmitRate = 500; - particleProps = PARTICLE_EFFECT_PROPS; - particleProps.isEmitting = 0; - particleProps.position = calcParticlePos(myHandPosition, otherHand, otherOrientation); - particleProps.parentID = MyAvatar.sessionUUID; - particleEffect = Entities.addEntity(particleProps, true); - } else { - particleProps.position = calcParticlePos(myHandPosition, otherHand, otherOrientation); - particleProps.isEmitting = 1; - Entities.editEntity(particleEffect, particleProps); - } - if (!makingConnectionParticleEffect) { - var props = MAKING_CONNECTION_PARTICLE_PROPS; - props.parentID = MyAvatar.sessionUUID; - makingConnectionEmitRate = 2000; - props.emitRate = makingConnectionEmitRate; - props.position = myHandPosition; - makingConnectionParticleEffect = Entities.addEntity(props, true); - } else { - makingConnectionEmitRate *= 0.5; - Entities.editEntity(makingConnectionParticleEffect, { - emitRate: makingConnectionEmitRate, - position: myHandPosition, - isEmitting: true - }); - } - break; - case STATES.MAKING_CONNECTION: - particleEmitRate = Math.max(50, particleEmitRate * 0.5); - Entities.editEntity(makingConnectionParticleEffect, {emitRate: 0, isEmitting: 0, position: myHandPosition}); - Entities.editEntity(particleEffect, { - position: calcParticlePos(myHandPosition, otherHand, otherOrientation), - emitRate: particleEmitRate + case STATES.WAITING: + // no visualization while waiting + deleteParticleEffect(); + deleteMakeConnectionParticleEffect(); + stopHandshakeSound(); + break; + case STATES.CONNECTING: + var particleProps = {}; + // put the position between the 2 hands, if we have a connectingId. This + // helps define the plane in which the particles move. + positionFractionallyTowards(myHandPosition, otherHand, 0.5); + // now manage the rest of the entity + if (!particleEffect) { + particleRotationAngle = 0.0; + particleEmitRate = 500; + particleProps = PARTICLE_EFFECT_PROPS; + particleProps.isEmitting = 0; + particleProps.position = calcParticlePos(myHandPosition, otherHand, otherOrientation); + particleProps.parentID = MyAvatar.sessionUUID; + particleEffect = Entities.addEntity(particleProps, true); + } else { + particleProps.position = calcParticlePos(myHandPosition, otherHand, otherOrientation); + particleProps.isEmitting = 1; + Entities.editEntity(particleEffect, particleProps); + } + if (!makingConnectionParticleEffect) { + var props = MAKING_CONNECTION_PARTICLE_PROPS; + props.parentID = MyAvatar.sessionUUID; + makingConnectionEmitRate = 2000; + props.emitRate = makingConnectionEmitRate; + props.position = myHandPosition; + makingConnectionParticleEffect = Entities.addEntity(props, true); + } else { + makingConnectionEmitRate *= 0.5; + Entities.editEntity(makingConnectionParticleEffect, { + emitRate: makingConnectionEmitRate, + position: myHandPosition, + isEmitting: true }); - break; - default: - debug("unexpected state", state); - break; + } + break; + case STATES.MAKING_CONNECTION: + particleEmitRate = Math.max(50, particleEmitRate * 0.5); + Entities.editEntity(makingConnectionParticleEffect, {emitRate: 0, isEmitting: 0, position: myHandPosition}); + Entities.editEntity(particleEffect, { + position: calcParticlePos(myHandPosition, otherHand, otherOrientation), + emitRate: particleEmitRate + }); + break; + default: + debug("unexpected state", state); + break; } } @@ -412,8 +417,42 @@ }); return nearestAvatar; } + function messageSend(message) { + Messages.sendMessage(MESSAGE_CHANNEL, JSON.stringify(message)); + } + function lookForWaitingAvatar() { + // we started with nobody close enough, but maybe I've moved + // or they did. Note that 2 people doing this race, so stop + // as soon as you have a connectingId (which means you got their + // message before noticing they were in range in this loop) + // just in case we re-enter before stopping + stopWaiting(); + debug("started looking for waiting avatars"); + waitingInterval = Script.setInterval(function () { + if (state === STATES.WAITING && !connectingId) { + // find the closest in-range avatar, and send connection request + var nearestAvatar = findNearestWaitingAvatar(); + if (nearestAvatar.avatar) { + connectingId = nearestAvatar.avatar; + connectingHandString = handToString(nearestAvatar.hand); + debug("sending connectionRequest to", connectingId); + messageSend({ + key: "connectionRequest", + id: connectingId, + hand: handToString(currentHand) + }); + } + } else { + // something happened, stop looking for avatars to connect + stopWaiting(); + debug("stopped looking for waiting avatars"); + } + }, WAITING_INTERVAL); + } + + var pollCount = 0, requestUrl = location.metaverseServerUrl + '/api/v1/user/connection_request'; // As currently implemented, we select the closest waiting avatar (if close enough) and send // them a connectionRequest. If nobody is close enough we send a waiting message, and wait for a // connectionRequest. If the 2 people who want to connect are both somewhat out of range when they @@ -510,9 +549,8 @@ debug("updateTriggers called - gripping", handToString(hand)); if (state !== STATES.INACTIVE) { return; - } else { - startHandshake(fromKeyboard); } + startHandshake(fromKeyboard); } else { // TODO: should we end handshake even when inactive? Ponder debug("updateTriggers called -- no longer gripping", handToString(hand)); @@ -524,47 +562,12 @@ } } - function messageSend(message) { - Messages.sendMessage(MESSAGE_CHANNEL, JSON.stringify(message)); - } - - function lookForWaitingAvatar() { - // we started with nobody close enough, but maybe I've moved - // or they did. Note that 2 people doing this race, so stop - // as soon as you have a connectingId (which means you got their - // message before noticing they were in range in this loop) - - // just in case we re-enter before stopping - stopWaiting(); - debug("started looking for waiting avatars"); - waitingInterval = Script.setInterval(function () { - if (state === STATES.WAITING && !connectingId) { - // find the closest in-range avatar, and send connection request - var nearestAvatar = findNearestWaitingAvatar(); - if (nearestAvatar.avatar) { - connectingId = nearestAvatar.avatar; - connectingHandString = handToString(nearestAvatar.hand); - debug("sending connectionRequest to", connectingId); - messageSend({ - key: "connectionRequest", - id: connectingId, - hand: handToString(currentHand) - }); - } - } else { - // something happened, stop looking for avatars to connect - stopWaiting(); - debug("stopped looking for waiting avatars"); - } - }, WAITING_INTERVAL); - } - /* There is a mini-state machine after entering STATES.makingConnection. We make a request (which might immediately succeed, fail, or neither. If we immediately fail, we tell the user. Otherwise, we wait MAKING_CONNECTION_TIMEOUT. At that time, we poll until success or fail. */ - var result, requestBody, pollCount = 0, requestUrl = location.metaverseServerUrl + '/api/v1/user/connection_request'; + var result, requestBody; function connectionRequestCompleted() { // Final result is in. Do effects. if (result.status === 'success') { // set earlier if (!successfulHandshakeInjector) { @@ -580,10 +583,15 @@ handToHaptic(currentHand)); // don't change state (so animation continues while gripped) // but do send a notification, by calling the slot that emits the signal for it - Window.makeConnection(true, result.connection.new_connection ? - "You and " + result.connection.username + " are now connected!" : result.connection.username); - UserActivityLogger.makeUserConnection(connectingId, true, result.connection.new_connection ? - "new connection" : "already connected"); + Window.makeConnection(true, + result.connection.new_connection ? + "You and " + result.connection.username + " are now connected!" : + result.connection.username); + UserActivityLogger.makeUserConnection(connectingId, + true, + result.connection.new_connection ? + "new connection" : + "already connected"); return; } // failed endHandshake(); @@ -658,13 +666,16 @@ // This will immediately set response if successful (e.g., the other guy got his request in first), // or immediate failure, and will otherwise poll (using the requestBody we just set). - request({ // + request({ uri: requestUrl, method: 'POST', json: true, body: {'user_connection_request': requestBody} }, handleConnectionResponseAndMaybeRepeat); } + function getConnectingHandJointIndex() { + return AvatarList.getAvatarIdentifiers().indexOf(connectingId) !== -1 ? getIdealHandJointIndex(AvatarList.getAvatar(connectingId), stringToHand(connectingHandString)) : -1; + } // we change states, start the connectionInterval where we check // to be sure the hand is still close enough. If not, we terminate @@ -676,8 +687,7 @@ // do we need to do this? connectingId = id; connectingHandString = hand; - connectingHandJointIndex = AvatarList.getAvatarIdentifiers().indexOf(connectingId) !== -1 ? - getIdealHandJointIndex(AvatarList.getAvatar(connectingId), stringToHand(connectingHandString)) : -1; + connectingHandJointIndex = getConnectingHandJointIndex(); state = STATES.CONNECTING; // play sound @@ -714,7 +724,7 @@ key: "done" }); startHandshake(); - } else if (count > CONNECTING_TIME/CONNECTING_INTERVAL) { + } else if (count > CONNECTING_TIME / CONNECTING_INTERVAL) { debug("made connection with " + id); makeConnection(id); stopConnecting(); @@ -753,127 +763,119 @@ debug(e); } switch (message.key) { - case "waiting": - // add this guy to waiting object. Any other message from this person will - // remove it from the list - waitingList[senderID] = message.hand; - break; - case "connectionRequest": - delete waitingList[senderID]; - if (state === STATES.WAITING && message.id === MyAvatar.sessionUUID && - (!connectingId || connectingId === senderID)) { - // you were waiting for a connection request, so send the ack. Or, you and the other - // guy raced and both send connectionRequests. Handle that too + case "waiting": + // add this guy to waiting object. Any other message from this person will + // remove it from the list + waitingList[senderID] = message.hand; + break; + case "connectionRequest": + delete waitingList[senderID]; + if (state === STATES.WAITING && message.id === MyAvatar.sessionUUID && (!connectingId || connectingId === senderID)) { + // you were waiting for a connection request, so send the ack. Or, you and the other + // guy raced and both send connectionRequests. Handle that too + connectingId = senderID; + connectingHandString = message.hand; + connectingHandJointIndex = getConnectingHandJointIndex(); + messageSend({ + key: "connectionAck", + id: senderID, + hand: handToString(currentHand) + }); + } else if (state === STATES.WAITING && connectingId === senderID) { + // the person you are trying to connect sent a request to someone else. See the + // if statement above. So, don't cry, just start the handshake over again + startHandshake(); + } + break; + case "connectionAck": + delete waitingList[senderID]; + if (state === STATES.WAITING && (!connectingId || connectingId === senderID)) { + if (message.id === MyAvatar.sessionUUID) { + // start connecting... connectingId = senderID; connectingHandString = message.hand; - connectingHandJointIndex = AvatarList.getAvatarIdentifiers().indexOf(connectingId) !== -1 ? - getIdealHandJointIndex(AvatarList.getAvatar(connectingId), stringToHand(connectingHandString)) : -1; - messageSend({ - key: "connectionAck", - id: senderID, - hand: handToString(currentHand) - }); - } else if (state === STATES.WAITING && connectingId === senderID) { - // the person you are trying to connect sent a request to someone else. See the - // if statement above. So, don't cry, just start the handshake over again + connectingHandJointIndex = getConnectingHandJointIndex(); + stopWaiting(); + startConnecting(senderID, connectingHandString); + } else if (connectingId) { + // this is for someone else (we lost race in connectionRequest), + // so lets start over startHandshake(); } - break; - case "connectionAck": - delete waitingList[senderID]; - if (state === STATES.WAITING && (!connectingId || connectingId === senderID)) { - if (message.id === MyAvatar.sessionUUID) { - // start connecting... - connectingId = senderID; - connectingHandString = message.hand; - connectingHandJointIndex = AvatarList.getAvatarIdentifiers().indexOf(connectingId) !== -1 ? - getIdealHandJointIndex(AvatarList.getAvatar(connectingId), stringToHand(connectingHandString)) : -1; - stopWaiting(); - startConnecting(senderID, connectingHandString); - } else if (connectingId) { - // this is for someone else (we lost race in connectionRequest), - // so lets start over + } + // TODO: check to see if we are waiting for this but the person we are connecting sent it to + // someone else, and try again + break; + case "connecting": + delete waitingList[senderID]; + if (state === STATES.WAITING && senderID === connectingId) { + // temporary logging + if (connectingHandString !== message.hand) { + debug("connecting hand", connectingHandString, "not same as connecting hand in message", message.hand); + } + connectingHandString = message.hand; + if (message.id !== MyAvatar.sessionUUID) { + // the person we were trying to connect is connecting to someone else + // so try again + startHandshake(); + break; + } + startConnecting(senderID, message.hand); + } + break; + case "done": + delete waitingList[senderID]; + if (state === STATES.CONNECTING && connectingId === senderID) { + // if they are done, and didn't connect us, terminate our + // connecting + if (message.connectionId !== MyAvatar.sessionUUID) { + stopConnecting(); + // now just call startHandshake. Should be ok to do so without a + // value for isKeyboard, as we should not change the animation + // state anyways (if any) + startHandshake(); + } + } else { + // if waiting or inactive, lets clear the connecting id. If in makingConnection, + // do nothing + if (state !== STATES.MAKING_CONNECTION && connectingId === senderID) { + connectingId = undefined; + connectingHandString = undefined; + connectingHandJointIndex = -1; + if (state !== STATES.INACTIVE) { startHandshake(); } } - // TODO: check to see if we are waiting for this but the person we are connecting sent it to - // someone else, and try again - break; - case "connecting": - delete waitingList[senderID]; - if (state === STATES.WAITING && senderID === connectingId) { - // temporary logging - if (connectingHandString !== message.hand) { - debug("connecting hand", connectingHandString, "not same as connecting hand in message", message.hand); - } - connectingHandString = message.hand; - if (message.id !== MyAvatar.sessionUUID) { - // the person we were trying to connect is connecting to someone else - // so try again - startHandshake(); - break; - } - startConnecting(senderID, message.hand); - } - break; - case "done": - delete waitingList[senderID]; - if (state === STATES.CONNECTING && connectingId === senderID) { - // if they are done, and didn't connect us, terminate our - // connecting - if (message.connectionId !== MyAvatar.sessionUUID) { - stopConnecting(); - // now just call startHandshake. Should be ok to do so without a - // value for isKeyboard, as we should not change the animation - // state anyways (if any) - startHandshake(); - } - } else { - // if waiting or inactive, lets clear the connecting id. If in makingConnection, - // do nothing - if (state !== STATES.MAKING_CONNECTION && connectingId === senderID) { - connectingId = undefined; - connectingHandString = undefined; - connectingHandJointIndex = -1; - if (state !== STATES.INACTIVE) { - startHandshake(); - } - } - } - break; - default: - debug("unknown message", message); - break; + } + break; + default: + debug("unknown message", message); + break; } } Messages.subscribe(MESSAGE_CHANNEL); Messages.messageReceived.connect(messageHandler); - function makeGripHandler(hand, animate) { // determine if we are gripping or un-gripping if (animate) { - return function(value) { + return function (value) { updateTriggers(value, true, hand); }; - - } else { - return function (value) { - updateTriggers(value, false, hand); - }; } + return function (value) { + updateTriggers(value, false, hand); + }; } function keyPressEvent(event) { - if ((event.text === "x") && !event.isAutoRepeat && !event.isShifted && !event.isMeta && !event.isControl && - !event.isAlt) { + if ((event.text === "x") && !event.isAutoRepeat && !event.isShifted && !event.isMeta && !event.isControl && !event.isAlt) { updateTriggers(1.0, true, Controller.Standard.RightHand); } } function keyReleaseEvent(event) { - if ((event.text === "x") && !event.isAutoRepeat && !event.isShifted && !event.isMeta && !event.isControl && - !event.isAlt) { + if ((event.text === "x") && !event.isAutoRepeat && !event.isShifted && !event.isMeta && !event.isControl && !event.isAlt) { updateTriggers(0.0, true, Controller.Standard.RightHand); } } From 274cc5b5558e9c03a6fe17f9106b8674ef517249 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Sat, 29 Apr 2017 14:29:43 -0700 Subject: [PATCH 24/47] unique message for exceeding poll limit --- scripts/system/makeUserConnection.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index eda461f541..e3785e9d77 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -610,7 +610,7 @@ debug(response, 'pollCount', pollCount); if (pollCount++ >= POLL_LIMIT) { // server will expire, but let's not wait that long. debug('POLL LIMIT REACHED; TIMEOUT: expired message generated by CLIENT'); - result = {status: 'error', connection: 'expired'}; + result = {status: 'error', connection: 'no-partner-found'}; connectionRequestCompleted(); } else { // poll Script.setTimeout(function () { @@ -640,8 +640,6 @@ } } - // this should be where we make the appropriate connection call. For now just make the - // visualization change. function makeConnection(id) { // send done to let the connection know you have made connection. messageSend({ @@ -651,8 +649,7 @@ state = STATES.MAKING_CONNECTION; - // continue the haptic background until the timeout fires. When we make calls, we will have an interval - // probably, in which we do this. + // continue the haptic background until the timeout fires. Controller.triggerHapticPulse(HAPTIC_DATA.background.strength, MAKING_CONNECTION_TIMEOUT, handToHaptic(currentHand)); requestBody = {'node_id': cleanId(MyAvatar.sessionUUID), 'proposed_node_id': cleanId(id)}; // for use when repeating From d7a2e571a72a18066bfd000e10ab7648c7c994e2 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Sat, 29 Apr 2017 14:39:35 -0700 Subject: [PATCH 25/47] simplify animation load and code --- scripts/system/makeUserConnection.js | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index e3785e9d77..57a9764e6e 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -260,7 +260,20 @@ return avatar.getJointPosition(handJointIndex); } + var animationData = {}; function shakeHandsAnimation() { + return animationData; + } + function endHandshakeAnimation() { + if (animHandlerId) { + debug("removing animation"); + animHandlerId = MyAvatar.removeAnimationStateHandler(animHandlerId); + } + } + function startHandshakeAnimation() { + endHandshakeAnimation(); // just in case order of press/unpress is broken + debug("adding animation"); + // all we are doing here is moving the right hand to a spot // that is in front of and a bit above the hips. Basing how // far in front as scaling with the avatar's height (say hips @@ -273,7 +286,8 @@ } result.rightHandPosition = Vec3.multiply(offset, {x: -0.25, y: 0.8, z: 1.3}); result.rightHandRotation = Quat.fromPitchYawRollDegrees(90, 0, 90); - return result; + + animHandlerId = MyAvatar.addAnimationStateHandler(shakeHandsAnimation, []); } function positionFractionallyTowards(posA, posB, frac) { @@ -460,12 +474,7 @@ // waiting message. Either way, they will start connecting eachother at that point. function startHandshake(fromKeyboard) { if (fromKeyboard) { - debug("adding animation"); - // just in case order of press/unpress is broken - if (animHandlerId) { - animHandlerId = MyAvatar.removeAnimationStateHandler(animHandlerId); - } - animHandlerId = MyAvatar.addAnimationStateHandler(shakeHandsAnimation, []); + startHandshakeAnimation(); } debug("starting handshake for", currentHand); pollCount = 0; @@ -525,10 +534,7 @@ key: "done" }); - if (animHandlerId) { - debug("removing animation"); - MyAvatar.removeAnimationStateHandler(animHandlerId); - } + endHandshakeAnimation(); // No-op if we were successful, but this way we ensure that failures and abandoned handshakes don't leave us // in a weird state. request({uri: requestUrl, method: 'DELETE'}, debug); From 9c83e21e1780d0dc0f02cbe047b6acc642492ce0 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Sat, 29 Apr 2017 15:50:04 -0700 Subject: [PATCH 26/47] fix that --- scripts/system/makeUserConnection.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index 57a9764e6e..c2d86e71fd 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -261,6 +261,19 @@ } var animationData = {}; + function updateAnimationData() { + // all we are doing here is moving the right hand to a spot + // that is in front of and a bit above the hips. Basing how + // far in front as scaling with the avatar's height (say hips + // to head distance) + var headIndex = MyAvatar.getJointIndex("Head"); + var offset = 0.5; // default distance of hand in front of you + if (headIndex) { + offset = 0.8 * MyAvatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y; + } + animationData.rightHandPosition = Vec3.multiply(offset, {x: -0.25, y: 0.8, z: 1.3}); + animationData.rightHandRotation = Quat.fromPitchYawRollDegrees(90, 0, 90); + } function shakeHandsAnimation() { return animationData; } @@ -273,20 +286,7 @@ function startHandshakeAnimation() { endHandshakeAnimation(); // just in case order of press/unpress is broken debug("adding animation"); - - // all we are doing here is moving the right hand to a spot - // that is in front of and a bit above the hips. Basing how - // far in front as scaling with the avatar's height (say hips - // to head distance) - var headIndex = MyAvatar.getJointIndex("Head"); - var offset = 0.5; // default distance of hand in front of you - var result = {}; - if (headIndex) { - offset = 0.8 * MyAvatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y; - } - result.rightHandPosition = Vec3.multiply(offset, {x: -0.25, y: 0.8, z: 1.3}); - result.rightHandRotation = Quat.fromPitchYawRollDegrees(90, 0, 90); - + updateAnimationData(); animHandlerId = MyAvatar.addAnimationStateHandler(shakeHandsAnimation, []); } From 819f02a988a025c7ae33cf18d8f265c4352c0f11 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sun, 30 Apr 2017 11:40:28 +1200 Subject: [PATCH 27/47] Fix keyboard not raising for Facebook username field --- interface/resources/html/raiseAndLowerKeyboard.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/html/raiseAndLowerKeyboard.js b/interface/resources/html/raiseAndLowerKeyboard.js index 63e016c5d4..27ead23124 100644 --- a/interface/resources/html/raiseAndLowerKeyboard.js +++ b/interface/resources/html/raiseAndLowerKeyboard.js @@ -19,7 +19,7 @@ function shouldRaiseKeyboard() { var nodeName = document.activeElement.nodeName; var nodeType = document.activeElement.type; - if (nodeName === "INPUT" && (nodeType === "text" || nodeType === "number" || nodeType === "password") + if (nodeName === "INPUT" && ["email", "number", "password", "tel", "text", "url"].indexOf(nodeType) !== -1 || document.activeElement.nodeName === "TEXTAREA") { return true; } else { From 18dbe3568c8012f649c7ba8ed01d3ff0a3a47222 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Sat, 29 Apr 2017 18:25:33 -0700 Subject: [PATCH 28/47] clarify names, particularly hand vs handString --- scripts/system/makeUserConnection.js | 86 ++++++++++++++-------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index c2d86e71fd..5540f0c122 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -24,6 +24,7 @@ MAKING_CONNECTION: 3 }; var STATE_STRINGS = ["inactive", "waiting", "connecting", "makingConnection"]; + var HAND_STRING_PROPERTY = 'hand'; // Used in our message protocol. IWBNI we changed it to handString, but that would break compatability. var WAITING_INTERVAL = 100; // ms var CONNECTING_INTERVAL = 100; // ms var MAKING_CONNECTION_TIMEOUT = 800; // ms @@ -234,7 +235,7 @@ } // This returns the ideal hand joint index for the avatar. - // [hand]middle1 -> [hand]index1 -> [hand] + // [handString]middle1 -> [handString]index1 -> [handString] function getIdealHandJointIndex(avatar, hand) { debug("get hand " + hand + " for avatar " + avatar.sessionUUID); var suffixIndex, jointName, jointIndex, handString = handToString(hand); @@ -313,11 +314,11 @@ } } - function calcParticlePos(myHand, otherHand, otherOrientation, reset) { + function calcParticlePos(myHandPosition, otherHandPosition, otherOrientation, reset) { if (reset) { particleRotationAngle = 0.0; } - var position = positionFractionallyTowards(myHand, otherHand, 0.5); + var position = positionFractionallyTowards(myHandPosition, otherHandPosition, 0.5); particleRotationAngle += PARTICLE_ANGLE_INCREMENT; // about 0.5 hz var radius = Math.min(PARTICLE_RADIUS, PARTICLE_RADIUS * particleRotationAngle / 360); var axis = Vec3.mix(Quat.getFront(MyAvatar.orientation), Quat.inverse(Quat.getFront(otherOrientation)), 0.5); @@ -333,13 +334,13 @@ } var myHandPosition = getHandPosition(MyAvatar, currentHandJointIndex); - var otherHand; + var otherHandPosition; var otherOrientation; if (connectingId) { var other = AvatarList.getAvatar(connectingId); if (other) { otherOrientation = other.orientation; - otherHand = getHandPosition(other, connectingHandJointIndex); + otherHandPosition = getHandPosition(other, connectingHandJointIndex); } } @@ -354,18 +355,18 @@ var particleProps = {}; // put the position between the 2 hands, if we have a connectingId. This // helps define the plane in which the particles move. - positionFractionallyTowards(myHandPosition, otherHand, 0.5); + positionFractionallyTowards(myHandPosition, otherHandPosition, 0.5); // now manage the rest of the entity if (!particleEffect) { particleRotationAngle = 0.0; particleEmitRate = 500; particleProps = PARTICLE_EFFECT_PROPS; particleProps.isEmitting = 0; - particleProps.position = calcParticlePos(myHandPosition, otherHand, otherOrientation); + particleProps.position = calcParticlePos(myHandPosition, otherHandPosition, otherOrientation); particleProps.parentID = MyAvatar.sessionUUID; particleEffect = Entities.addEntity(particleProps, true); } else { - particleProps.position = calcParticlePos(myHandPosition, otherHand, otherOrientation); + particleProps.position = calcParticlePos(myHandPosition, otherHandPosition, otherOrientation); particleProps.isEmitting = 1; Entities.editEntity(particleEffect, particleProps); } @@ -389,7 +390,7 @@ particleEmitRate = Math.max(50, particleEmitRate * 0.5); Entities.editEntity(makingConnectionParticleEffect, {emitRate: 0, isEmitting: 0, position: myHandPosition}); Entities.editEntity(particleEffect, { - position: calcParticlePos(myHandPosition, otherHand, otherOrientation), + position: calcParticlePos(myHandPosition, otherHandPosition, otherOrientation), emitRate: particleEmitRate }); break; @@ -399,14 +400,14 @@ } } - function isNearby(id, hand) { + function isNearby(id, handString) { if (currentHand) { - var handPos = getHandPosition(MyAvatar, currentHandJointIndex); + var handPosition = getHandPosition(MyAvatar, currentHandJointIndex); var avatar = AvatarList.getAvatar(id); if (avatar) { - var otherHand = stringToHand(hand); + var otherHand = stringToHand(handString); var otherHandJointIndex = getIdealHandJointIndex(avatar, otherHand); - var distance = Vec3.distance(getHandPosition(avatar, otherHandJointIndex), handPos); + var distance = Vec3.distance(getHandPosition(avatar, otherHandJointIndex), handPosition); return (distance < MAX_AVATAR_DISTANCE); } } @@ -414,7 +415,7 @@ } function findNearestWaitingAvatar() { - var handPos = getHandPosition(MyAvatar, currentHandJointIndex); + var handPosition = getHandPosition(MyAvatar, currentHandJointIndex); var minDistance = MAX_AVATAR_DISTANCE; var nearestAvatar = {}; Object.keys(waitingList).forEach(function (identifier) { @@ -422,7 +423,7 @@ if (avatar) { var hand = stringToHand(waitingList[identifier]); var handJointIndex = getIdealHandJointIndex(avatar, hand); - var distance = Vec3.distance(getHandPosition(avatar, handJointIndex), handPos); + var distance = Vec3.distance(getHandPosition(avatar, handJointIndex), handPosition); if (distance < minDistance) { minDistance = distance; nearestAvatar = {avatar: identifier, hand: hand, avatarObject: avatar}; @@ -434,6 +435,10 @@ function messageSend(message) { Messages.sendMessage(MESSAGE_CHANNEL, JSON.stringify(message)); } + function handStringMessageSend(message, handString) { + message[HAND_STRING_PROPERTY] = handString; + messageSend(message); + } function lookForWaitingAvatar() { // we started with nobody close enough, but maybe I've moved @@ -452,11 +457,10 @@ connectingId = nearestAvatar.avatar; connectingHandString = handToString(nearestAvatar.hand); debug("sending connectionRequest to", connectingId); - messageSend({ + handStringMessageSend({ key: "connectionRequest", - id: connectingId, - hand: handToString(currentHand) - }); + id: connectingId + }, handToString(currentHand)); } } else { // something happened, stop looking for avatars to connect @@ -494,18 +498,16 @@ connectingHandJointIndex = getIdealHandJointIndex(nearestAvatar.avatarObject, nearestAvatar.hand); currentHandJointIndex = getIdealHandJointIndex(MyAvatar, currentHand); debug("sending connectionRequest to", connectingId); - messageSend({ + handStringMessageSend({ key: "connectionRequest", id: connectingId, - hand: handToString(currentHand) - }); + }, handToString(currentHand)); } else { // send waiting message debug("sending waiting message"); - messageSend({ + handStringMessageSend({ key: "waiting", - hand: handToString(currentHand) - }); + }, handToString(currentHand)); lookForWaitingAvatar(); } } @@ -684,12 +686,12 @@ // to be sure the hand is still close enough. If not, we terminate // the interval, go back to the waiting state. If we make it // the entire CONNECTING_TIME, we make the connection. - function startConnecting(id, hand) { + function startConnecting(id, handString) { var count = 0; - debug("connecting", id, "hand", hand); + debug("connecting", id, "hand", handString); // do we need to do this? connectingId = id; - connectingHandString = hand; + connectingHandString = handString; connectingHandJointIndex = getConnectingHandJointIndex(); state = STATES.CONNECTING; @@ -705,11 +707,10 @@ } // send message that we are connecting with them - messageSend({ + handStringMessageSend({ key: "connecting", - id: id, - hand: handToString(currentHand) - }); + id: id + }, handToString(currentHand)); Controller.triggerHapticPulse(HAPTIC_DATA.initial.strength, HAPTIC_DATA.initial.duration, handToHaptic(currentHand)); connectingInterval = Script.setInterval(function () { @@ -719,7 +720,7 @@ if (state !== STATES.CONNECTING) { debug("stopping connecting interval, state changed"); stopConnecting(); - } else if (!isNearby(id, hand)) { + } else if (!isNearby(id, handString)) { // gotta go back to waiting debug(id, "moved, back to waiting"); stopConnecting(); @@ -769,7 +770,7 @@ case "waiting": // add this guy to waiting object. Any other message from this person will // remove it from the list - waitingList[senderID] = message.hand; + waitingList[senderID] = message[HAND_STRING_PROPERTY]; break; case "connectionRequest": delete waitingList[senderID]; @@ -777,13 +778,12 @@ // you were waiting for a connection request, so send the ack. Or, you and the other // guy raced and both send connectionRequests. Handle that too connectingId = senderID; - connectingHandString = message.hand; + connectingHandString = message[HAND_STRING_PROPERTY]; connectingHandJointIndex = getConnectingHandJointIndex(); - messageSend({ + handStringMessageSend({ key: "connectionAck", id: senderID, - hand: handToString(currentHand) - }); + }, handToString(currentHand)); } else if (state === STATES.WAITING && connectingId === senderID) { // the person you are trying to connect sent a request to someone else. See the // if statement above. So, don't cry, just start the handshake over again @@ -796,7 +796,7 @@ if (message.id === MyAvatar.sessionUUID) { // start connecting... connectingId = senderID; - connectingHandString = message.hand; + connectingHandString = message[HAND_STRING_PROPERTY]; connectingHandJointIndex = getConnectingHandJointIndex(); stopWaiting(); startConnecting(senderID, connectingHandString); @@ -813,17 +813,17 @@ delete waitingList[senderID]; if (state === STATES.WAITING && senderID === connectingId) { // temporary logging - if (connectingHandString !== message.hand) { - debug("connecting hand", connectingHandString, "not same as connecting hand in message", message.hand); + if (connectingHandString !== message[HAND_STRING_PROPERTY]) { + debug("connecting hand", connectingHandString, "not same as connecting hand in message", message[HAND_STRING_PROPERTY]); } - connectingHandString = message.hand; + connectingHandString = message[HAND_STRING_PROPERTY]; if (message.id !== MyAvatar.sessionUUID) { // the person we were trying to connect is connecting to someone else // so try again startHandshake(); break; } - startConnecting(senderID, message.hand); + startConnecting(senderID, connectingHandString); } break; case "done": From d41a911fd2866aeda640d54a7f6347f94d4d6670 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Sat, 29 Apr 2017 19:39:50 -0700 Subject: [PATCH 29/47] setupCandidate --- scripts/system/makeUserConnection.js | 37 +++++++++++----------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index 5540f0c122..f45efafdc8 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -439,6 +439,19 @@ message[HAND_STRING_PROPERTY] = handString; messageSend(message); } + function setupCandidate() { // find the closest in-range avatar, send connection request, an return true. (Otherwise falsey) + var nearestAvatar = findNearestWaitingAvatar(); + if (nearestAvatar.avatar) { + connectingId = nearestAvatar.avatar; + connectingHandString = handToString(nearestAvatar.hand); + debug("sending connectionRequest to", connectingId); + handStringMessageSend({ + key: "connectionRequest", + id: connectingId + }, handToString(currentHand)); + return true; + } + } function lookForWaitingAvatar() { // we started with nobody close enough, but maybe I've moved @@ -451,17 +464,7 @@ debug("started looking for waiting avatars"); waitingInterval = Script.setInterval(function () { if (state === STATES.WAITING && !connectingId) { - // find the closest in-range avatar, and send connection request - var nearestAvatar = findNearestWaitingAvatar(); - if (nearestAvatar.avatar) { - connectingId = nearestAvatar.avatar; - connectingHandString = handToString(nearestAvatar.hand); - debug("sending connectionRequest to", connectingId); - handStringMessageSend({ - key: "connectionRequest", - id: connectingId - }, handToString(currentHand)); - } + setupCandidate(); } else { // something happened, stop looking for avatars to connect stopWaiting(); @@ -490,18 +493,8 @@ stopWaiting(); stopConnecting(); stopMakingConnection(); - - var nearestAvatar = findNearestWaitingAvatar(); - if (nearestAvatar.avatar) { - connectingId = nearestAvatar.avatar; - connectingHandString = handToString(nearestAvatar.hand); - connectingHandJointIndex = getIdealHandJointIndex(nearestAvatar.avatarObject, nearestAvatar.hand); + if (setupCandidate()) { currentHandJointIndex = getIdealHandJointIndex(MyAvatar, currentHand); - debug("sending connectionRequest to", connectingId); - handStringMessageSend({ - key: "connectionRequest", - id: connectingId, - }, handToString(currentHand)); } else { // send waiting message debug("sending waiting message"); From 92481bf475d7b6af005eeecb2e9bae05ec387baa Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Sun, 30 Apr 2017 08:04:46 -0700 Subject: [PATCH 30/47] standardize data init/clear --- scripts/system/makeUserConnection.js | 35 +++++++++++++--------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index f45efafdc8..92d6791617 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -452,6 +452,11 @@ return true; } } + function clearConnecting() { + connectingId = undefined; + connectingHandString = undefined; + connectingHandJointIndex = -1; + } function lookForWaitingAvatar() { // we started with nobody close enough, but maybe I've moved @@ -486,9 +491,7 @@ debug("starting handshake for", currentHand); pollCount = 0; state = STATES.WAITING; - connectingId = undefined; - connectingHandString = undefined; - connectingHandJointIndex = -1; + clearConnecting(); // just in case stopWaiting(); stopConnecting(); @@ -517,9 +520,7 @@ // as we ignore the key release event when inactive. See updateTriggers // below. state = STATES.INACTIVE; - connectingId = undefined; - connectingHandString = undefined; - connectingHandJointIndex = -1; + clearConnecting(); stopWaiting(); stopConnecting(); stopMakingConnection(); @@ -674,6 +675,11 @@ function getConnectingHandJointIndex() { return AvatarList.getAvatarIdentifiers().indexOf(connectingId) !== -1 ? getIdealHandJointIndex(AvatarList.getAvatar(connectingId), stringToHand(connectingHandString)) : -1; } + function setupConnecting(id, handString) { + connectingId = id; + connectingHandString = handString; + connectingHandJointIndex = getConnectingHandJointIndex(); + } // we change states, start the connectionInterval where we check // to be sure the hand is still close enough. If not, we terminate @@ -683,9 +689,7 @@ var count = 0; debug("connecting", id, "hand", handString); // do we need to do this? - connectingId = id; - connectingHandString = handString; - connectingHandJointIndex = getConnectingHandJointIndex(); + setupConnecting(id, handString); state = STATES.CONNECTING; // play sound @@ -770,9 +774,7 @@ if (state === STATES.WAITING && message.id === MyAvatar.sessionUUID && (!connectingId || connectingId === senderID)) { // you were waiting for a connection request, so send the ack. Or, you and the other // guy raced and both send connectionRequests. Handle that too - connectingId = senderID; - connectingHandString = message[HAND_STRING_PROPERTY]; - connectingHandJointIndex = getConnectingHandJointIndex(); + setupConnecting(senderID, message[HAND_STRING_PROPERTY]); handStringMessageSend({ key: "connectionAck", id: senderID, @@ -787,10 +789,7 @@ delete waitingList[senderID]; if (state === STATES.WAITING && (!connectingId || connectingId === senderID)) { if (message.id === MyAvatar.sessionUUID) { - // start connecting... - connectingId = senderID; - connectingHandString = message[HAND_STRING_PROPERTY]; - connectingHandJointIndex = getConnectingHandJointIndex(); + setupConnecting(senderID, message[HAND_STRING_PROPERTY]); stopWaiting(); startConnecting(senderID, connectingHandString); } else if (connectingId) { @@ -835,9 +834,7 @@ // if waiting or inactive, lets clear the connecting id. If in makingConnection, // do nothing if (state !== STATES.MAKING_CONNECTION && connectingId === senderID) { - connectingId = undefined; - connectingHandString = undefined; - connectingHandJointIndex = -1; + clearConnecting(); if (state !== STATES.INACTIVE) { startHandshake(); } From eca72926514d7aad764d704d862fa20f4b589128 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Sun, 30 Apr 2017 09:44:34 -0700 Subject: [PATCH 31/47] check your own location data on failure, and notify if it is wrong --- scripts/system/makeUserConnection.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index 92d6791617..2c984833b4 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -600,6 +600,22 @@ debug("failing with result data", result); // IWBNI we also did some fail sound/visual effect. Window.makeConnection(false, result.connection); + if (Account.isLoggedIn()) { // Give extra failure info + request(location.metaverseServerUrl + '/api/v1/users/' + Account.username + '/location', function (error, response) { + var message = ''; + if (error || response.status !== 'success') { + message = 'Unable to get location.'; + } else if (!response.data || !response.data.location) { + message = "Unexpected location value: " + JSON.stringify(response); + } else if (response.data.location.node_id !== cleanId(MyAvatar.sessionUUID)) { + message = 'Session identification does not match database. Maybe you are logged in on another machine? That would prevent handshakes.' + JSON.stringify(response) + MyAvatar.sessionUUID; + } + if (message) { + Window.makeConnection(false, message); + } + debug("account location:", message || 'ok'); + }); + } UserActivityLogger.makeUserConnection(connectingId, false, result.connection); } var POLL_INTERVAL_MS = 200, POLL_LIMIT = 5; @@ -612,7 +628,7 @@ debug(response, 'pollCount', pollCount); if (pollCount++ >= POLL_LIMIT) { // server will expire, but let's not wait that long. debug('POLL LIMIT REACHED; TIMEOUT: expired message generated by CLIENT'); - result = {status: 'error', connection: 'no-partner-found'}; + result = {status: 'error', connection: 'No logged-in partner found.'}; connectionRequestCompleted(); } else { // poll Script.setTimeout(function () { From 292065a918b7bb30ba3ae9b3801c554073594cbd Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Sun, 30 Apr 2017 13:32:21 -0700 Subject: [PATCH 32/47] It may be possible to switch hands without endHandshake, and thus not reset currentHandJointIndex on updateTrigger. That was filled in later by startHanshake ONLY if there was already waiting avatars so that we didn't have wait. As a result, our distance measure would be from hips instead of hand. This changes it to always compute currentHandJointIndex on updateTriggers (and not elsewhere). --- scripts/system/makeUserConnection.js | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index 2c984833b4..eabd4d5dac 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -255,7 +255,6 @@ function getHandPosition(avatar, handJointIndex) { if (handJointIndex === -1) { debug("calling getHandPosition with no hand joint index! (returning avatar position but this is a BUG)"); - debug(new Error().stack); return avatar.position; } return avatar.getJointPosition(handJointIndex); @@ -439,7 +438,7 @@ message[HAND_STRING_PROPERTY] = handString; messageSend(message); } - function setupCandidate() { // find the closest in-range avatar, send connection request, an return true. (Otherwise falsey) + function setupCandidate() { // find the closest in-range avatar, send connection request, and return true. (Otherwise falsey) var nearestAvatar = findNearestWaitingAvatar(); if (nearestAvatar.avatar) { connectingId = nearestAvatar.avatar; @@ -483,7 +482,7 @@ // them a connectionRequest. If nobody is close enough we send a waiting message, and wait for a // connectionRequest. If the 2 people who want to connect are both somewhat out of range when they // initiate the shake, they will race to see who sends the connectionRequest after noticing the - // waiting message. Either way, they will start connecting eachother at that point. + // waiting message. Either way, they will start connecting each other at that point. function startHandshake(fromKeyboard) { if (fromKeyboard) { startHandshakeAnimation(); @@ -496,9 +495,7 @@ stopWaiting(); stopConnecting(); stopMakingConnection(); - if (setupCandidate()) { - currentHandJointIndex = getIdealHandJointIndex(MyAvatar, currentHand); - } else { + if (!setupCandidate()) { // send waiting message debug("sending waiting message"); handStringMessageSend({ @@ -541,10 +538,8 @@ debug("currentHand", currentHand, "ignoring messages from", hand); return; } - if (!currentHand) { - currentHand = hand; - currentHandJointIndex = getIdealHandJointIndex(MyAvatar, currentHand); - } + currentHand = hand; + currentHandJointIndex = getIdealHandJointIndex(MyAvatar, currentHand); // Always, in case of changed skeleton. // ok now, we are either initiating or quitting... var isGripping = value > GRIP_MIN; if (isGripping) { From bc9049cb42b2ef7217e5f9be5127ef1fd789b69d Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Sun, 30 Apr 2017 14:19:33 -0700 Subject: [PATCH 33/47] remove extra joint search --- scripts/system/makeUserConnection.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index eabd4d5dac..0faaf00ded 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -399,14 +399,12 @@ } } - function isNearby(id, handString) { + function isNearby() { if (currentHand) { var handPosition = getHandPosition(MyAvatar, currentHandJointIndex); - var avatar = AvatarList.getAvatar(id); + var avatar = AvatarList.getAvatar(connectingId); if (avatar) { - var otherHand = stringToHand(handString); - var otherHandJointIndex = getIdealHandJointIndex(avatar, otherHand); - var distance = Vec3.distance(getHandPosition(avatar, otherHandJointIndex), handPosition); + var distance = Vec3.distance(getHandPosition(avatar, connectingHandJointIndex), handPosition); return (distance < MAX_AVATAR_DISTANCE); } } @@ -728,7 +726,7 @@ if (state !== STATES.CONNECTING) { debug("stopping connecting interval, state changed"); stopConnecting(); - } else if (!isNearby(id, handString)) { + } else if (!isNearby()) { // gotta go back to waiting debug(id, "moved, back to waiting"); stopConnecting(); @@ -800,9 +798,8 @@ delete waitingList[senderID]; if (state === STATES.WAITING && (!connectingId || connectingId === senderID)) { if (message.id === MyAvatar.sessionUUID) { - setupConnecting(senderID, message[HAND_STRING_PROPERTY]); stopWaiting(); - startConnecting(senderID, connectingHandString); + startConnecting(senderID, message[HAND_STRING_PROPERTY]); } else if (connectingId) { // this is for someone else (we lost race in connectionRequest), // so lets start over From 2bf7b12c7dc13682424d00ad6cf7dbb9d92ee4be Mon Sep 17 00:00:00 2001 From: druiz17 Date: Sun, 30 Apr 2017 15:27:34 -0700 Subject: [PATCH 34/47] open webview from tabletwebscreen --- interface/resources/qml/controls/TabletWebView.qml | 5 ----- interface/resources/qml/hifi/tablet/TabletRoot.qml | 8 +++++--- interface/resources/qml/hifi/tablet/WindowRoot.qml | 5 +++++ libraries/script-engine/src/TabletScriptingInterface.cpp | 2 +- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/interface/resources/qml/controls/TabletWebView.qml b/interface/resources/qml/controls/TabletWebView.qml index e43441045f..e202d89060 100644 --- a/interface/resources/qml/controls/TabletWebView.qml +++ b/interface/resources/qml/controls/TabletWebView.qml @@ -270,13 +270,8 @@ Item { } onNewViewRequested: { - console.log("--------------> new window opened <-------------"); request.openIn(webview); } - - onWindowCloseRequested: { - console.log("-------------> requested to cloes window <---------------"); - } } HiFiControls.Keyboard { diff --git a/interface/resources/qml/hifi/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml index b19525d21a..e78cc74b7b 100644 --- a/interface/resources/qml/hifi/tablet/TabletRoot.qml +++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml @@ -24,7 +24,6 @@ Item { option = value; } - Component { id: profileCreator; HFTabletWebEngineProfile {} } Component { id: inputDialogBuilder; TabletQueryDialog { } } function inputDialog(properties) { openModal = inputDialogBuilder.createObject(tabletRoot, properties); @@ -91,6 +90,11 @@ Item { loader.item.gotoPreviousApp = true; } } + + function loadWebBase() { + loader.source = ""; + loader.source = "TabletWebView.qml"; + } function returnToPreviousApp() { tabletApps.remove(currentApp); @@ -117,8 +121,6 @@ Item { function loadWebUrl(url, injectedJavaScriptUrl) { tabletApps.clear(); - var newProfile = profileCreator.createObject(); - loader.item.viewProfile = newProfile; loader.item.url = url; loader.item.scriptURL = injectedJavaScriptUrl; tabletApps.append({"appUrl": "TabletWebView.qml", "isWebUrl": true, "scriptUrl": injectedJavaScriptUrl, "appWebUrl": url}); diff --git a/interface/resources/qml/hifi/tablet/WindowRoot.qml b/interface/resources/qml/hifi/tablet/WindowRoot.qml index 5f842df7b7..72dcdf0cbd 100644 --- a/interface/resources/qml/hifi/tablet/WindowRoot.qml +++ b/interface/resources/qml/hifi/tablet/WindowRoot.qml @@ -38,6 +38,11 @@ Windows.ScrollingWindow { loader.source = url; } + function loadWebBase() { + loader.source = ""; + loader.source = "WindowWebView.qml"; + } + function loadWebUrl(url, injectedJavaScriptUrl) { loader.item.url = url; loader.item.scriptURL = injectedJavaScriptUrl; diff --git a/libraries/script-engine/src/TabletScriptingInterface.cpp b/libraries/script-engine/src/TabletScriptingInterface.cpp index bffe318c11..d4eeecc82e 100644 --- a/libraries/script-engine/src/TabletScriptingInterface.cpp +++ b/libraries/script-engine/src/TabletScriptingInterface.cpp @@ -508,7 +508,7 @@ void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaS if (root) { removeButtonsFromHomeScreen(); - QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, QVariant(WEB_VIEW_SOURCE_URL))); + QMetaObject::invokeMethod(root, "loadWebBase"); QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true))); QMetaObject::invokeMethod(root, "loadWebUrl", Q_ARG(const QVariant&, QVariant(url)), Q_ARG(const QVariant&, QVariant(injectedJavaScriptUrl))); } From e740fb67f4b1fa8b404c0cbaa145fd22e1acfeb8 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Sun, 30 Apr 2017 15:50:30 -0700 Subject: [PATCH 35/47] convert foreign handString to jointIndex once, not often --- scripts/system/makeUserConnection.js | 83 +++++++++++----------------- 1 file changed, 31 insertions(+), 52 deletions(-) diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index 0faaf00ded..4b5f74a2aa 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -104,7 +104,6 @@ var makingConnectionTimeout; var animHandlerId; var connectingId; - var connectingHandString; var connectingHandJointIndex = -1; var waitingList = {}; var particleEffect; @@ -119,7 +118,7 @@ function debug() { var stateString = "<" + STATE_STRINGS[state] + ">"; - var connecting = "[" + connectingId + "/" + connectingHandString + "]"; + var connecting = "[" + connectingId + "/" + connectingHandJointIndex + "]"; print.apply(null, [].concat.apply([LABEL, stateString, JSON.stringify(waitingList), connecting], [].map.call(arguments, JSON.stringify))); } @@ -194,17 +193,6 @@ return ""; } - function stringToHand(hand) { - if (hand === "RightHand") { - return Controller.Standard.RightHand; - } - if (hand === "LeftHand") { - return Controller.Standard.LeftHand; - } - debug("stringToHand called with bad hand string:", hand); - return 0; - } - function handToHaptic(hand) { if (hand === Controller.Standard.RightHand) { return 1; @@ -236,10 +224,10 @@ // This returns the ideal hand joint index for the avatar. // [handString]middle1 -> [handString]index1 -> [handString] - function getIdealHandJointIndex(avatar, hand) { - debug("get hand " + hand + " for avatar " + avatar.sessionUUID); - var suffixIndex, jointName, jointIndex, handString = handToString(hand); - for (suffixIndex = 0; suffixIndex < PREFERRER_HAND_JOINT_POSTFIX_ORDER.length; suffixIndex++) { + function getIdealHandJointIndex(avatar, handString) { + debug("get hand " + handString + " for avatar " + (avatar && avatar.sessionUUID)); + var suffixIndex, jointName, jointIndex; + for (suffixIndex = 0; suffixIndex < (avatar ? PREFERRER_HAND_JOINT_POSTFIX_ORDER.length : 0); suffixIndex++) { jointName = handString + PREFERRER_HAND_JOINT_POSTFIX_ORDER[suffixIndex]; jointIndex = avatar.getJointIndex(jointName); if (jointIndex !== -1) { @@ -418,12 +406,11 @@ Object.keys(waitingList).forEach(function (identifier) { var avatar = AvatarList.getAvatar(identifier); if (avatar) { - var hand = stringToHand(waitingList[identifier]); - var handJointIndex = getIdealHandJointIndex(avatar, hand); + var handJointIndex = waitingList[identifier]; var distance = Vec3.distance(getHandPosition(avatar, handJointIndex), handPosition); if (distance < minDistance) { minDistance = distance; - nearestAvatar = {avatar: identifier, hand: hand, avatarObject: avatar}; + nearestAvatar = {avatarId: identifier, jointIndex: handJointIndex}; } } }); @@ -432,26 +419,25 @@ function messageSend(message) { Messages.sendMessage(MESSAGE_CHANNEL, JSON.stringify(message)); } - function handStringMessageSend(message, handString) { - message[HAND_STRING_PROPERTY] = handString; + function handStringMessageSend(message) { + message[HAND_STRING_PROPERTY] = handToString(currentHand); messageSend(message); } function setupCandidate() { // find the closest in-range avatar, send connection request, and return true. (Otherwise falsey) var nearestAvatar = findNearestWaitingAvatar(); - if (nearestAvatar.avatar) { - connectingId = nearestAvatar.avatar; - connectingHandString = handToString(nearestAvatar.hand); + if (nearestAvatar.avatarId) { + connectingId = nearestAvatar.avatarId; + connectingHandJointIndex = nearestAvatar.jointIndex; debug("sending connectionRequest to", connectingId); handStringMessageSend({ key: "connectionRequest", id: connectingId - }, handToString(currentHand)); + }); return true; } } function clearConnecting() { connectingId = undefined; - connectingHandString = undefined; connectingHandJointIndex = -1; } @@ -498,7 +484,7 @@ debug("sending waiting message"); handStringMessageSend({ key: "waiting", - }, handToString(currentHand)); + }); lookForWaitingAvatar(); } } @@ -537,7 +523,7 @@ return; } currentHand = hand; - currentHandJointIndex = getIdealHandJointIndex(MyAvatar, currentHand); // Always, in case of changed skeleton. + 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) { @@ -681,24 +667,20 @@ body: {'user_connection_request': requestBody} }, handleConnectionResponseAndMaybeRepeat); } - function getConnectingHandJointIndex() { - return AvatarList.getAvatarIdentifiers().indexOf(connectingId) !== -1 ? getIdealHandJointIndex(AvatarList.getAvatar(connectingId), stringToHand(connectingHandString)) : -1; - } - function setupConnecting(id, handString) { + function setupConnecting(id, jointIndex) { connectingId = id; - connectingHandString = handString; - connectingHandJointIndex = getConnectingHandJointIndex(); + connectingHandJointIndex = jointIndex; } // we change states, start the connectionInterval where we check // to be sure the hand is still close enough. If not, we terminate // the interval, go back to the waiting state. If we make it // the entire CONNECTING_TIME, we make the connection. - function startConnecting(id, handString) { + function startConnecting(id, jointIndex) { var count = 0; - debug("connecting", id, "hand", handString); + debug("connecting", id, "hand", jointIndex); // do we need to do this? - setupConnecting(id, handString); + setupConnecting(id, jointIndex); state = STATES.CONNECTING; // play sound @@ -716,7 +698,7 @@ handStringMessageSend({ key: "connecting", id: id - }, handToString(currentHand)); + }); Controller.triggerHapticPulse(HAPTIC_DATA.initial.strength, HAPTIC_DATA.initial.duration, handToHaptic(currentHand)); connectingInterval = Script.setInterval(function () { @@ -760,13 +742,16 @@ | ---------- (done) ---------> | */ function messageHandler(channel, messageString, senderID) { + var message = {}; + function exisitingOrSearchedJointIndex() { // If this is a new connectingId, we'll need to find the jointIndex + return connectingId ? connectingHandJointIndex : getIdealHandJointIndex(AvatarList.getAvatar(senderID), message[HAND_STRING_PROPERTY]); + } if (channel !== MESSAGE_CHANNEL) { return; } if (MyAvatar.sessionUUID === senderID) { // ignore my own return; } - var message = {}; try { message = JSON.parse(messageString); } catch (e) { @@ -774,20 +759,19 @@ } switch (message.key) { case "waiting": - // add this guy to waiting object. Any other message from this person will - // remove it from the list - waitingList[senderID] = message[HAND_STRING_PROPERTY]; + // add this guy to waiting object. Any other message from this person will remove it from the list + waitingList[senderID] = getIdealHandJointIndex(AvatarList.getAvatar(senderID), message[HAND_STRING_PROPERTY]); break; case "connectionRequest": delete waitingList[senderID]; if (state === STATES.WAITING && message.id === MyAvatar.sessionUUID && (!connectingId || connectingId === senderID)) { // you were waiting for a connection request, so send the ack. Or, you and the other // guy raced and both send connectionRequests. Handle that too - setupConnecting(senderID, message[HAND_STRING_PROPERTY]); + setupConnecting(senderID, exisitingOrSearchedJointIndex()); handStringMessageSend({ key: "connectionAck", id: senderID, - }, handToString(currentHand)); + }); } else if (state === STATES.WAITING && connectingId === senderID) { // the person you are trying to connect sent a request to someone else. See the // if statement above. So, don't cry, just start the handshake over again @@ -799,7 +783,7 @@ if (state === STATES.WAITING && (!connectingId || connectingId === senderID)) { if (message.id === MyAvatar.sessionUUID) { stopWaiting(); - startConnecting(senderID, message[HAND_STRING_PROPERTY]); + startConnecting(senderID, exisitingOrSearchedJointIndex()); } else if (connectingId) { // this is for someone else (we lost race in connectionRequest), // so lets start over @@ -812,18 +796,13 @@ case "connecting": delete waitingList[senderID]; if (state === STATES.WAITING && senderID === connectingId) { - // temporary logging - if (connectingHandString !== message[HAND_STRING_PROPERTY]) { - debug("connecting hand", connectingHandString, "not same as connecting hand in message", message[HAND_STRING_PROPERTY]); - } - connectingHandString = message[HAND_STRING_PROPERTY]; if (message.id !== MyAvatar.sessionUUID) { // the person we were trying to connect is connecting to someone else // so try again startHandshake(); break; } - startConnecting(senderID, connectingHandString); + startConnecting(senderID, connectingHandJointIndex); } break; case "done": From f60deb0cfcaffce2d06a73fadae17a34e9162f25 Mon Sep 17 00:00:00 2001 From: druiz17 Date: Sun, 30 Apr 2017 22:55:44 -0700 Subject: [PATCH 36/47] 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 0c09823f6731e15e50c5bf361144ea120fb94d20 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 1 May 2017 09:10:31 -0700 Subject: [PATCH 37/47] clang warning fixes, unused variables... --- interface/src/avatar/MyAvatar.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 05e5277746..2966f8a95d 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -83,16 +83,12 @@ const float MyAvatar::ZOOM_MAX = 25.0f; const float MyAvatar::ZOOM_DEFAULT = 1.5f; // default values, used when avatar is missing joints... (avatar space) -static const glm::quat DEFAULT_AVATAR_MIDDLE_EYE_ROT { Quaternions::Y_180 }; +// static const glm::quat DEFAULT_AVATAR_MIDDLE_EYE_ROT { Quaternions::Y_180 }; static const glm::vec3 DEFAULT_AVATAR_MIDDLE_EYE_POS { 0.0f, 0.6f, 0.0f }; -static const glm::quat DEFAULT_AVATAR_HEAD_ROT { Quaternions::Y_180 }; static const glm::vec3 DEFAULT_AVATAR_HEAD_POS { 0.0f, 0.53f, 0.0f }; static const glm::vec3 DEFAULT_AVATAR_NECK_POS { 0.0f, 0.445f, 0.025f }; -static const glm::quat DEFAULT_AVATAR_NECK_ROT { Quaternions::Y_180 }; static const glm::vec3 DEFAULT_AVATAR_SPINE2_POS { 0.0f, 0.32f, 0.02f }; -static const glm::quat DEFAULT_AVATAR_SPINE2_ROT { Quaternions::Y_180}; static const glm::vec3 DEFAULT_AVATAR_HIPS_POS { 0.0f, 0.0f, 0.0f }; -static const glm::quat DEFAULT_AVATAR_HIPS_ROT { Quaternions::Y_180 }; static const glm::vec3 DEFAULT_AVATAR_LEFTFOOT_POS { -0.08f, -0.96f, 0.029f}; static const glm::quat DEFAULT_AVATAR_LEFTFOOT_ROT { -0.40167322754859924f, 0.9154590368270874f, -0.005437685176730156f, -0.023744143545627594f }; static const glm::vec3 DEFAULT_AVATAR_RIGHTFOOT_POS { 0.08f, -0.96f, 0.029f }; From 9f33af479dd97c7b86d0011a88f0e3a4bdd28ccc Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Mon, 1 May 2017 18:04:22 +0100 Subject: [PATCH 38/47] 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 763feccbe104fab542d80fcf67dc1d38188d8ae0 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Mon, 1 May 2017 18:07:42 +0100 Subject: [PATCH 39/47] removed tabs --- interface/resources/qml/hifi/tablet/TabletRoot.qml | 8 ++++---- interface/resources/qml/hifi/tablet/WindowRoot.qml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/interface/resources/qml/hifi/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml index e78cc74b7b..33af7da1ae 100644 --- a/interface/resources/qml/hifi/tablet/TabletRoot.qml +++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml @@ -92,16 +92,16 @@ Item { } function loadWebBase() { - loader.source = ""; - loader.source = "TabletWebView.qml"; + loader.source = ""; + loader.source = "TabletWebView.qml"; } function returnToPreviousApp() { tabletApps.remove(currentApp); var isWebPage = tabletApps.get(currentApp).isWebUrl; if (isWebPage) { - var webUrl = tabletApps.get(currentApp).appWebUrl; - var scriptUrl = tabletApps.get(currentApp).scriptUrl; + var webUrl = tabletApps.get(currentApp).appWebUrl; + var scriptUrl = tabletApps.get(currentApp).scriptUrl; loadSource("TabletWebView.qml"); loadWebUrl(webUrl, scriptUrl); } else { diff --git a/interface/resources/qml/hifi/tablet/WindowRoot.qml b/interface/resources/qml/hifi/tablet/WindowRoot.qml index 72dcdf0cbd..470fd4a830 100644 --- a/interface/resources/qml/hifi/tablet/WindowRoot.qml +++ b/interface/resources/qml/hifi/tablet/WindowRoot.qml @@ -39,8 +39,8 @@ Windows.ScrollingWindow { } function loadWebBase() { - loader.source = ""; - loader.source = "WindowWebView.qml"; + loader.source = ""; + loader.source = "WindowWebView.qml"; } function loadWebUrl(url, injectedJavaScriptUrl) { From c205bf0980a2d4450b5bcaf876cd68f11840406d Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Mon, 1 May 2017 21:23:38 +0100 Subject: [PATCH 40/47] 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 4f368cc867c31165fcae279eb3e7d5eb11373cb7 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 27 Apr 2017 10:47:07 -0700 Subject: [PATCH 41/47] Load High Mips before Fbx after skybox --- .../src/model-networking/TextureCache.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 55704236e3..be3bfcc0e9 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -50,7 +50,8 @@ Q_LOGGING_CATEGORY(trace_resource_parse_image_ktx, "trace.resource.parse.image.k const std::string TextureCache::KTX_DIRNAME { "ktx_cache" }; const std::string TextureCache::KTX_EXT { "ktx" }; -static const int SKYBOX_LOAD_PRIORITY { 10 }; // Make sure skybox loads first +static const float SKYBOX_LOAD_PRIORITY { 10.0f }; // Make sure skybox loads first +static const float HIGH_MIPS_LOAD_PRIORITY { 9.0f }; // Make sure high mips loads after skybox but before models TextureCache::TextureCache() : _ktxCache(KTX_DIRNAME, KTX_EXT) { @@ -261,9 +262,6 @@ QSharedPointer TextureCache::createResource(const QUrl& url, const QSh auto content = textureExtra ? textureExtra->content : QByteArray(); auto maxNumPixels = textureExtra ? textureExtra->maxNumPixels : ABSOLUTE_MAX_TEXTURE_NUM_PIXELS; NetworkTexture* texture = new NetworkTexture(url, type, content, maxNumPixels); - if (type == image::TextureUsage::CUBE_TEXTURE) { - texture->setLoadPriority(this, SKYBOX_LOAD_PRIORITY); - } return QSharedPointer(texture, &Resource::deleter); } @@ -276,6 +274,12 @@ NetworkTexture::NetworkTexture(const QUrl& url, image::TextureUsage::Type type, _textureSource = std::make_shared(); _lowestRequestedMipLevel = 0; + if (type == image::TextureUsage::CUBE_TEXTURE) { + setLoadPriority(this, SKYBOX_LOAD_PRIORITY); + } else if (_sourceIsKTX) { + setLoadPriority(this, HIGH_MIPS_LOAD_PRIORITY); + } + if (!url.isValid()) { _loaded = true; } @@ -397,7 +401,8 @@ void NetworkTexture::startRequestForNextMipLevel() { _ktxResourceState = PENDING_MIP_REQUEST; init(); - setLoadPriority(this, -static_cast(_originalKtxDescriptor->header.numberOfMipmapLevels) + _lowestKnownPopulatedMip); + float priority = -(float)_originalKtxDescriptor->header.numberOfMipmapLevels + (float)_lowestKnownPopulatedMip; + setLoadPriority(this, priority); _url.setFragment(QString::number(_lowestKnownPopulatedMip - 1)); TextureCache::attemptRequest(_self); } From bb9eb986257177f0025975a10e09eee15a30e04b Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Mon, 1 May 2017 21:49:57 +0100 Subject: [PATCH 42/47] prevent mouse events from propagating to far --- interface/resources/qml/controls/TabletWebView.qml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/interface/resources/qml/controls/TabletWebView.qml b/interface/resources/qml/controls/TabletWebView.qml index e202d89060..dd0c3c8135 100644 --- a/interface/resources/qml/controls/TabletWebView.qml +++ b/interface/resources/qml/controls/TabletWebView.qml @@ -84,6 +84,13 @@ Item { horizontalCenter: parent.horizontalCenter; } } + + + MouseArea { + anchors.fill: parent + preventStealing: true + propagateComposedEvents: true + } } ListModel { From 64b8237bd66d69ed2cb53d2728050c525f4780e1 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Mon, 1 May 2017 22:45:13 +0100 Subject: [PATCH 43/47] 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 Date: Mon, 1 May 2017 23:08:25 +0100 Subject: [PATCH 44/47] 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 Date: Mon, 1 May 2017 15:35:17 -0700 Subject: [PATCH 45/47] 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 Date: Mon, 1 May 2017 15:45:26 -0700 Subject: [PATCH 46/47] 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 Date: Mon, 1 May 2017 15:57:22 -0700 Subject: [PATCH 47/47] 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;